diff --git a/app/src/main/java/com/hippo/beerbelly/DiskLruCache.java b/app/src/main/java/com/hippo/beerbelly/DiskLruCache.java deleted file mode 100644 index c9a332a89..000000000 --- a/app/src/main/java/com/hippo/beerbelly/DiskLruCache.java +++ /dev/null @@ -1,985 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * 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.beerbelly; - -import java.io.BufferedWriter; -import java.io.Closeable; -import java.io.EOFException; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * A cache that uses a bounded amount of space on a filesystem. Each cache - * entry has a string key and a fixed number of values. Each key must match - * the regex [a-z0-9_-]{1,120}. Values are byte sequences, - * accessible as streams or files. Each value must be between {@code 0} and - * {@code Integer.MAX_VALUE} bytes in length. - * - *
The cache stores its data in a directory on the filesystem. This - * directory must be exclusive to the cache; the cache may delete or overwrite - * files from its directory. It is an error for multiple processes to use the - * same cache directory at the same time. - * - *
This cache limits the number of bytes that it will store on the - * filesystem. When the number of stored bytes exceeds the limit, the cache will - * remove entries in the background until the limit is satisfied. The limit is - * not strict: the cache may temporarily exceed it while waiting for files to be - * deleted. The limit does not include filesystem overhead or the cache - * journal so space-sensitive applications should set a conservative limit. - * - *
Clients call {@link #edit} to create or update the values of an entry. An - * entry may have only one editor at one time; if a value is not available to be - * edited then {@link #edit} will return null. - *
Clients call {@link #get} to read a snapshot of an entry. The read will - * observe the value at the time that {@link #get} was called. Updates and - * removals after the call do not impact ongoing reads. - * - *
This class is tolerant of some I/O errors. If files are missing from the
- * filesystem, the corresponding entries will be dropped from the cache. If
- * an error occurs while writing a cache value, the edit will fail silently.
- * Callers should handle other problems by catching {@code IOException} and
- * responding appropriately.
- */
-public final class DiskLruCache implements Closeable {
- static final String JOURNAL_FILE = "journal";
- static final String JOURNAL_FILE_TEMP = "journal.tmp";
- static final String JOURNAL_FILE_BACKUP = "journal.bkp";
- static final String MAGIC = "libcore.io.DiskLruCache";
- static final String VERSION_1 = "1";
- static final long ANY_SEQUENCE_NUMBER = -1;
- static final String STRING_KEY_PATTERN = "[a-z0-9_-]{1,120}";
- static final Pattern LEGAL_KEY_PATTERN = Pattern.compile(STRING_KEY_PATTERN);
- private static final String CLEAN = "CLEAN";
- private static final String DIRTY = "DIRTY";
- private static final String REMOVE = "REMOVE";
- private static final String READ = "READ";
-
- /*
- * This cache uses a journal file named "journal". A typical journal file
- * looks like this:
- * libcore.io.DiskLruCache
- * 1
- * 100
- * 2
- *
- * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
- * DIRTY 335c4c6028171cfddfbaae1a9c313c52
- * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
- * REMOVE 335c4c6028171cfddfbaae1a9c313c52
- * DIRTY 1ab96a171faeeee38496d8b330771a7a
- * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
- * READ 335c4c6028171cfddfbaae1a9c313c52
- * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
- *
- * The first five lines of the journal form its header. They are the
- * constant string "libcore.io.DiskLruCache", the disk cache's version,
- * the application's version, the value count, and a blank line.
- *
- * Each of the subsequent lines in the file is a record of the state of a
- * cache entry. Each line contains space-separated values: a state, a key,
- * and optional state-specific values.
- * o DIRTY lines track that an entry is actively being created or updated.
- * Every successful DIRTY action should be followed by a CLEAN or REMOVE
- * action. DIRTY lines without a matching CLEAN or REMOVE indicate that
- * temporary files may need to be deleted.
- * o CLEAN lines track a cache entry that has been successfully published
- * and may be read. A publish line is followed by the lengths of each of
- * its values.
- * o READ lines track accesses for LRU.
- * o REMOVE lines track entries that have been deleted.
- *
- * The journal file is appended to as cache operations occur. The journal may
- * occasionally be compacted by dropping redundant lines. A temporary file named
- * "journal.tmp" will be used during compaction; that file should be deleted if
- * it exists when the cache is opened.
- */
- private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() {
- @Override
- public void write(int b) {
- // Eat all writes silently. Nom nom.
- }
- };
- /**
- * This cache uses a single background thread to evict entries.
- */
- final ThreadPoolExecutor executorService =
- new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
- private final File directory;
- private final File journalFile;
- private final File journalFileTmp;
- private final File journalFileBackup;
- private final int appVersion;
- private final int valueCount;
- private final LinkedHashMap This class is used for buffered reading of lines. For purposes of this class, a line ends
- * with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated
- * line at end of input is invalid and will be ignored, the caller may use {@code
- * hasUnterminatedLine()} to detect it after catching the {@code EOFException}.
- *
- * This class is intended for reading input that strictly consists of lines, such as line-based
- * cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction
- * with {@link java.io.InputStreamReader} provides similar functionality, this class uses different
- * end-of-input reporting and a more restrictive definition of a line.
- *
- * This class supports only charsets that encode '\r' and '\n' as a single byte with value 13
- * and 10, respectively, and the representation of no other character contains these values.
- * We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.
- * The default charset is US_ASCII.
- */
-class StrictLineReader implements Closeable {
- private static final byte CR = (byte) '\r';
- private static final byte LF = (byte) '\n';
-
- private final InputStream in;
- private final Charset charset;
-
- /*
- * Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end
- * and the data in the range [pos, end) is buffered for reading. At end of input, if there is
- * an unterminated line, we set end == -1, otherwise end == pos. If the underlying
- * {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.
- */
- private byte[] buf;
- private int pos;
- private int end;
-
- /**
- * Constructs a new {@code LineReader} with the specified charset and the default capacity.
- *
- * @param in the {@code InputStream} to read data from.
- * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
- * supported.
- * @throws NullPointerException if {@code in} or {@code charset} is null.
- * @throws IllegalArgumentException if the specified charset is not supported.
- */
- public StrictLineReader(InputStream in, Charset charset) {
- this(in, 8192, charset);
- }
-
- /**
- * Constructs a new {@code LineReader} with the specified capacity and charset.
- *
- * @param in the {@code InputStream} to read data from.
- * @param capacity the capacity of the buffer.
- * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
- * supported.
- * @throws NullPointerException if {@code in} or {@code charset} is null.
- * @throws IllegalArgumentException if {@code capacity} is negative or zero
- * or the specified charset is not supported.
- */
- public StrictLineReader(InputStream in, int capacity, Charset charset) {
- if (in == null || charset == null) {
- throw new NullPointerException();
- }
- if (capacity < 0) {
- throw new IllegalArgumentException("capacity <= 0");
- }
- if (!(charset.equals(Util.US_ASCII))) {
- throw new IllegalArgumentException("Unsupported encoding");
- }
-
- this.in = in;
- this.charset = charset;
- buf = new byte[capacity];
- }
-
- /**
- * Closes the reader by closing the underlying {@code InputStream} and
- * marking this reader as closed.
- *
- * @throws IOException for errors when closing the underlying {@code InputStream}.
- */
- public void close() throws IOException {
- synchronized (in) {
- if (buf != null) {
- buf = null;
- in.close();
- }
- }
- }
-
- /**
- * Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},
- * this end of line marker is not included in the result.
- *
- * @return the next line from the input.
- * @throws IOException for underlying {@code InputStream} errors.
- * @throws EOFException for the end of source stream.
- */
- public String readLine() throws IOException {
- synchronized (in) {
- if (buf == null) {
- throw new IOException("LineReader is closed");
- }
-
- // Read more data if we are at the end of the buffered data.
- // Though it's an error to read after an exception, we will let {@code fillBuf()}
- // throw again if that happens; thus we need to handle end == -1 as well as end == pos.
- if (pos >= end) {
- fillBuf();
- }
- // Try to find LF in the buffered data and return the line if successful.
- for (int i = pos; i != end; ++i) {
- if (buf[i] == LF) {
- int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
- String res = new String(buf, pos, lineEnd - pos, charset);
- pos = i + 1;
- return res;
- }
- }
-
- // Let's anticipate up to 80 characters on top of those already read.
- ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
- @NonNull
- @Override
- public String toString() {
- int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
- return new String(buf, 0, length, charset);
- }
- };
-
- while (true) {
- out.write(buf, pos, end - pos);
- // Mark unterminated line in case fillBuf throws EOFException or IOException.
- end = -1;
- fillBuf();
- // Try to find LF in the buffered data and return the line if successful.
- for (int i = pos; i != end; ++i) {
- if (buf[i] == LF) {
- if (i != pos) {
- out.write(buf, pos, i - pos);
- }
- pos = i + 1;
- return out.toString();
- }
- }
- }
- }
- }
-
- public boolean hasUnterminatedLine() {
- return end == -1;
- }
-
- /**
- * Reads new input data into the buffer. Call only with pos == end or end == -1,
- * depending on the desired outcome if the function throws.
- */
- private void fillBuf() throws IOException {
- int result = in.read(buf, 0, buf.length);
- if (result == -1) {
- throw new EOFException();
- }
- pos = 0;
- end = result;
- }
-}
diff --git a/app/src/main/java/com/hippo/beerbelly/Util.java b/app/src/main/java/com/hippo/beerbelly/Util.java
deleted file mode 100644
index 25c70ddfc..000000000
--- a/app/src/main/java/com/hippo/beerbelly/Util.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * 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.beerbelly;
-
-import java.io.Closeable;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.Reader;
-import java.io.StringWriter;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-
-/**
- * Junk drawer of utility methods.
- */
-final class Util {
- static final Charset US_ASCII = StandardCharsets.US_ASCII;
- static final Charset UTF_8 = StandardCharsets.UTF_8;
- private static final int EOF = -1;
- private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
-
- private Util() {
- }
-
- static String readFully(Reader reader) throws IOException {
- try (reader) {
- StringWriter writer = new StringWriter();
- char[] buffer = new char[1024];
- int count;
- while ((count = reader.read(buffer)) != -1) {
- writer.write(buffer, 0, count);
- }
- return writer.toString();
- }
- }
-
- /**
- * Deletes the contents of {@code dir}. Throws an IOException if any file
- * could not be deleted, or if {@code dir} is not a readable directory.
- */
- static void deleteContents(File dir) throws IOException {
- File[] files = dir.listFiles();
- if (files == null) {
- throw new IOException("not a readable directory: " + dir);
- }
- for (File file : files) {
- if (file.isDirectory()) {
- deleteContents(file);
- }
- if (!file.delete()) {
- throw new IOException("failed to delete file: " + file);
- }
- }
- }
-
- static void closeQuietly(/*Auto*/Closeable closeable) {
- if (closeable != null) {
- try {
- closeable.close();
- } catch (RuntimeException rethrown) {
- throw rethrown;
- } catch (Exception ignored) {
- }
- }
- }
-
- /**
- * Copy bytes from an null for missing
- */
- @Nullable
- public InputStreamPipe getInputStreamPipe(@NonNull String key) {
- if (contain(key)) {
- String diskKey = hashKeyForDisk(key);
- return new CacheInputStreamPipe(diskKey);
- } else {
- return null;
- }
- }
-
- @NonNull
- public OutputStreamPipe getOutputStreamPipe(@NonNull String key) {
- String diskKey = hashKeyForDisk(key);
- return new CacheOutputStreamPipe(diskKey);
- }
-
- private class CacheInputStreamPipe implements InputStreamPipe {
-
- private final String mKey;
- private CounterLock mLock;
- private DiskLruCache.Snapshot mCurrentSnapshot;
-
- private CacheInputStreamPipe(String key) {
- mKey = key;
- }
-
- @Override
- public void obtain() {
- if (mLock == null) {
- mLock = obtainLock(mKey);
- mLock.readLock().lock();
- }
- }
-
- @Override
- public void release() {
- if (mCurrentSnapshot != null) {
- throw new IllegalStateException("Please close it first");
- }
-
- if (mLock != null) {
- mLock.readLock().unlock();
- releaseLock(mKey, mLock);
- mLock = null;
- }
- }
-
- @Override
- public @NonNull
- InputStream open() throws IOException {
- if (mLock == null) {
- throw new IllegalStateException("Please obtain it first");
- }
- if (mCurrentSnapshot != null) {
- throw new IllegalStateException("Please close it before reopen");
- }
- if (null == mDiskLruCache) {
- throw new IOException("Can't find disk lru cache");
- }
-
- DiskLruCache.Snapshot snapshot;
- snapshot = mDiskLruCache.get(mKey);
- if (snapshot == null) {
- throw new IOException("Miss the key " + mKey);
- }
- mCurrentSnapshot = snapshot;
- return snapshot.getInputStream(0);
- }
-
- @Override
- public void close() {
- Util.closeQuietly(mCurrentSnapshot);
- mCurrentSnapshot = null;
- }
- }
-
- private class CacheOutputStreamPipe implements OutputStreamPipe {
-
- private final String mKey;
- private CounterLock mLock;
- private DiskLruCache.Editor mCurrentEditor;
-
- private CacheOutputStreamPipe(String key) {
- mKey = key;
- }
-
- @Override
- public void obtain() {
- if (mLock == null) {
- mLock = obtainLock(mKey);
- mLock.writeLock().lock();
- }
- }
-
- @Override
- public void release() {
- if (mCurrentEditor != null) {
- throw new IllegalStateException("Please close it first");
- }
-
- if (mLock != null) {
- mLock.writeLock().unlock();
- releaseLock(mKey, mLock);
- mLock = null;
- }
- }
-
- @Override
- public @NonNull
- OutputStream open() throws IOException {
- if (mLock == null) {
- throw new IllegalStateException("Please obtain it first");
- }
- if (mCurrentEditor != null) {
- throw new IllegalStateException("Please close it before reopen");
- }
- if (null == mDiskLruCache) {
- throw new IOException("Can't find disk lru cache");
- }
-
- DiskLruCache.Editor editor = mDiskLruCache.edit(mKey);
- if (editor == null) {
- throw new IOException("Miss the key " + mKey);
- }
-
- OutputStream os;
- try {
- os = editor.newOutputStream(0);
- mCurrentEditor = editor;
- return os;
- } catch (IOException e) {
- // Get trouble
- try {
- editor.abort();
- } catch (IOException ex) {
- // Ignore
- }
- throw e;
- }
- }
-
- @Override
- public void close() {
- if (mCurrentEditor != null) {
- try {
- mCurrentEditor.commit();
- } catch (IOException e) {
- // Empty
- }
- mCurrentEditor = null;
- }
- }
- }
-
- private class CounterLock extends ReentrantReadWriteLock {
-
- private int mCount;
-
- public boolean isFree() {
- return mCount == 0;
- }
-
- public void release() {
- if (--mCount < 0) {
- throw new IllegalStateException("Release time is more than occupy time");
- }
- }
-
- public void obtain() {
- ++mCount;
- }
- }
-
- private class LockPool {
-
- private static final int MAX_SIZE = 10;
-
- private final CounterLock[] mArray = new CounterLock[MAX_SIZE];
- private final int mMaxSize = MAX_SIZE;
- private int mSize;
-
- public void push(CounterLock lock) {
- if (lock != null && mSize < mMaxSize) {
- mArray[mSize++] = lock;
- }
- }
-
- public CounterLock get() {
- if (mSize > 0) {
- CounterLock lock = mArray[--mSize];
- mArray[mSize] = null;
- return lock;
- } else {
- return new CounterLock();
- }
- }
- }
-}
diff --git a/app/src/main/java/com/hippo/beerbelly/StrictLineReader.java b/app/src/main/java/com/hippo/beerbelly/StrictLineReader.java
deleted file mode 100644
index 9f7c885d0..000000000
--- a/app/src/main/java/com/hippo/beerbelly/StrictLineReader.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.beerbelly;
-
-import androidx.annotation.NonNull;
-
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.Charset;
-
-/**
- * Buffers input from an {@link InputStream} for reading lines.
- *
- * InputStream to an
- * OutputStream.
- *
- * @param input the InputStream
- * @param output the OutputStream
- * @return the number of bytes copied
- */
- public static long copy(InputStream input, OutputStream output)
- throws IOException {
- byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
- long count = 0;
- int n;
- while (EOF != (n = input.read(buffer))) {
- output.write(buffer, 0, n);
- count += n;
- }
- return count;
- }
-}
diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.java b/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.java
index bb7594312..e2001cb39 100644
--- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.java
+++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.java
@@ -17,12 +17,10 @@
package com.hippo.ehviewer.spider;
import android.content.Context;
-import android.graphics.BitmapFactory;
-import android.webkit.MimeTypeMap;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.hippo.beerbelly.SimpleDiskCache;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhCacheKeyFactory;
@@ -40,14 +38,21 @@ 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 SimpleDiskCache sCache;
+ 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
@@ -61,8 +66,8 @@ public final class SpiderDen {
}
public static void initialize(Context context) {
- sCache = new SimpleDiskCache(new File(context.getCacheDir(), "image"),
- MathUtils.clamp(Settings.getReadCacheSize(), 40, 1280) * 1024 * 1024);
+ 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) {
@@ -137,7 +142,10 @@ public final class SpiderDen {
}
String key = EhCacheKeyFactory.getImageKey(mGid, index);
- return sCache.contain(key);
+ var snapshot = sCache.get(key);
+ var exist = snapshot != null;
+ if (exist) snapshot.close();
+ return exist;
}
private boolean containInDownloadDir(int index) {
@@ -171,42 +179,27 @@ public final class SpiderDen {
}
// Find image file in cache
String key = EhCacheKeyFactory.getImageKey(mGid, index);
- InputStreamPipe pipe = sCache.getInputStreamPipe(key);
- if (pipe == null) {
+ var snapshot = sCache.get(key);
+ if (snapshot == null) {
return false;
}
OutputStream os = null;
- try {
+ try (snapshot; FileInputStream is = new FileInputStream(snapshot.getData().toFile()); BufferedSource buf = Okio.buffer(Okio.source(snapshot.getMetadata().toFile()))) {
// Get extension
- String extension;
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- pipe.obtain();
- BitmapFactory.decodeStream(pipe.open(), null, options);
- pipe.close();
- extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(options.outMimeType);
- if (extension != null) {
- extension = '.' + extension;
- } else {
- return false;
- }
- // Fix extension
- extension = fixExtension(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(pipe.open(), os);
+ IOUtils.copy(is, os);
return true;
} catch (IOException e) {
return false;
} finally {
IOUtils.closeQuietly(os);
- pipe.close();
- pipe.release();
}
}
@@ -253,13 +246,39 @@ public final class SpiderDen {
}
@Nullable
- private OutputStreamPipe openCacheOutputStreamPipe(int index) {
+ private OutputStreamPipe openCacheOutputStreamPipe(int index, @Nullable String extension) {
if (sCache == null) {
return null;
}
String key = EhCacheKeyFactory.getImageKey(mGid, index);
- return sCache.getOutputStreamPipe(key);
+ 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();
+ }
+ };
}
/**
@@ -287,7 +306,7 @@ public final class SpiderDen {
// Return the download pipe is the gallery has been downloaded
OutputStreamPipe pipe = openDownloadOutputStreamPipe(index, extension);
if (pipe == null) {
- pipe = openCacheOutputStreamPipe(index);
+ pipe = openCacheOutputStreamPipe(index, extension);
}
return pipe;
} else if (mMode == SpiderQueen.MODE_DOWNLOAD) {
@@ -304,7 +323,30 @@ public final class SpiderDen {
}
String key = EhCacheKeyFactory.getImageKey(mGid, index);
- return sCache.getInputStreamPipe(key);
+ 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
diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java
index 67999f0c2..9b228d954 100644
--- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java
+++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java
@@ -46,7 +46,6 @@ import com.hippo.ehviewer.client.parser.GalleryMultiPageViewerPTokenParser;
import com.hippo.ehviewer.client.parser.GalleryPageApiParser;
import com.hippo.ehviewer.client.parser.GalleryPageParser;
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
-import com.hippo.ehviewer.gallery.PageLoader2;
import com.hippo.image.Image;
import com.hippo.streampipe.InputStreamPipe;
import com.hippo.streampipe.OutputStreamPipe;
@@ -102,7 +101,7 @@ public final class SpiderQueen implements Runnable {
public static final String SPIDER_INFO_FILENAME = ".ehviewer";
private static final String TAG = SpiderQueen.class.getSimpleName();
private static final AtomicInteger sIdGenerator = new AtomicInteger();
- private static final boolean DEBUG_LOG = false;
+ private static final boolean DEBUG_LOG = true;
private static final boolean DEBUG_PTOKEN = true;
private static final String[] URL_509_SUFFIX_ARRAY = {
"/509.gif",
@@ -155,6 +154,8 @@ public final class SpiderQueen implements Runnable {
// For download, when it go to mPageStateArray.size(), done
private volatile int mDownloadPage = -1;
+ private boolean mStoped = false;
+
private SpiderQueen(@NonNull GalleryInfo galleryInfo) {
mHttpClient = EhApplication.getOkHttpClient();
mSpiderInfoCache = EhApplication.getSpiderInfoCache();
@@ -387,10 +388,15 @@ public final class SpiderQueen implements Runnable {
}
private void stop() {
- Thread queenThread = mQueenThread;
- if (queenThread != null) {
- queenThread.interrupt();
- mQueenThread = null;
+ mStoped = true;
+ synchronized (mQueenLock) {
+ mQueenLock.notifyAll();
+ }
+ synchronized (mDecodeRequestQueue) {
+ mDecodeRequestQueue.notifyAll();
+ }
+ synchronized (mWorkerLock) {
+ mWorkerLock.notifyAll();
}
}
@@ -875,8 +881,8 @@ public final class SpiderQueen implements Runnable {
// Read spider info
SpiderInfo spiderInfo = readSpiderInfoFromLocal();
- // Check interrupted
- if (Thread.currentThread().isInterrupted()) {
+ // Check Stopped
+ if (mStoped) {
return;
}
@@ -891,16 +897,16 @@ public final class SpiderQueen implements Runnable {
}
mSpiderInfo.lazySet(spiderInfo);
- // Check interrupted
- if (Thread.currentThread().isInterrupted()) {
+ // Check Stopped
+ if (mStoped) {
return;
}
// Write spider info to file
writeSpiderInfoToLocal(spiderInfo);
- // Check interrupted
- if (Thread.currentThread().isInterrupted()) {
+ // Check Stopped
+ if (mStoped) {
return;
}
@@ -924,17 +930,19 @@ public final class SpiderQueen implements Runnable {
}
// handle pToken request
- while (!Thread.currentThread().isInterrupted()) {
+ while (!mStoped) {
Integer index = mRequestPTokenQueue.poll();
if (index == null) {
// No request index, wait here
synchronized (mQueenLock) {
+ if (mStoped) break;
try {
mQueenLock.wait();
} catch (InterruptedException e) {
break;
}
+ if (mStoped) break;
}
continue;
}
@@ -989,16 +997,9 @@ public final class SpiderQueen implements Runnable {
// Set mQueenThread null
mQueenThread = null;
- // Interrupt decoder
- for (Thread decoderThread : mDecodeThreadArray) {
- if (decoderThread != null) {
- decoderThread.interrupt();
- }
- }
-
- // Interrupt all workers
+ // Stop all workers
synchronized (mWorkerLock) {
- mWorkerPoolExecutor.shutdownNow();
+ mWorkerPoolExecutor.shutdown();
mWorkerPoolExecutor = null;
}
notifyFinish();
@@ -1146,7 +1147,6 @@ public final class SpiderQueen implements Runnable {
String pageUrl = null;
String error = null;
boolean forceHtml = false;
- boolean interrupt = false;
boolean leakSkipHathKey = false;
for (int i = 0; i < 5; i++) {
@@ -1191,10 +1191,9 @@ public final class SpiderQueen implements Runnable {
break;
}
- // Check interrupted
- if (Thread.currentThread().isInterrupted()) {
+ // Check Stopped
+ if (mStoped) {
error = "Interrupted";
- interrupt = true;
break;
}
}
@@ -1226,10 +1225,9 @@ public final class SpiderQueen implements Runnable {
}
}
- // Check interrupted
- if (Thread.currentThread().isInterrupted()) {
+ // Check Stopped
+ if (mStoped) {
error = "Interrupted";
- interrupt = true;
break;
}
}
@@ -1296,7 +1294,7 @@ public final class SpiderQueen implements Runnable {
final byte[] data = new byte[1024 * 4];
long receivedSize = 0;
- while (!Thread.currentThread().isInterrupted()) {
+ while (!mStoped) {
int bytesRead = is.read(data);
if (bytesRead == -1) {
response.close();
@@ -1373,9 +1371,8 @@ public final class SpiderQueen implements Runnable {
}
}
- // Check interrupted
- if (Thread.currentThread().isInterrupted()) {
- interrupt = true;
+ // Check Stopped
+ if (mStoped) {
error = "Interrupted";
break;
}
@@ -1391,13 +1388,11 @@ public final class SpiderQueen implements Runnable {
} catch (InterruptedException e) {
e.printStackTrace();
}
- Thread.sleep(0); // to make allow to catch InterruptedException
return true;
} catch (IOException e) {
e.printStackTrace();
error = GetText.getString(R.string.error_socket);
forceHtml = true;
- } catch (InterruptedException ignored) {
} finally {
IOUtils.closeQuietly(is);
@@ -1411,7 +1406,7 @@ public final class SpiderQueen implements Runnable {
mSpiderDen.remove(index);
updatePageState(index, STATE_FAILED, error);
- return !interrupt;
+ return !mStoped;
}
// false for stop
@@ -1482,7 +1477,7 @@ public final class SpiderQueen implements Runnable {
String pToken = null;
// Get token
- while (!Thread.currentThread().isInterrupted()) {
+ while (!mStoped) {
synchronized (mPTokenLock) {
pToken = spiderInfo.pTokenMap.get(index);
}
@@ -1494,6 +1489,7 @@ public final class SpiderQueen implements Runnable {
}
// Wait
synchronized (mWorkerLock) {
+ if (mStoped) break;
try {
mWorkerLock.wait();
} catch (InterruptedException e) {
@@ -1503,6 +1499,7 @@ public final class SpiderQueen implements Runnable {
}
break;
}
+ if (mStoped) break;
}
} else {
break;
@@ -1519,7 +1516,7 @@ public final class SpiderQueen implements Runnable {
String previousPToken = null;
int previousIndex = index - 1;
// Get token
- while (previousIndex >= 0 && !Thread.currentThread().isInterrupted()) {
+ while (previousIndex >= 0 && !mStoped) {
synchronized (mPTokenLock) {
previousPToken = spiderInfo.pTokenMap.get(previousIndex);
}
@@ -1531,6 +1528,7 @@ public final class SpiderQueen implements Runnable {
}
// Wait
synchronized (mWorkerLock) {
+ if (mStoped) break;
try {
mWorkerLock.wait();
} catch (InterruptedException e) {
@@ -1540,6 +1538,7 @@ public final class SpiderQueen implements Runnable {
}
break;
}
+ if (mStoped) break;
}
} else {
break;
@@ -1563,8 +1562,7 @@ public final class SpiderQueen implements Runnable {
Log.i(TAG, Thread.currentThread().getName() + ": start");
}
- while (mSpiderDen.isReady() && !Thread.currentThread().isInterrupted() && runInternal())
- ;
+ while (mSpiderDen.isReady() && !mStoped && runInternal()) ;
boolean finish;
// Clear in spider worker array
@@ -1607,9 +1605,10 @@ public final class SpiderQueen implements Runnable {
Log.i(TAG, Thread.currentThread().getName() + ": start");
}
- while (!Thread.currentThread().isInterrupted()) {
+ while (!mStoped) {
int index;
synchronized (mDecodeRequestQueue) {
+ if (mStoped) break;
if (mDecodeRequestQueue.isEmpty()) {
try {
mDecodeRequestQueue.wait();
@@ -1617,6 +1616,7 @@ public final class SpiderQueen implements Runnable {
// Interrupted
break;
}
+ if (mStoped) break;
continue;
}
index = mDecodeRequestQueue.remove();