Compare commits

...

8 Commits

Author SHA1 Message Date
Pupil
547b6e8e3b Fixed renamed file 2020-02-09 18:16:45 +09:00
Pupil
d88ac27e72 Fixed renamed file 2020-02-09 18:15:46 +09:00
Pupil
e551a40d08 Fixed renamed file 2020-02-09 18:13:16 +09:00
Pupil
e810abe33a Bug fix 2020-02-09 17:57:18 +09:00
Pupil
6172a73719 Bug fix 2020-02-09 17:36:28 +09:00
Pupil
7455e68a45 Bug fix 2020-02-09 17:35:34 +09:00
Pupil
748495ca64 Downloader thread number to 4 2020-02-09 17:25:55 +09:00
Pupil
f6d9c7f550 Bug fix
Networking optimized
2020-02-09 17:11:35 +09:00
16 changed files with 139 additions and 88 deletions

View File

@@ -19,8 +19,8 @@ android {
applicationId "xyz.quaver.pupil"
minSdkVersion 16
targetSdkVersion 29
versionCode 33
versionName "5.0"
versionCode 37
versionName "5.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
vectorDrawables.useSupportLibrary = true

View File

@@ -1 +1 @@
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":33,"versionName":"5.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":37,"versionName":"5.4","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]

View File

@@ -22,7 +22,7 @@
tools:replace="android:theme">
<provider
android:authorities="${applicationId}.fileprovider"
android:authorities="${applicationId}.provider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">

View File

@@ -49,7 +49,11 @@ class Pupil : MultiDexApplication() {
histories = Histories(File(ContextCompat.getDataDir(this), "histories.json"))
favorites = Histories(File(ContextCompat.getDataDir(this), "favorites.json"))
val download = preference.getString("dl_location", null)
val download = try {
preference.getString("dl_location", null)
} catch (e: Exception) {
preference.edit().remove("dl_location").apply()
}
if (download == null) {
val default = ContextCompat.getExternalFilesDirs(this, null)[0]

View File

@@ -129,7 +129,12 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
})
CoroutineScope(Dispatchers.Main).launch {
val thumbnail = Base64.decode(Cache(context).getThumbnail(galleryBlock.id), Base64.DEFAULT)
val thumbnail = Cache(context).getThumbnail(galleryBlock.id).let {
if (it != null)
Base64.decode(it, Base64.DEFAULT)
else
null
}
glide
.load(thumbnail)

View File

@@ -26,7 +26,8 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.google.android.material.snackbar.Snackbar
import com.crashlytics.android.Crashlytics
import io.fabric.sdk.android.Fabric
import kotlinx.android.synthetic.main.item_reader.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -108,17 +109,13 @@ class ReaderAdapter(private val context: Context,
val progress = DownloadWorker.getInstance(context).progress[galleryID]?.get(position)
if (progress?.isNaN() == true) {
if (Fabric.isInitialized())
Crashlytics.logException(DownloadWorker.getInstance(context).exception[galleryID]?.get(position))
glide
.load(R.drawable.image_broken_variant)
.into(holder.view.image)
Snackbar
.make(
holder.view,
DownloadWorker.getInstance(context).exception[galleryID]!![position]?.message
?: context.getText(R.string.default_error_msg),
Snackbar.LENGTH_INDEFINITE
)
.show()
return
}

View File

@@ -969,11 +969,11 @@ class MainActivity : AppCompatActivity() {
}
}
Mode.DOWNLOAD -> {
val downloads = getDownloadDirectory(this@MainActivity)?.listFiles()?.filter { file ->
val downloads = getDownloadDirectory(this@MainActivity).listFiles().filter { file ->
file.isDirectory && (file.name!!.toIntOrNull() != null) && file.findFile(".metadata") != null
}?.map {
}.map {
it.name!!.toInt()
}?: listOf()
}
when {
query.isEmpty() -> downloads.apply {

View File

@@ -162,7 +162,7 @@ class ReaderActivity : AppCompatActivity() {
val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, findViewById(android.R.id.content), false)
with(view.dialog_number_picker) {
minValue=1
maxValue=reader_recyclerview.adapter?.itemCount ?: 0
maxValue=reader_recyclerview?.adapter?.itemCount ?: 0
value=currentPage
}
val dialog = AlertDialog.Builder(this).apply {
@@ -196,7 +196,7 @@ class ReaderActivity : AppCompatActivity() {
super.onDestroy()
timer.cancel()
(reader_recyclerview.adapter as ReaderAdapter).timer.cancel()
(reader_recyclerview?.adapter as? ReaderAdapter)?.timer?.cancel()
if (!Cache(this).isDownloading(galleryID))
DownloadWorker.getInstance(this@ReaderActivity).cancel(galleryID)

View File

@@ -101,7 +101,7 @@ class SettingsFragment :
}.show()
}
"delete_downloads" -> {
val dir = getDownloadDirectory(context)!!
val dir = getDownloadDirectory(context)
AlertDialog.Builder(context).apply {
setTitle(R.string.warning)
@@ -150,7 +150,12 @@ class SettingsFragment :
"backup" -> {
File(ContextCompat.getDataDir(context), "favorites.json").copyTo(
context,
getDownloadDirectory(context)?.createFile("null", "favorites.json")!!
getDownloadDirectory(context).let {
if (it.findFile("favorites.json") != null)
it
else
it.createFile("null", "favorites.json")!!
}
)
Snackbar.make(this@SettingsFragment.listView, R.string.settings_backup_snackbar, Snackbar.LENGTH_LONG)
@@ -192,8 +197,7 @@ class SettingsFragment :
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
when (key) {
"dl_location" -> {
findPreference<Preference>(key)?.summary =
FileUtils.getPath(context, getDownloadDirectory(context!!)?.uri)
findPreference<Preference>(key)?.summary = getDownloadDirectory(context!!).uri.path
}
}
}
@@ -230,7 +234,7 @@ class SettingsFragment :
onPreferenceClickListener = this@SettingsFragment
}
"delete_downloads" -> {
val dir = getDownloadDirectory(context)!!
val dir = getDownloadDirectory(context)
summary = getDirSize(dir)
onPreferenceClickListener = this@SettingsFragment
@@ -242,7 +246,7 @@ class SettingsFragment :
onPreferenceClickListener = this@SettingsFragment
}
"dl_location" -> {
summary = FileUtils.getPath(context, getDownloadDirectory(context)?.uri)
summary = getDownloadDirectory(context).uri.path
onPreferenceClickListener = this@SettingsFragment
}

View File

@@ -23,11 +23,15 @@ import android.content.ContextWrapper
import android.util.Base64
import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.parse
import kotlinx.serialization.stringify
import xyz.quaver.Code
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.Reader
import xyz.quaver.pupil.util.*
@@ -41,7 +45,7 @@ class Cache(context: Context) : ContextWrapper(context) {
// Search in this order
// Download -> Cache
fun getCachedGallery(galleryID: Int) : DocumentFile? {
var file = getDownloadDirectory(this)?.findFile(galleryID.toString())
var file = getDownloadDirectory(this).findFile(galleryID.toString())
if (file?.exists() == true)
return file
@@ -107,17 +111,21 @@ class Cache(context: Context) : ContextWrapper(context) {
suspend fun getGalleryBlock(galleryID: Int): GalleryBlock? {
val metadata = Cache(this).getCachedMetadata(galleryID)
val source = mapOf(
Code.HITOMI to { xyz.quaver.hitomi.getGalleryBlock(galleryID) },
Code.HIYOBI to { xyz.quaver.hiyobi.getGalleryBlock(galleryID) }
)
val galleryBlock = if (metadata?.galleryBlock == null)
listOf(
{ xyz.quaver.hitomi.getGalleryBlock(galleryID) },
{ xyz.quaver.hiyobi.getGalleryBlock(galleryID) }
).map {
source.entries.map {
CoroutineScope(Dispatchers.IO).async {
kotlin.runCatching {
it.invoke()
it.value.invoke()
}.getOrNull()
}
}.awaitAll().filterNotNull()
}.firstOrNull {
it.await() != null
}?.await()
else
metadata.galleryBlock
@@ -126,52 +134,56 @@ class Cache(context: Context) : ContextWrapper(context) {
Metadata(Cache(this).getCachedMetadata(galleryID), galleryBlock = galleryBlock)
)
val mirrors = preference.getString("mirrors", "")!!.split('>')
return galleryBlock.firstOrNull {
mirrors.contains(it.code.name)
} ?: galleryBlock.firstOrNull()
return galleryBlock
}
fun getReaderOrNull(galleryID: Int): Reader? {
val metadata = getCachedMetadata(galleryID)
val mirrors = preference.getString("mirrors", "")!!.split('>')
return metadata?.readers?.firstOrNull {
mirrors.contains(it.code.name)
} ?: metadata?.readers?.firstOrNull()
return getCachedMetadata(galleryID)?.reader
}
suspend fun getReader(galleryID: Int): Reader? {
val metadata = getCachedMetadata(galleryID)
val mirrors = preference.getString("mirrors", null)?.split('>') ?: listOf()
val readers = if (metadata?.readers == null) {
listOf(
{ xyz.quaver.hitomi.getReader(galleryID) },
{ xyz.quaver.hiyobi.getReader(galleryID) }
).map {
val sources = mapOf(
Code.HITOMI to { xyz.quaver.hitomi.getReader(galleryID) },
Code.HIYOBI to { xyz.quaver.hiyobi.getReader(galleryID) }
).let {
if (mirrors.isNotEmpty())
it.toSortedMap(
Comparator { o1, o2 ->
mirrors.indexOf(o1.name) - mirrors.indexOf(o2.name)
}
)
else
it
}
val reader = if (metadata?.reader == null) {
CoroutineScope(Dispatchers.IO).async {
kotlin.runCatching {
it.invoke()
var retval: Reader? = null
for (source in sources) {
retval = kotlin.runCatching {
source.value.invoke()
}.getOrNull()
}
}.awaitAll().filterNotNull()
} else {
metadata.readers
if (retval != null)
break
}
if (readers.isNotEmpty())
retval
}.await()
} else
metadata.reader
if (reader != null)
setCachedMetadata(
galleryID,
Metadata(Cache(this).getCachedMetadata(galleryID), readers = readers)
Metadata(Cache(this).getCachedMetadata(galleryID), readers = reader)
)
val mirrors = preference.getString("mirrors", "")!!.split('>')
return readers.firstOrNull {
mirrors.contains(it.code.name)
} ?: readers.firstOrNull()
return reader
}
fun getImages(galleryID: Int): List<DocumentFile?>? {
@@ -180,7 +192,7 @@ class Cache(context: Context) : ContextWrapper(context) {
val images = gallery.listFiles()
return reader.galleryInfo.indices.map { index ->
images.firstOrNull { file -> file.name?.startsWith(index.toString()) == true }
images.firstOrNull { file -> file.name?.startsWith("%05d".format(index)) == true }
}
}
@@ -194,21 +206,26 @@ class Cache(context: Context) : ContextWrapper(context) {
if (!Regex("""^[0-9]+.+$""").matches(name))
throw IllegalArgumentException("File name is not a number")
cache.createFile("null", name)?.writeBytes(this, data)
cache.let {
if (it.findFile(name) != null)
it
else
it.createFile("null", name)
}?.writeBytes(this, data)
}
fun moveToDownload(galleryID: Int) {
val cache = getCachedGallery(galleryID)
if (cache != null) {
val download = getDownloadDirectory(this)!!
val download = getDownloadDirectory(this)
if (!download.isParentOf(cache)) {
cache.copyRecursively(this, download)
cache.deleteRecursively()
}
} else
getDownloadDirectory(this)?.createDirectory(galleryID.toString())
getDownloadDirectory(this).createDirectory(galleryID.toString())
}
fun isDownloading(galleryID: Int) = getCachedMetadata(galleryID)?.isDownloading == true

View File

@@ -162,7 +162,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
.body(ProgressResponseBody(request.tag(), response.body(), progressListener))
.build()
}
.dispatcher(Dispatcher(Executors.newSingleThreadExecutor()))
.dispatcher(Dispatcher(Executors.newFixedThreadPool(4)))
.build()
fun stop() {
@@ -279,6 +279,9 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
progress.put(galleryID, reader.galleryInfo.map { 0F }.toMutableList())
exception.put(galleryID, reader.galleryInfo.map { null }.toMutableList())
if (notification[galleryID] == null)
initNotification(galleryID)
notification[galleryID].setContentTitle(reader.title)
notify(galleryID)
@@ -309,7 +312,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
val ext =
call.request().url().encodedPath().split('.').last()
Cache(this@DownloadWorker).putImage(galleryID, "$i.$ext", res)
Cache(this@DownloadWorker).putImage(galleryID, "%05d.%s".format(i, ext), res)
progress[galleryID]?.set(i, Float.POSITIVE_INFINITY)
}

View File

@@ -25,20 +25,20 @@ import xyz.quaver.hitomi.Reader
@Serializable
data class Metadata(
val thumbnail: String? = null,
val galleryBlock: List<GalleryBlock>? = null,
val readers: List<Reader>? = null,
val galleryBlock: GalleryBlock? = null,
val reader: Reader? = null,
val isDownloading: Boolean? = null
) {
constructor(
metadata: Metadata?,
thumbnail: String? = null,
galleryBlock: List<GalleryBlock>? = null,
readers: List<Reader>? = null,
galleryBlock: GalleryBlock? = null,
readers: Reader? = null,
isDownloading: Boolean? = null
) : this(
thumbnail ?: metadata?.thumbnail,
galleryBlock ?: metadata?.galleryBlock,
readers ?: metadata?.readers,
readers ?: metadata?.reader,
isDownloading ?: metadata?.isDownloading
)
}

View File

@@ -20,6 +20,7 @@ package xyz.quaver.pupil.util
import android.content.Context
import android.net.Uri
import androidx.core.content.FileProvider
import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager
import java.io.File
@@ -28,20 +29,29 @@ import java.nio.charset.Charset
import java.util.*
fun getCachedGallery(context: Context, galleryID: Int) =
getDownloadDirectory(context)?.findFile(galleryID.toString()) ?:
getDownloadDirectory(context).findFile(galleryID.toString()) ?:
DocumentFile.fromFile(File(context.cacheDir, "imageCache/$galleryID"))
fun getDownloadDirectory(context: Context) : DocumentFile? {
fun getDownloadDirectory(context: Context) : DocumentFile {
val uri = PreferenceManager.getDefaultSharedPreferences(context).getString("dl_location", null).let {
if (it != null)
Uri.parse(it)
else
Uri.fromFile(context.getExternalFilesDir(null))
}
return if (uri.toString().startsWith("file"))
DocumentFile.fromFile(File(uri.path!!))
else
DocumentFile.fromTreeUri(context, uri)
DocumentFile.fromTreeUri(context, uri) ?: DocumentFile.fromFile(context.getExternalFilesDir(null)!!)
}
fun convertUpdateUri(context: Context, uri: Uri) : Uri =
if (uri.toString().startsWith("file"))
FileProvider.getUriForFile(context, context.applicationContext.packageName + ".provider", File(uri.path!!.substringAfter("file:///")))
else
uri
fun URL.download(context: Context, to: DocumentFile, onDownloadProgress: ((Long, Long) -> Unit)? = null) {
context.contentResolver.openOutputStream(to.uri).use { out ->
out!!
@@ -94,12 +104,17 @@ fun DocumentFile.copyRecursively(
if (!exists())
throw Exception("The source file doesn't exist.")
if (this.isFile)
target.createFile("null", name!!)!!.writeBytes(
if (this.isFile) {
target.let {
if (it.findFile(name!!) != null)
it
else
createFile("null", name!!)!!
}.writeBytes(
context,
readBytes(context)
)
else if (this.isDirectory) {
} else if (this.isDirectory) {
target.createDirectory(name!!).also { newTarget ->
listFiles().forEach { child ->
child.copyRecursively(context, newTarget!!)

View File

@@ -146,7 +146,12 @@ fun checkUpdate(context: AppCompatActivity, force: Boolean = false) {
}
CoroutineScope(Dispatchers.IO).launch io@{
val target = getDownloadDirectory(context)?.createFile("null", "Pupil.apk")!!
val target = getDownloadDirectory(context).let {
if (it.findFile("Pupil.apk") != null)
it
else
it.createFile("null", "Pupil.apk")!!
}
try {
URL(url).download(context, target) { progress, fileSize ->
@@ -168,7 +173,7 @@ fun checkUpdate(context: AppCompatActivity, force: Boolean = false) {
val install = Intent(Intent.ACTION_VIEW).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_GRANT_READ_URI_PERMISSION
setDataAndType(target.uri, MimeTypeMap.getSingleton().getMimeTypeFromExtension("apk"))
setDataAndType(convertUpdateUri(context, target.uri), MimeTypeMap.getSingleton().getMimeTypeFromExtension("apk"))
}
builder.apply {

View File

@@ -17,6 +17,7 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<paths>
<external-path name="external" path="/"/>
<external-files-path name="files" path="/"/>
</paths>

View File

@@ -75,7 +75,7 @@ class UnitTest {
@Test
fun test_getReader() {
val reader = getReader(1442740)
val reader = getReader(1567569)
print(reader)
}