Convert SpiderDen to kotlin

This commit is contained in:
tarsin committed 2023-01-30 11:11:57 +08:00
1 parent 014b8db20d
commit 0834ff26be
2 files changed
+311 -373

No files matched your search

@@ -33,7 +33,6 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.disk.DiskCache
import com.hippo.Native
import com.hippo.app.BaseDialogBuilder
import com.hippo.ehviewer.client.EhCookieStore
@@ -47,7 +46,6 @@ import com.hippo.ehviewer.client.data.GalleryDetail
import com.hippo.ehviewer.client.parser.EventPaneParser
import com.hippo.ehviewer.dao.buildMainDB
import com.hippo.ehviewer.download.DownloadManager
import com.hippo.ehviewer.spider.SpiderDen
import com.hippo.ehviewer.ui.CommonOperations
import com.hippo.ehviewer.ui.EhActivity
import com.hippo.util.BitmapUtils
@@ -107,7 +105,6 @@ class EhApplication : Application(), DefaultLifecycleObserver, ImageLoaderFactor
Settings.initialize()
ReadableTime.initialize(this)
AppConfig.initialize(this)
SpiderDen.initialize(this)
BitmapUtils.initialize(this)
EhTagDatabase.update()
AppCompatDelegate.setDefaultNightMode(Settings.getTheme())
@@ -13,471 +13,412 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:OptIn(ExperimentalCoilApi::class)
package com.hippo.ehviewer.spider;
package com.hippo.ehviewer.spider
import android.content.Context;
import coil.annotation.ExperimentalCoilApi
import coil.disk.DiskCache
import com.hippo.Native.mapFd
import com.hippo.Native.unmapDirectByteBuffer
import com.hippo.ehviewer.EhApplication.Companion.application
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhCacheKeyFactory
import com.hippo.ehviewer.client.EhUtils.getSuitableTitle
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.gallery.PageLoader2
import com.hippo.image.Image.ByteBufferSource
import com.hippo.io.UniFileInputStreamPipe
import com.hippo.io.UniFileOutputStreamPipe
import com.hippo.streampipe.InputStreamPipe
import com.hippo.streampipe.OutputStreamPipe
import com.hippo.unifile.RawFile
import com.hippo.unifile.UniFile
import com.hippo.yorozuya.FileUtils
import com.hippo.yorozuya.IOUtils
import com.hippo.yorozuya.MathUtils
import com.hippo.yorozuya.Utilities
import okio.buffer
import okio.sink
import okio.source
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.io.RandomAccessFile
import java.nio.ByteBuffer
import java.nio.channels.FileChannel
import java.util.Locale
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
class SpiderDen(private val mGalleryInfo: GalleryInfo) {
private val mGid: Long = mGalleryInfo.gid
private var mDownloadDir: UniFile? = getGalleryDownloadDir(mGid)
import com.hippo.Native;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhCacheKeyFactory;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.gallery.PageLoader2;
import com.hippo.image.Image;
import com.hippo.io.UniFileInputStreamPipe;
import com.hippo.io.UniFileOutputStreamPipe;
import com.hippo.streampipe.InputStreamPipe;
import com.hippo.streampipe.OutputStreamPipe;
import com.hippo.unifile.RawFile;
import com.hippo.unifile.UniFile;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.Utilities;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Locale;
import coil.disk.DiskCache;
import okio.BufferedSource;
import okio.Okio;
public final class SpiderDen {
@Nullable
private static DiskCache sCache; // We use data to store image file, and metadata for image type
private final GalleryInfo mGalleryInfo;
private final long mGid;
@Nullable
private UniFile mDownloadDir;
private volatile int mMode = SpiderQueen.MODE_READ;
public SpiderDen(GalleryInfo galleryInfo) {
mGalleryInfo = galleryInfo;
mGid = galleryInfo.getGid();
mDownloadDir = getGalleryDownloadDir(galleryInfo.getGid());
}
public static void initialize(Context context) {
sCache = new DiskCache.Builder().directory(new File(context.getCacheDir(), "gallery_image"))
.maxSizeBytes((long) MathUtils.clamp(Settings.getReadCacheSize(), 40, 1280) * 1024 * 1024).build();
}
public static UniFile getGalleryDownloadDir(long gid) {
UniFile dir = Settings.getDownloadLocation();
// Read from DB
String dirname = EhDB.getDownloadDirname(gid);
if (dir != null && dirname != null) {
// Some dirname may be invalid in some version
dirname = FileUtils.sanitizeFilename(dirname);
EhDB.putDownloadDirname(gid, dirname);
return dir.subFile(dirname);
} else {
return null;
}
}
/**
* @param extension with dot
*/
public static String generateImageFilename(int index, String extension) {
return String.format(Locale.US, "%08d%s", index + 1, extension);
}
@Nullable
private static UniFile findImageFile(UniFile dir, int index) {
for (String extension : PageLoader2.SUPPORT_IMAGE_EXTENSIONS) {
String filename = generateImageFilename(index, extension);
UniFile file = dir.findFile(filename);
if (file != null) {
return file;
}
}
return null;
}
public void setMode(@SpiderQueen.Mode int mode) {
mMode = mode;
@Volatile
private var mMode = SpiderQueen.MODE_READ
fun setMode(@SpiderQueen.Mode mode: Int) {
mMode = mode
if (mode == SpiderQueen.MODE_DOWNLOAD) {
ensureDownloadDir();
ensureDownloadDir()
}
}
private boolean ensureDownloadDir() {
private fun ensureDownloadDir(): Boolean {
if (mDownloadDir == null) {
var dirname = FileUtils.sanitizeFilename(mGid + "-" + EhUtils.getSuitableTitle(mGalleryInfo));
EhDB.putDownloadDirname(mGid, dirname);
mDownloadDir = getGalleryDownloadDir(mGid);
val dirname =
FileUtils.sanitizeFilename(mGid.toString() + "-" + getSuitableTitle(mGalleryInfo))
EhDB.putDownloadDirname(mGid, dirname)
mDownloadDir = getGalleryDownloadDir(mGid)
}
return mDownloadDir != null && mDownloadDir.ensureDir();
return mDownloadDir != null && mDownloadDir!!.ensureDir()
}
public boolean isReady() {
return switch (mMode) {
case SpiderQueen.MODE_READ -> sCache != null;
case SpiderQueen.MODE_DOWNLOAD -> mDownloadDir != null && mDownloadDir.isDirectory();
default -> false;
};
}
@Nullable
public UniFile getDownloadDir() {
if (mDownloadDir == null) {
mDownloadDir = getGalleryDownloadDir(mGid);
val isReady: Boolean
get() = when (mMode) {
SpiderQueen.MODE_READ -> true
SpiderQueen.MODE_DOWNLOAD -> mDownloadDir != null && mDownloadDir!!.isDirectory
else -> false
}
return mDownloadDir != null && mDownloadDir.isDirectory() ? mDownloadDir : null;
}
private boolean containInCache(int index) {
if (sCache == null) {
return false;
val downloadDir: UniFile?
get() {
if (mDownloadDir == null) {
mDownloadDir = getGalleryDownloadDir(mGid)
}
return if (mDownloadDir != null && mDownloadDir!!.isDirectory) mDownloadDir else null
}
String key = EhCacheKeyFactory.getImageKey(mGid, index);
var snapshot = sCache.get(key);
var exist = snapshot != null;
if (exist) snapshot.close();
return exist;
private fun containInCache(index: Int): Boolean {
val key = EhCacheKeyFactory.getImageKey(mGid, index)
return sCache[key]?.apply { close() } != null
}
private boolean containInDownloadDir(int index) {
UniFile dir = getDownloadDir();
if (dir == null) {
return false;
}
// Find image file in download dir
return findImageFile(dir, index) != null;
private fun containInDownloadDir(index: Int): Boolean {
val dir = downloadDir ?: return false
return findImageFile(dir, index) != null
}
/**
* @param extension with dot
*/
private String fixExtension(String extension) {
if (Utilities.contain(PageLoader2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
return extension;
private fun fixExtension(extension: String): String {
return if (Utilities.contain(
PageLoader2.SUPPORT_IMAGE_EXTENSIONS,
extension
)
) {
extension
} else {
return PageLoader2.SUPPORT_IMAGE_EXTENSIONS[0];
PageLoader2.SUPPORT_IMAGE_EXTENSIONS[0]
}
}
private boolean copyFromCacheToDownloadDir(int index) {
if (sCache == null) {
return false;
}
UniFile dir = getDownloadDir();
if (dir == null) {
return false;
}
private fun copyFromCacheToDownloadDir(index: Int): Boolean {
val dir = downloadDir ?: return false
// Find image file in cache
String key = EhCacheKeyFactory.getImageKey(mGid, index);
var snapshot = sCache.get(key);
if (snapshot == null) {
return false;
}
OutputStream os = null;
try (snapshot; FileInputStream is = new FileInputStream(snapshot.getData().toFile()); BufferedSource buf = Okio.buffer(Okio.source(snapshot.getMetadata().toFile()))) {
// Get extension
String extension = fixExtension("." + buf.readUtf8Line());
// Copy from cache to download dir
UniFile file = dir.createFile(generateImageFilename(index, extension));
if (file == null) {
return false;
val key = EhCacheKeyFactory.getImageKey(mGid, index)
val snapshot = sCache[key] ?: return false
var os: OutputStream? = null
try {
snapshot.use {
FileInputStream(snapshot.data.toFile()).use { `is` ->
snapshot.metadata.toFile().source().buffer().use { buf ->
// Get extension
val extension = fixExtension("." + buf.readUtf8Line())
// Copy from cache to download dir
val file = dir.createFile(generateImageFilename(index, extension))
?: return false
os = file.openOutputStream()
IOUtils.copy(`is`, os)
return true
}
}
}
os = file.openOutputStream();
IOUtils.copy(is, os);
return true;
} catch (IOException e) {
return false;
} catch (e: IOException) {
return false
} finally {
IOUtils.closeQuietly(os);
IOUtils.closeQuietly(os)
}
}
public boolean contain(int index) {
if (mMode == SpiderQueen.MODE_READ) {
return containInCache(index) || containInDownloadDir(index);
} else if (mMode == SpiderQueen.MODE_DOWNLOAD) {
return containInDownloadDir(index) || copyFromCacheToDownloadDir(index);
} else {
return false;
fun contain(index: Int): Boolean {
return when (mMode) {
SpiderQueen.MODE_READ -> {
containInCache(index) || containInDownloadDir(index)
}
SpiderQueen.MODE_DOWNLOAD -> {
containInDownloadDir(index) || copyFromCacheToDownloadDir(index)
}
else -> {
false
}
}
}
private boolean removeFromCache(int index) {
if (sCache == null) {
return false;
}
String key = EhCacheKeyFactory.getImageKey(mGid, index);
return sCache.remove(key);
private fun removeFromCache(index: Int): Boolean {
val key = EhCacheKeyFactory.getImageKey(mGid, index)
return sCache.remove(key)
}
private boolean removeFromDownloadDir(int index) {
UniFile dir = getDownloadDir();
if (dir == null) {
return false;
}
boolean result = false;
for (int i = 0, n = PageLoader2.SUPPORT_IMAGE_EXTENSIONS.length; i < n; i++) {
String filename = generateImageFilename(index, PageLoader2.SUPPORT_IMAGE_EXTENSIONS[i]);
UniFile file = dir.subFile(filename);
private fun removeFromDownloadDir(index: Int): Boolean {
val dir = downloadDir ?: return false
var result = false
var i = 0
val n = PageLoader2.SUPPORT_IMAGE_EXTENSIONS.size
while (i < n) {
val filename = generateImageFilename(index, PageLoader2.SUPPORT_IMAGE_EXTENSIONS[i])
val file = dir.subFile(filename)
if (file != null) {
result |= file.delete();
result = result or file.delete()
}
i++
}
return result;
return result
}
public boolean remove(int index) {
boolean result = removeFromCache(index);
result |= removeFromDownloadDir(index);
return result;
fun remove(index: Int): Boolean {
var result = removeFromCache(index)
result = result or removeFromDownloadDir(index)
return result
}
@Nullable
private OutputStreamPipe openCacheOutputStreamPipe(int index, @Nullable String extension) {
if (sCache == null) {
return null;
private fun openCacheOutputStreamPipe(index: Int, extension: String): OutputStreamPipe? {
val key = EhCacheKeyFactory.getImageKey(mGid, index)
val editor = sCache.edit(key) ?: return null
try {
editor.metadata.toFile().sink().buffer().use { sink -> sink.writeUtf8(extension) }
} catch (e: IOException) {
e.printStackTrace()
}
return object : OutputStreamPipe {
private var os: OutputStream? = null
override fun release() {
editor.commit()
}
String key = EhCacheKeyFactory.getImageKey(mGid, index);
var editor = sCache.edit(key);
if (editor == null) return null;
try (var sink = Okio.buffer(Okio.sink(editor.getMetadata().toFile()))) {
sink.writeUtf8(extension);
} catch (IOException e) {
e.printStackTrace();
@Throws(IOException::class)
override fun open(): OutputStream {
if (os != null) IOUtils.closeQuietly(os)
os = FileOutputStream(editor.data.toFile())
return os!!
}
override fun close() {
if (os != null) IOUtils.closeQuietly(os)
}
}
return new OutputStreamPipe() {
private OutputStream os = null;
@Override
public void release() {
editor.commit();
}
@NonNull
@Override
public OutputStream open() throws IOException {
if (os != null) IOUtils.closeQuietly(os);
os = new FileOutputStream(editor.getData().toFile());
return os;
}
@Override
public void close() {
if (os != null) IOUtils.closeQuietly(os);
}
};
}
/**
* @param extension without dot
*/
@Nullable
private OutputStreamPipe openDownloadOutputStreamPipe(int index, @Nullable String extension) {
UniFile dir = getDownloadDir();
if (dir == null) {
return null;
}
private fun openDownloadOutputStreamPipe(index: Int, extension: String): OutputStreamPipe? {
var extension = extension
val dir = downloadDir ?: return null
extension = fixExtension(".$extension")
val file = dir.createFile(generateImageFilename(index, extension))
return file?.let { UniFileOutputStreamPipe(it) }
}
extension = fixExtension('.' + extension);
UniFile file = dir.createFile(generateImageFilename(index, extension));
if (file != null) {
return new UniFileOutputStreamPipe(file);
} else {
return null;
fun openOutputStreamPipe(index: Int, extension: String): OutputStreamPipe? {
return when (mMode) {
SpiderQueen.MODE_READ -> {
// Return the download pipe is the gallery has been downloaded
var pipe = openDownloadOutputStreamPipe(index, extension)
if (pipe == null) {
pipe = openCacheOutputStreamPipe(index, extension)
}
pipe
}
SpiderQueen.MODE_DOWNLOAD -> {
openDownloadOutputStreamPipe(index, extension)
}
else -> {
null
}
}
}
@Nullable
public OutputStreamPipe openOutputStreamPipe(int index, @Nullable String extension) {
if (mMode == SpiderQueen.MODE_READ) {
// Return the download pipe is the gallery has been downloaded
OutputStreamPipe pipe = openDownloadOutputStreamPipe(index, extension);
if (pipe == null) {
pipe = openCacheOutputStreamPipe(index, extension);
private fun openCacheInputStreamPipe(index: Int): InputStreamPipe? {
val key = EhCacheKeyFactory.getImageKey(mGid, index)
val snapshot = sCache[key] ?: return null
return object : InputStreamPipe {
private var `is`: InputStream? = null
override fun release() {
snapshot.close()
}
@Throws(IOException::class)
override fun open(): InputStream {
if (`is` != null) IOUtils.closeQuietly(`is`)
`is` = FileInputStream(snapshot.data.toFile())
return `is`!!
}
override fun close() {
if (`is` != null) IOUtils.closeQuietly(`is`)
}
return pipe;
} else if (mMode == SpiderQueen.MODE_DOWNLOAD) {
return openDownloadOutputStreamPipe(index, extension);
} else {
return null;
}
}
@Nullable
private InputStreamPipe openCacheInputStreamPipe(int index) {
if (sCache == null) {
return null;
}
String key = EhCacheKeyFactory.getImageKey(mGid, index);
var snapshot = sCache.get(key);
if (snapshot == null) return null;
return new InputStreamPipe() {
private InputStream is = null;
@Override
public void release() {
snapshot.close();
}
@NonNull
@Override
public InputStream open() throws IOException {
if (is != null) IOUtils.closeQuietly(is);
is = new FileInputStream(snapshot.getData().toFile());
return is;
}
@Override
public void close() {
if (is != null) IOUtils.closeQuietly(is);
}
};
}
@Nullable
public InputStreamPipe openDownloadInputStreamPipe(int index) {
UniFile dir = getDownloadDir();
if (dir == null) {
return null;
}
for (int i = 0; i < 2; i++) {
UniFile file = findImageFile(dir, index);
private fun openDownloadInputStreamPipe(index: Int): InputStreamPipe? {
val dir = downloadDir ?: return null
for (i in 0..1) {
val file = findImageFile(dir, index)
if (file != null) {
return new UniFileInputStreamPipe(file);
return UniFileInputStreamPipe(file)
} else if (!copyFromCacheToDownloadDir(index)) {
return null;
return null
}
}
return null;
return null
}
@Nullable
public InputStreamPipe openInputStreamPipe(int index) {
if (mMode == SpiderQueen.MODE_READ) {
InputStreamPipe pipe = openCacheInputStreamPipe(index);
if (pipe == null) {
pipe = openDownloadInputStreamPipe(index);
fun openInputStreamPipe(index: Int): InputStreamPipe? {
return when (mMode) {
SpiderQueen.MODE_READ -> {
var pipe = openCacheInputStreamPipe(index)
if (pipe == null) {
pipe = openDownloadInputStreamPipe(index)
}
pipe
}
SpiderQueen.MODE_DOWNLOAD -> {
openDownloadInputStreamPipe(index)
}
else -> {
null
}
return pipe;
} else if (mMode == SpiderQueen.MODE_DOWNLOAD) {
return openDownloadInputStreamPipe(index);
} else {
return null;
}
}
@Nullable
public Image.ByteBufferSource getImageSource(int index) {
fun getImageSource(index: Int): ByteBufferSource? {
if (mMode == SpiderQueen.MODE_READ) {
String key = EhCacheKeyFactory.getImageKey(mGid, index);
if (sCache != null) {
var snapshot = sCache.get(key);
if (snapshot != null) {
try {
var file = new RandomAccessFile(snapshot.getData().toFile(), "rw");
var chan = file.getChannel();
var map = chan.map(FileChannel.MapMode.PRIVATE, 0, file.length());
return new Image.ByteBufferSource() {
@NonNull
@Override
public ByteBuffer getByteBuffer() {
return map;
}
val key = EhCacheKeyFactory.getImageKey(mGid, index)
val snapshot: DiskCache.Snapshot? = sCache[key]
if (snapshot != null) {
try {
val file = RandomAccessFile(snapshot.data.toFile(), "rw")
val chan = file.channel
val map = chan.map(FileChannel.MapMode.PRIVATE, 0, file.length())
return object : ByteBufferSource {
override fun getByteBuffer(): ByteBuffer {
return map
}
@Override
public void close() throws Exception {
chan.close();
file.close();
snapshot.close();
}
};
} catch (Throwable e) {
e.printStackTrace();
@Throws(Exception::class)
override fun close() {
chan.close()
file.close()
snapshot.close()
}
}
} catch (e: Throwable) {
e.printStackTrace()
}
}
}
UniFile dir = getDownloadDir();
if (dir == null) {
return null;
}
UniFile file = findImageFile(dir, index);
val dir = downloadDir ?: return null
var file = findImageFile(dir, index)
if (file != null && mMode == SpiderQueen.MODE_DOWNLOAD) {
if (copyFromCacheToDownloadDir(index)) {
file = findImageFile(dir, index);
file = findImageFile(dir, index)
}
}
if (file instanceof RawFile rawFile) {
if (file is RawFile) {
try {
var file2 = new RandomAccessFile(rawFile.mFile, "rw");
var chan = file2.getChannel();
var map = chan.map(FileChannel.MapMode.PRIVATE, 0, file2.length());
return new Image.ByteBufferSource() {
@NonNull
@Override
public ByteBuffer getByteBuffer() {
return map;
val file2 = RandomAccessFile(file.mFile, "rw")
val chan = file2.channel
val map = chan.map(FileChannel.MapMode.PRIVATE, 0, file2.length())
return object : ByteBufferSource {
override fun getByteBuffer(): ByteBuffer {
return map
}
@Override
public void close() throws Exception {
chan.close();
file2.close();
@Throws(Exception::class)
override fun close() {
chan.close()
file2.close()
}
};
} catch (Throwable e) {
e.printStackTrace();
}
} catch (e: Throwable) {
e.printStackTrace()
}
}
if (file != null) {
try {
var pfd = EhApplication.getApplication().getContentResolver().openFileDescriptor(file.getUri(), "rw");
var map = Native.mapFd(pfd.getFd(), pfd.getStatSize());
val pfd = application.contentResolver.openFileDescriptor(file.uri, "rw")
val map = mapFd(pfd!!.fd, pfd.statSize)
if (map != null) {
return new Image.ByteBufferSource() {
@NonNull
@Override
public ByteBuffer getByteBuffer() {
return map;
return object : ByteBufferSource {
override fun getByteBuffer(): ByteBuffer {
return map
}
@Override
public void close() throws Exception {
Native.unmapDirectByteBuffer(map);
pfd.close();
@Throws(Exception::class)
override fun close() {
unmapDirectByteBuffer(map)
pfd.close()
}
};
}
} else {
pfd.close();
pfd.close()
}
} catch (Throwable e) {
e.printStackTrace();
} catch (e: Throwable) {
e.printStackTrace()
}
}
return null;
return null
}
}
companion object {
// We use data to store image file, and metadata for image type
private val sCache by lazy {
DiskCache.Builder().directory(File(application.cacheDir, "gallery_image"))
.maxSizeBytes(
MathUtils.clamp(Settings.getReadCacheSize(), 40, 1280).toLong() * 1024 * 1024
).build()
}
fun getGalleryDownloadDir(gid: Long): UniFile? {
val dir = Settings.getDownloadLocation()
// Read from DB
var dirname = EhDB.getDownloadDirname(gid)
return if (dir != null && dirname != null) {
// Some dirname may be invalid in some version
dirname = FileUtils.sanitizeFilename(dirname)
EhDB.putDownloadDirname(gid, dirname)
dir.subFile(dirname)
} else {
null
}
}
/**
* @param extension with dot
*/
fun generateImageFilename(index: Int, extension: String?): String {
return String.format(Locale.US, "%08d%s", index + 1, extension)
}
private fun findImageFile(dir: UniFile, index: Int): UniFile? {
for (extension in PageLoader2.SUPPORT_IMAGE_EXTENSIONS) {
val filename = generateImageFilename(index, extension)
val file = dir.findFile(filename)
if (file != null) {
return file
}
}
return null
}
}
}