Files
EhViewer/app/src/main/java/com/hippo/ehviewer/gallery/ArchivePageLoader.java
T

359 lines
11 KiB
Java

/*
* Copyright 2019 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.gallery;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.os.Process;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.Native;
import com.hippo.UriArchiveAccessor;
import com.hippo.ehviewer.GetText;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.image.Image;
import com.hippo.unifile.UniFile;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.SimpleHandler;
import com.hippo.yorozuya.thread.PVLock;
import com.hippo.yorozuya.thread.PriorityThread;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
import kotlin.Pair;
import kotlin.Unit;
import kotlin.coroutines.Continuation;
import kotlin.coroutines.intrinsics.CoroutineSingletons;
public class ArchivePageLoader extends PageLoader2 {
private static final AtomicInteger sIdGenerator = new AtomicInteger();
public static Handler showPasswd;
public static String passwd;
public static PVLock pv = new PVLock(0);
private final UriArchiveAccessor archiveAccessor;
private final Stack<Integer> requests = new Stack<>();
private final LinkedHashMap<Integer, Image.CloseableSource> streams = new LinkedHashMap<>();
private final Thread[] decodeThread = new Thread[]{
new Thread(new DecodeTask()),
new Thread(new DecodeTask()),
new Thread(new DecodeTask()),
new Thread(new DecodeTask())
};
private Thread archiveThread;
private volatile int size = PageLoader.STATE_WAIT;
private String error;
public ArchivePageLoader(Context context, Uri uri) {
UriArchiveAccessor archiveAccessor1;
try {
archiveAccessor1 = new UriArchiveAccessor(context, uri);
} catch (Exception e) {
archiveAccessor1 = null;
e.printStackTrace();
}
archiveAccessor = archiveAccessor1;
}
@Override
public void start() {
int id = sIdGenerator.incrementAndGet();
archiveThread = new PriorityThread(
new ArchiveHostTask(), "ArchiveTask" + '-' + id, Process.THREAD_PRIORITY_BACKGROUND);
archiveThread.start();
for (Thread i : decodeThread) {
i.start();
}
}
@Override
public void stop() {
super.stop();
if (archiveThread != null) {
archiveThread.interrupt();
archiveThread = null;
}
for (Thread i : decodeThread) {
i.interrupt();
}
try {
archiveAccessor.close();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public int getSize() {
return size;
}
@Override
protected void onRequest(int index) {
boolean inDecodeTask;
synchronized (streams) {
inDecodeTask = streams.containsKey(index);
}
synchronized (requests) {
boolean inArchiveTask = requests.contains(index);
if (!inArchiveTask && !inDecodeTask) {
requests.add(index);
requests.notify();
}
}
notifyPageWait(index);
}
@Override
protected void onForceRequest(int index) {
onRequest(index);
}
@Override
protected void onCancelRequest(int index) {
synchronized (requests) {
requests.remove(Integer.valueOf(index));
}
}
@NonNull
@Override
public String getImageFilename(int index) {
return FileUtils.getNameFromFilename(getImageFilenameWithExtension(index));
}
@NonNull
@Override
public String getImageFilenameWithExtension(int index) {
return FileUtils.sanitizeFilename(archiveAccessor.getFilename(index));
}
@Override
public boolean save(int index, @NonNull UniFile file) {
int fd;
FileOutputStream stream;
try {
stream = (FileOutputStream) file.openOutputStream();
fd = Native.getFd(stream.getFD());
} catch (IOException e) {
e.printStackTrace();
return false;
}
archiveAccessor.extractToFd(index, fd);
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
@Nullable
@Override
public UniFile save(int index, @NonNull UniFile dir, @NonNull String filename) {
String extension = FileUtils.getExtensionFromFilename(getImageFilenameWithExtension(index));
UniFile dst = dir.subFile(null != extension ? filename + "." + extension : filename);
save(index, dst);
return dst;
}
@Override
protected void preloadPages(@NonNull List<Integer> pages, @NonNull Pair<Integer, Integer> pair) {
}
private Continuation<? super Unit> continuation = null;
@Nullable
@Override
public Object awaitReady(@NonNull Continuation<? super Unit> $completion) {
continuation = $completion;
return CoroutineSingletons.COROUTINE_SUSPENDED;
}
private class ArchiveHostTask implements Runnable {
public final Thread[] archiveThreads = new Thread[]{
new Thread(new ArchiveTask()),
new Thread(new ArchiveTask()),
new Thread(new ArchiveTask()),
new Thread(new ArchiveTask())
};
private void waitPasswd() throws InterruptedException {
showPasswd.sendEmptyMessage(0);
pv.p();
}
private void notifyError() {
showPasswd.sendEmptyMessage(1);
}
private void notifyDismiss() {
showPasswd.sendEmptyMessage(2);
}
@Override
public void run() {
try {
size = archiveAccessor.open();
} catch (Exception e) {
e.printStackTrace();
size = 0;
}
if (size <= 0) {
size = PageLoader.STATE_ERROR;
error = GetText.getString(R.string.error_reading_failed);
return;
}
SimpleHandler.getInstance().post(() -> continuation.resumeWith(true));
if (archiveAccessor.needPassword()) {
boolean need_request = true;
Set<String> set = Settings.getArchivePasswds();
if (set != null) {
for (String passwd : set) {
if (archiveAccessor.providePassword(passwd)) {
need_request = false;
break;
}
}
}
while (!Thread.currentThread().isInterrupted() && need_request) {
try {
waitPasswd();
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
if (!archiveAccessor.providePassword(passwd))
notifyError();
else {
Settings.putPasswdToArchivePasswds(passwd);
notifyDismiss();
break;
}
}
}
for (Thread i : archiveThreads) {
i.start();
}
Object o = new Object();
synchronized (o) {
try {
o.wait();
} catch (InterruptedException e) {
for (Thread i : archiveThreads) {
i.interrupt();
}
}
}
}
}
private class ArchiveTask implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
int index;
synchronized (requests) {
if (requests.isEmpty()) {
try {
requests.wait();
} catch (InterruptedException e) {
// Interrupted
break;
}
continue;
}
index = requests.pop();
}
// Check index valid
if (index < 0 || index >= size) {
notifyPageFailed(index, null);
continue;
}
synchronized (streams) {
if (streams.get(index) != null) {
continue;
}
}
Image.CloseableSource src = archiveAccessor.getImageSource(index);
synchronized (streams) {
streams.put(index, src);
streams.notify();
}
}
}
}
private class DecodeTask implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
int index;
Image.CloseableSource src;
synchronized (streams) {
if (streams.isEmpty()) {
try {
streams.wait();
} catch (InterruptedException e) {
break;
}
continue;
}
Iterator<Map.Entry<Integer, Image.CloseableSource>> iterator = streams.entrySet().iterator();
Map.Entry<Integer, Image.CloseableSource> entry = iterator.next();
iterator.remove();
index = entry.getKey();
src = entry.getValue();
}
Image image = null;
if (src != null) {
image = Image.decode(src);
}
if (image != null) {
notifyPageSucceed(index, image);
} else {
notifyPageFailed(index, null);
}
}
}
}
}