From a139c78025076cab7a854f519b4caa53ea257756 Mon Sep 17 00:00:00 2001 From: w568w <1278297578@qq.com> Date: Sat, 8 Aug 2020 15:36:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0Content=20Provider=EF=BC=9B?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=BA=86Markdown=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +- api.md | 88 +++++ .../io/legado/app/ExampleInstrumentedTest.kt | 11 +- app/src/main/AndroidManifest.xml | 197 +++++----- .../io/legado/app/api/ReaderProvider.java | 358 ++++++++++++++++++ 5 files changed, 551 insertions(+), 109 deletions(-) create mode 100644 api.md create mode 100644 app/src/main/java/io/legado/app/api/ReaderProvider.java diff --git a/README.md b/README.md index 7c014360e..b7249d50a 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,11 @@ [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) ## 阅读3.0 -书源规则 https://celeter.github.io +书源规则 https://celeter.github.io +### 阅读API +阅读3.0 提供了2种方式的API:`Web方式`和`Content Provider方式`。您可以在[这里](api.md)根据需要自行调用。 +## 免责声明 [![aVElJs.th.jpg](https://s1.ax1x.com/2020/07/28/aVElJs.th.jpg)](https://imgchr.com/i/aVElJs) [![aVEQij.th.jpg](https://s1.ax1x.com/2020/07/28/aVEQij.th.jpg)](https://imgchr.com/i/aVEQij) [![aVEudg.th.jpg](https://s1.ax1x.com/2020/07/28/aVEudg.th.jpg)](https://imgchr.com/i/aVEudg) [![aVEneS.th.jpg](https://s1.ax1x.com/2020/07/28/aVEneS.th.jpg)](https://imgchr.com/i/aVEneS) [![aVEKoQ.th.jpg](https://s1.ax1x.com/2020/07/28/aVEKoQ.th.jpg)](https://imgchr.com/i/aVEKoQ) [![aVE1Wn.th.jpg](https://s1.ax1x.com/2020/07/28/aVE1Wn.th.jpg)](https://imgchr.com/i/aVE1Wn) -## 免责声明 https://gedoor.github.io/MyBookshelf/disclaimer.html diff --git a/api.md b/api.md new file mode 100644 index 000000000..3c4aa0983 --- /dev/null +++ b/api.md @@ -0,0 +1,88 @@ +#阅读API +## 对于Web的配置 +您需要先在设置中启用"Web 服务"。 +## 使用 +### Web +待补充 +### Content Provider +#### 插入单个书源 +``` +URL = content://io.legado.app.api.ReaderProvider/source/insert +Method = insert +``` + +创建`Key="json"`的`ContentValues`,内容为`JSON`字符串, +格式参考[这个文件](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/BookSource.kt) + +#### 插入多个书源 +``` +URL = content://io.legado.app.api.ReaderProvider/sources/insert +Method = insert +``` + +创建`Key="json"`的`ContentValues`,内容为`JSON`字符串, +格式参考[这个文件](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/BookSource.kt),**为数组格式**。 + +#### 获取书源 +``` +URL = content://io.legado.app.api.ReaderProvider/source/query?url=xxx +Method = query +``` + +获取指定URL对应的书源信息。 +用`Cursor.getString(0)`取出返回结果。 + +#### 获取所有书源 +``` +URL = content://io.legado.app.api.ReaderProvider/sources/query +Method = query +``` + +获取APP内的所有书源。 +用`Cursor.getString(0)`取出返回结果。 + +#### 删除多个书源 +``` +URL = content://io.legado.app.api.ReaderProvider/sources/delete +Method = delete +``` + +创建`Key="json"`的`ContentValues`,内容为`JSON`字符串, +格式参考[这个文件](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/BookSource.kt),**为数组格式**。 + +#### 插入书籍 +``` +URL = content://io.legado.app.api.ReaderProvider/book/insert +Method = insert +``` + +创建`Key="json"`的`ContentValues`,内容为`JSON`字符串, +格式参考[这个文件](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/Book.kt)。 + +#### 获取所有书籍 +``` +URL = content://io.legado.app.api.ReaderProvider/books/query +Method = query +``` + +获取APP内的所有书籍。 +用`Cursor.getString(0)`取出返回结果。 + +#### 获取书籍章节列表 +``` +URL = content://io.legado.app.api.ReaderProvider/book/chapter/query?url=xxx +Method = query +``` + +获取指定图书的章节列表。 +用`Cursor.getString(0)`取出返回结果。 + +#### 获取书籍内容 + +``` +URL = content://io.legado.app.api.ReaderProvider/book/content/query?url=xxx&index=1 +Method = query +``` + +获取指定图书的第`index`章节的文本内容。 +用`Cursor.getString(0)`取出返回结果。 \ No newline at end of file diff --git a/app/src/androidTest/java/io/legado/app/ExampleInstrumentedTest.kt b/app/src/androidTest/java/io/legado/app/ExampleInstrumentedTest.kt index 86b312778..9820b40fb 100644 --- a/app/src/androidTest/java/io/legado/app/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/io/legado/app/ExampleInstrumentedTest.kt @@ -1,5 +1,7 @@ package io.legado.app +import android.net.Uri +import android.util.Log import androidx.test.InstrumentationRegistry import androidx.test.runner.AndroidJUnit4 @@ -16,9 +18,12 @@ import org.junit.Assert.* @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { @Test - fun useAppContext() { + fun testContentProvider() { // Context of the app under test. val appContext = InstrumentationRegistry.getTargetContext() - assertEquals("cn.legado.book", appContext.packageName) - } + Log.d("test", + appContext.contentResolver.query(Uri.parse("content://io.legado.app.api.ReaderProvider/sources/query"),null,null,null,null) + !!.getString(0) + ) + } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index db69e078b..7d0a3f19e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -25,164 +25,163 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:networkSecurityConfig="@xml/network_security_config" - android:supportsRtl="true" android:requestLegacyExternalStorage="true" + android:supportsRtl="true" android:theme="@style/AppTheme.Light" tools:ignore="AllowBackup,GoogleAppIndexingWarning,UnusedAttribute"> - + + + + - - + android:launchMode="singleTask" + android:resource="@xml/shortcuts" /> + + android:enabled="false" + android:icon="@mipmap/launcher1"> + + - - + android:launchMode="singleTask" + android:resource="@xml/shortcuts" /> + + android:enabled="false" + android:icon="@mipmap/launcher2"> + + - - + android:launchMode="singleTask" + android:resource="@xml/shortcuts" /> + + android:enabled="false" + android:icon="@mipmap/launcher3"> + + - - + android:launchMode="singleTask" + android:resource="@xml/shortcuts" /> + + android:enabled="false" + android:icon="@mipmap/launcher4"> + + - - + android:launchMode="singleTask" + android:resource="@xml/shortcuts" /> + + android:enabled="false" + android:icon="@mipmap/launcher5"> + + - - + android:launchMode="singleTask" + android:resource="@xml/shortcuts" /> + + android:enabled="false" + android:icon="@mipmap/launcher6"> + + - - + android:launchMode="singleTask" + android:resource="@xml/shortcuts" /> + - + android:alwaysRetainTaskState="true" + android:launchMode="singleTask" /> - + android:launchMode="singleTask" /> - + android:launchMode="singleTop" /> - + android:name=".ui.book.info.edit.BookInfoEditActivity" + android:launchMode="singleTask" /> - + android:launchMode="singleTask" /> - + android:theme="@style/Activity.Permission" /> - + android:launchMode="singleTask" /> - + android:windowSoftInputMode="adjustResize|stateHidden" /> - + android:windowSoftInputMode="adjustResize|stateHidden" /> - + android:launchMode="singleTask" /> - + android:name=".ui.book.search.SearchActivity" + android:launchMode="singleTask" /> - + android:launchMode="singleTask" /> - + android:launchMode="singleTask" /> @@ -196,8 +195,7 @@ android:host="booksource" android:scheme="yuedu" /> - - + @@ -211,8 +209,7 @@ android:host="rsssource" android:scheme="yuedu" /> - - + @@ -226,67 +223,59 @@ android:host="replace" android:scheme="yuedu" /> - - + - + android:launchMode="singleTop" /> - + android:launchMode="singleTop" /> - + android:name=".ui.book.chapterlist.ChapterListActivity" + android:launchMode="singleTop" + android:screenOrientation="behind" /> - + android:launchMode="singleTop" /> - + android:launchMode="singleTop" /> - + android:name=".ui.book.local.ImportBookActivity" + android:launchMode="singleTop" /> - + android:name=".ui.book.explore.ExploreShowActivity" + android:launchMode="singleTop" /> - + android:launchMode="singleTop" /> - + android:launchMode="singleTop" /> - + android:name=".ui.book.download.DownloadActivity" + android:launchMode="singleTop" /> - + android:hardwareAccelerated="true" /> + + + + diff --git a/app/src/main/java/io/legado/app/api/ReaderProvider.java b/app/src/main/java/io/legado/app/api/ReaderProvider.java new file mode 100644 index 000000000..81d918219 --- /dev/null +++ b/app/src/main/java/io/legado/app/api/ReaderProvider.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2020 w568w + */ + +package io.legado.app.api; + +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.UriMatcher; +import android.database.CharArrayBuffer; +import android.database.ContentObserver; +import android.database.Cursor; +import android.database.DataSetObserver; +import android.net.Uri; +import android.os.Bundle; + +import com.google.gson.Gson; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import io.legado.app.web.controller.BookshelfController; +import io.legado.app.web.controller.SourceController; +import io.legado.app.web.utils.ReturnData; + +/** + * Export book data to other app. + */ +public class ReaderProvider extends ContentProvider { + private enum RequestCode { + saveSource, saveSources, saveBook, deleteSources, getSource, getSources, getBookshelf, getChapterList, getBookContent + } + + public static final String POST_BODY_KEY = "json"; + public static final String AUTHORITY = "io.legado.app.api.ReaderProvider"; + private static UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH); + private SourceController mSourceController; + private BookshelfController mBookshelfController; + + static { + sMatcher.addURI(AUTHORITY, "source/insert", RequestCode.saveSource.ordinal()); + sMatcher.addURI(AUTHORITY, "sources/insert", RequestCode.saveSources.ordinal()); + sMatcher.addURI(AUTHORITY, "book/insert", RequestCode.saveBook.ordinal()); + sMatcher.addURI(AUTHORITY, "sources/delete", RequestCode.deleteSources.ordinal()); + sMatcher.addURI(AUTHORITY, "source/query", RequestCode.getSource.ordinal()); + sMatcher.addURI(AUTHORITY, "sources/query", RequestCode.getSources.ordinal()); + sMatcher.addURI(AUTHORITY, "books/query", RequestCode.getBookshelf.ordinal()); + sMatcher.addURI(AUTHORITY, "book/chapter/query", RequestCode.getChapterList.ordinal()); + sMatcher.addURI(AUTHORITY, "book/content/query", RequestCode.getBookContent.ordinal()); + } + + public ReaderProvider() { + } + + @Override + public int delete(@NotNull Uri uri, String selection, String[] selectionArgs) { + if (sMatcher.match(uri) < 0) + return -1; + switch (RequestCode.values()[sMatcher.match(uri)]) { + case deleteSources: + mSourceController.deleteSources(selection); + break; + default: + throw new IllegalStateException("Unexpected value: " + RequestCode.values()[sMatcher.match(uri)].name()); + } + return 0; + } + + @Override + public String getType(@NotNull Uri uri) { + throw new UnsupportedOperationException("Not yet implemented"); + } + + @Override + public Uri insert(@NotNull Uri uri, ContentValues values) { + if (sMatcher.match(uri) < 0) + return null; + switch (RequestCode.values()[sMatcher.match(uri)]) { + case saveSource: + mSourceController.saveSource(values.getAsString(POST_BODY_KEY)); + break; + case saveBook: + mBookshelfController.saveBook(values.getAsString(POST_BODY_KEY)); + case saveSources: + mSourceController.saveSources(values.getAsString(POST_BODY_KEY)); + default: + throw new IllegalStateException("Unexpected value: " + RequestCode.values()[sMatcher.match(uri)].name()); + } + return null; + } + + @Override + public boolean onCreate() { + mSourceController = new SourceController(); + mBookshelfController = new BookshelfController(); + return false; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + + Map> map = new HashMap<>(); + + ArrayList list = new ArrayList<>(); + list.add(uri.getQueryParameter("url")); + map.put("url", list); + + list = new ArrayList<>(); + list.add(uri.getQueryParameter("index")); + map.put("index", list); + + if (sMatcher.match(uri) < 0) + return null; + switch (RequestCode.values()[sMatcher.match(uri)]) { + case getSource: + return new SimpleCursor(mSourceController.getSource(map)); + case getSources: + return new SimpleCursor(mSourceController.getSources()); + case getBookshelf: + return new SimpleCursor(mBookshelfController.getBookshelf()); + case getBookContent: + return new SimpleCursor(mBookshelfController.getBookContent(map)); + case getChapterList: + return new SimpleCursor(mBookshelfController.getChapterList(map)); + default: + throw new IllegalStateException("Unexpected value: " + RequestCode.values()[sMatcher.match(uri)].name()); + } + } + + @Override + public int update(@NotNull Uri uri, ContentValues values, String selection, + String[] selectionArgs) { + throw new UnsupportedOperationException("Not yet implemented"); + } + + /** + * Simple inner class to deliver json callback data. + * + * Only getString() makes sense. + */ + private static class SimpleCursor implements Cursor { + private String mData; + + public SimpleCursor(ReturnData data) { + this.mData = new Gson().toJson(data); + } + + @Override + public int getCount() { + return 1; + } + + @Override + public int getPosition() { + return 0; + } + + @Override + public boolean move(int i) { + return true; + } + + @Override + public boolean moveToPosition(int i) { + return true; + } + + @Override + public boolean moveToFirst() { + return true; + } + + @Override + public boolean moveToLast() { + return true; + } + + @Override + public boolean moveToNext() { + return true; + } + + @Override + public boolean moveToPrevious() { + return true; + } + + @Override + public boolean isFirst() { + return true; + } + + @Override + public boolean isLast() { + return true; + } + + @Override + public boolean isBeforeFirst() { + return false; + } + + @Override + public boolean isAfterLast() { + return false; + } + + @Override + public int getColumnIndex(String s) { + return 0; + } + + @Override + public int getColumnIndexOrThrow(String s) throws IllegalArgumentException { + throw new UnsupportedOperationException("Not yet implemented"); + } + + @Override + public String getColumnName(int i) { + return null; + } + + @Override + public String[] getColumnNames() { + return new String[0]; + } + + @Override + public int getColumnCount() { + return 0; + } + + @Override + public byte[] getBlob(int i) { + return new byte[0]; + } + + @Override + public String getString(int i) { + return mData; + } + + @Override + public void copyStringToBuffer(int i, CharArrayBuffer charArrayBuffer) { + + } + + @Override + public short getShort(int i) { + return 0; + } + + @Override + public int getInt(int i) { + return 0; + } + + @Override + public long getLong(int i) { + return 0; + } + + @Override + public float getFloat(int i) { + return 0; + } + + @Override + public double getDouble(int i) { + return 0; + } + + @Override + public int getType(int i) { + return 0; + } + + @Override + public boolean isNull(int i) { + return false; + } + + @Override + public void deactivate() { + + } + + @Override + public boolean requery() { + return false; + } + + @Override + public void close() { + + } + + @Override + public boolean isClosed() { + return false; + } + + @Override + public void registerContentObserver(ContentObserver contentObserver) { + + } + + @Override + public void unregisterContentObserver(ContentObserver contentObserver) { + + } + + @Override + public void registerDataSetObserver(DataSetObserver dataSetObserver) { + + } + + @Override + public void unregisterDataSetObserver(DataSetObserver dataSetObserver) { + + } + + @Override + public void setNotificationUri(ContentResolver contentResolver, Uri uri) { + + } + + @Override + public Uri getNotificationUri() { + return null; + } + + @Override + public boolean getWantsAllOnMoveCalls() { + return false; + } + + @Override + public void setExtras(Bundle bundle) { + + } + + @Override + public Bundle getExtras() { + return null; + } + + @Override + public Bundle respond(Bundle bundle) { + return null; + } + } +}