* Drop Old DiskLruCache * Avoid being interrupted when completing editor * Protect all journal operations * Revert "Protect all journal operations" This reverts commit 5b7c7e3b33a515ee3e92826a6b76c2dfb3040c5d. * Revert "Avoid being interrupted when completing editor" This reverts commit cf4fbed1281eff766c6e65b6f685dfc355208a0b. * Shutdown Thread gracefully Test: m, Check no thread leaks * Fix download extensions
387 lines
12 KiB
Java
387 lines
12 KiB
Java
/*
|
|
* Copyright 2016 Hippo Seven
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.hippo.ehviewer.spider;
|
|
|
|
import android.content.Context;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
|
|
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.io.UniFileInputStreamPipe;
|
|
import com.hippo.io.UniFileOutputStreamPipe;
|
|
import com.hippo.streampipe.InputStreamPipe;
|
|
import com.hippo.streampipe.OutputStreamPipe;
|
|
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.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;
|
|
|
|
if (mode == SpiderQueen.MODE_DOWNLOAD) {
|
|
ensureDownloadDir();
|
|
}
|
|
}
|
|
|
|
private boolean ensureDownloadDir() {
|
|
if (mDownloadDir == null) {
|
|
var dirname = FileUtils.sanitizeFilename(mGid + "-" + EhUtils.getSuitableTitle(mGalleryInfo));
|
|
EhDB.putDownloadDirname(mGid, dirname);
|
|
mDownloadDir = getGalleryDownloadDir(mGid);
|
|
}
|
|
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);
|
|
}
|
|
return mDownloadDir != null && mDownloadDir.isDirectory() ? mDownloadDir : null;
|
|
}
|
|
|
|
private boolean containInCache(int index) {
|
|
if (sCache == null) {
|
|
return false;
|
|
}
|
|
|
|
String key = EhCacheKeyFactory.getImageKey(mGid, index);
|
|
var snapshot = sCache.get(key);
|
|
var exist = snapshot != null;
|
|
if (exist) snapshot.close();
|
|
return exist;
|
|
}
|
|
|
|
private boolean containInDownloadDir(int index) {
|
|
UniFile dir = getDownloadDir();
|
|
if (dir == null) {
|
|
return false;
|
|
}
|
|
|
|
// Find image file in download dir
|
|
return findImageFile(dir, index) != null;
|
|
}
|
|
|
|
/**
|
|
* @param extension with dot
|
|
*/
|
|
private String fixExtension(String extension) {
|
|
if (Utilities.contain(PageLoader2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
|
|
return extension;
|
|
} else {
|
|
return PageLoader2.SUPPORT_IMAGE_EXTENSIONS[0];
|
|
}
|
|
}
|
|
|
|
private boolean copyFromCacheToDownloadDir(int index) {
|
|
if (sCache == null) {
|
|
return false;
|
|
}
|
|
UniFile dir = getDownloadDir();
|
|
if (dir == null) {
|
|
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;
|
|
}
|
|
os = file.openOutputStream();
|
|
IOUtils.copy(is, os);
|
|
return true;
|
|
} catch (IOException e) {
|
|
return false;
|
|
} finally {
|
|
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;
|
|
}
|
|
}
|
|
|
|
private boolean removeFromCache(int index) {
|
|
if (sCache == null) {
|
|
return false;
|
|
}
|
|
|
|
String 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);
|
|
if (file != null) {
|
|
result |= file.delete();
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public boolean remove(int index) {
|
|
boolean result = removeFromCache(index);
|
|
result |= removeFromDownloadDir(index);
|
|
return result;
|
|
}
|
|
|
|
@Nullable
|
|
private OutputStreamPipe openCacheOutputStreamPipe(int index, @Nullable String extension) {
|
|
if (sCache == null) {
|
|
return null;
|
|
}
|
|
|
|
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();
|
|
}
|
|
return new OutputStreamPipe() {
|
|
@Override
|
|
public void obtain() {
|
|
}
|
|
|
|
@Override
|
|
public void release() {
|
|
}
|
|
|
|
@NonNull
|
|
@Override
|
|
public OutputStream open() throws IOException {
|
|
return new FileOutputStream(editor.getData().toFile());
|
|
}
|
|
|
|
@Override
|
|
public void close() {
|
|
editor.commit();
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param extension without dot
|
|
*/
|
|
@Nullable
|
|
private OutputStreamPipe openDownloadOutputStreamPipe(int index, @Nullable String extension) {
|
|
UniFile dir = getDownloadDir();
|
|
if (dir == null) {
|
|
return null;
|
|
}
|
|
|
|
extension = fixExtension('.' + extension);
|
|
UniFile file = dir.createFile(generateImageFilename(index, extension));
|
|
if (file != null) {
|
|
return new UniFileOutputStreamPipe(file);
|
|
} else {
|
|
return 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);
|
|
}
|
|
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() {
|
|
@Override
|
|
public void obtain() {
|
|
|
|
}
|
|
|
|
@Override
|
|
public void release() {
|
|
|
|
}
|
|
|
|
@NonNull
|
|
@Override
|
|
public InputStream open() throws IOException {
|
|
return new FileInputStream(snapshot.getData().toFile());
|
|
}
|
|
|
|
@Override
|
|
public void close() {
|
|
snapshot.close();
|
|
}
|
|
};
|
|
}
|
|
|
|
@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);
|
|
if (file != null) {
|
|
return new UniFileInputStreamPipe(file);
|
|
} else if (!copyFromCacheToDownloadDir(index)) {
|
|
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);
|
|
}
|
|
return pipe;
|
|
} else if (mMode == SpiderQueen.MODE_DOWNLOAD) {
|
|
return openDownloadInputStreamPipe(index);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
}
|