diff --git a/app/src/main/java/com/hippo/ehviewer/EhApplication.kt b/app/src/main/java/com/hippo/ehviewer/EhApplication.kt index 4269ed656..ccfe187f7 100644 --- a/app/src/main/java/com/hippo/ehviewer/EhApplication.kt +++ b/app/src/main/java/com/hippo/ehviewer/EhApplication.kt @@ -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()) diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt b/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt index 8693db906..cb7163a4e 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt @@ -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 + } + } +} \ No newline at end of file