Add download

This commit is contained in:
seven332 committed 2015-08-19 22:20:03 +08:00
1 parent 3aaf14ef3f
commit 4a80ea7dcd
43 files changed
+2167 -270

No files matched your search

+5 -5
View File
@@ -2,13 +2,13 @@ apply plugin: 'com.android.model.application'
model {
android {
compileSdkVersion = 22
buildToolsVersion = "23.0.0 rc3"
compileSdkVersion = 23
buildToolsVersion = "23.0.0"
defaultConfig.with {
applicationId = "com.hippo.ehviewer.test"
minSdkVersion.apiLevel = 14
targetSdkVersion.apiLevel = 22
targetSdkVersion.apiLevel = 23
versionCode = 1
versionName = "1.0"
}
@@ -45,8 +45,8 @@ model {
}
dependencies {
compile 'com.android.support:support-v4:22.2.1'
compile 'com.android.support:appcompat-v7:22.2.1'
compile 'com.android.support:support-v4:23.0.0'
compile 'com.android.support:appcompat-v7:23.0.0'
compile 'de.greenrobot:greendao:1.3.7'
compile 'com.squareup.leakcanary:leakcanary-android:1.3.1'
compile fileTree(dir: 'libs', include: ['*.jar'])
+5
View File
@@ -51,6 +51,11 @@
</activity>
<service
android:name="com.hippo.ehviewer.service.DownloadService"
android:icon="@mipmap/ic_launcher"
android:label="@string/download_server"/>
</application>
</manifest>
@@ -30,6 +30,8 @@ public final class Constants {
public static final int MESSENGER_ID_ARCHIVE_DOWNLOAD_LOCATION;
public static final int MESSENGER_ID_UPDATE_QUICK_SEARCH;
public static final int MESSENGER_ID_UPDATE_QUICK_SEARCH_NAME;
public static final int MESSENGER_ID_UPDATE_DOWNLOAD;
public static final int MESSENGER_ID_UPDATE_DOWNLOAD_TAG;
static {
Messenger messenger = Messenger.getInstance();
@@ -43,5 +45,7 @@ public final class Constants {
MESSENGER_ID_ARCHIVE_DOWNLOAD_LOCATION = messenger.newId();
MESSENGER_ID_UPDATE_QUICK_SEARCH = messenger.newId();
MESSENGER_ID_UPDATE_QUICK_SEARCH_NAME = messenger.newId();
MESSENGER_ID_UPDATE_DOWNLOAD = messenger.newId();
MESSENGER_ID_UPDATE_DOWNLOAD_TAG = messenger.newId();
}
}
@@ -26,6 +26,7 @@ import com.hippo.ehviewer.gallery.GallerySpider;
import com.hippo.ehviewer.gallery.ImageHandler;
import com.hippo.ehviewer.gallery.ui.TouchHelper;
import com.hippo.ehviewer.network.EhHttpClient;
import com.hippo.ehviewer.service.DownloadManager;
import com.hippo.ehviewer.util.DBUtils;
import com.hippo.ehviewer.util.Settings;
import com.hippo.scene.SceneApplication;
@@ -51,6 +52,7 @@ public class EhApplication extends SceneApplication {
Settings.initialize(this);
DBUtils.initialize(this);
TouchHelper.initialize(this);
DownloadManager.initialize();
File sayFile = AppConfig.getFileInAppDir("EhViewer.log");
if (sayFile != null) {
@@ -289,8 +289,10 @@ public final class EhClient {
// Clear
mEhHttpRequest = null;
mListener = null;
} else if (status == Status.FINISHED) {
mEhHttpRequest.disconnect();
} else if (status == Status.RUNNING) {
if (mEhHttpRequest != null) {
mEhHttpRequest.cancel();
}
}
}
}
@@ -53,7 +53,7 @@ class EhEngine {
String body = response.getString();
return SignInParser.parse(body);
} catch (Exception e) {
if (httpRequest.getState() == HttpRequest.STATE_DISCONNECT) {
if (httpRequest.isCancelled()) {
throw new CanceledException();
} else {
throw e;
@@ -71,7 +71,7 @@ class EhEngine {
String body = response.getString();
return GalleryListParser.parse(body, source);
} catch (Exception e) {
if (httpRequest.getState() == HttpRequest.STATE_DISCONNECT) {
if (httpRequest.isCancelled()) {
throw new CanceledException();
} else {
throw e;
@@ -93,7 +93,7 @@ class EhEngine {
String body = response.getString();
return PopularParser.parse(body);
} catch (Exception e) {
if (httpRequest.getState() == HttpRequest.STATE_DISCONNECT) {
if (httpRequest.isCancelled()) {
throw new CanceledException();
} else {
throw e;
@@ -116,7 +116,7 @@ class EhEngine {
GalleryDetailParser.REQUEST_PREVIEW |
GalleryDetailParser.REQUEST_COMMENT);
} catch (Exception e) {
if (httpRequest.getState() == HttpRequest.STATE_DISCONNECT) {
if (httpRequest.isCancelled()) {
throw new CanceledException();
} else {
throw e;
@@ -134,7 +134,7 @@ class EhEngine {
String body = response.getString();
return GalleryDetailParser.parsePreview(body, source);
} catch (Exception e) {
if (httpRequest.getState() == HttpRequest.STATE_DISCONNECT) {
if (httpRequest.isCancelled()) {
throw new CanceledException();
} else {
throw e;
@@ -174,7 +174,7 @@ class EhEngine {
return list;
} catch (Exception e) {
if (httpRequest.getState() == HttpRequest.STATE_DISCONNECT) {
if (httpRequest.isCancelled()) {
throw new CanceledException();
} else {
throw e;
@@ -203,7 +203,7 @@ class EhEngine {
return RateGalleryParser.parse(body);
} catch (Exception e) {
if (httpRequest.getState() == HttpRequest.STATE_DISCONNECT) {
if (httpRequest.isCancelled()) {
throw new CanceledException();
} else {
throw e;
@@ -0,0 +1,54 @@
/*
* Copyright 2015 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.client.data;
import com.hippo.ehviewer.dao.DownloadInfoObj;
import com.hippo.ehviewer.util.DBUtils;
public class DownloadInfo {
public static final int STATE_NONE = 0x0;
public static final int STATE_WAIT = 0x1;
public static final int STATE_DOWNLOAD = 0x2;
public static final int STATE_FINISH = 0x3;
public String tag;
public GalleryBase galleryBase;
public int state = STATE_NONE;
// byte/second, -1 for can't get speed now
public long speed;
// -1 for unknown
public int download = -1;
// -1 for unknown
public int total = -1;
// Integer.MAX_VALUE for unknown
public int legacy = Integer.MAX_VALUE;
public static DownloadInfo fromDownloadInfoObj(DownloadInfoObj obj) {
GalleryBase galleryBase = DBUtils.getGalleryBase((int) (long) obj.getGid());
if (galleryBase != null) {
DownloadInfo downloadInfo = new DownloadInfo();
downloadInfo.galleryBase = galleryBase;
downloadInfo.tag = obj.getTag();
downloadInfo.state = obj.getState();
downloadInfo.legacy = obj.getLegacy();
return downloadInfo;
} else {
return null;
}
}
}
@@ -58,7 +58,8 @@ public class GallerySpider implements GalleryProvider, SpiderQueen.SpiderListene
private AtomicLongArray mReceivedSizes;
private AtomicReferenceArray<Float> mPercents;
private List<GalleryProviderListener> mGalleryProviderListeners = new ArrayList<>();
private List<GalleryProviderListener> mGalleryProviderListeners = new ArrayList<>(2);
private List<SpiderQueen.SpiderListener> mSpiderListeners = new ArrayList<>(2);
private int mReference = 0;
@@ -160,6 +161,14 @@ public class GallerySpider implements GalleryProvider, SpiderQueen.SpiderListene
mGalleryProviderListeners.remove(listener);
}
public void addSpiderSpiderListener(SpiderQueen.SpiderListener listener) {
mSpiderListeners.add(listener);
}
public void removeSpiderSpiderListener(SpiderQueen.SpiderListener listener) {
mSpiderListeners.remove(listener);
}
@Override
public Object request(int index) {
if (mSpiderQueen != null) {
@@ -178,7 +187,11 @@ public class GallerySpider implements GalleryProvider, SpiderQueen.SpiderListene
@Override
public Object forceRequest(int index) {
return null;// TODO
if (mSpiderQueen != null) {
return mSpiderQueen.forceRequest(index);
} else {
return GalleryProvider.RESULT_OUT_OF_RANGE;
}
}
@Override
@@ -226,6 +239,10 @@ public class GallerySpider implements GalleryProvider, SpiderQueen.SpiderListene
for (GalleryProviderListener l : mGalleryProviderListeners) {
l.onTotallyFailed(e);
}
for (SpiderQueen.SpiderListener l : mSpiderListeners) {
l.onTotallyFailed(e);
}
}
});
}
@@ -238,13 +255,24 @@ public class GallerySpider implements GalleryProvider, SpiderQueen.SpiderListene
for (GalleryProviderListener l : mGalleryProviderListeners) {
l.onPartlyFailed(e);
}
for (SpiderQueen.SpiderListener l : mSpiderListeners) {
l.onPartlyFailed(e);
}
}
});
}
@Override
public void onDone() {
// TODO
public void onDone(final int legacy) {
SimpleHandler.getInstance().post(new Runnable() {
@Override
public void run() {
for (SpiderQueen.SpiderListener l : mSpiderListeners) {
l.onDone(legacy);
}
}
});
}
@Override
@@ -259,6 +287,10 @@ public class GallerySpider implements GalleryProvider, SpiderQueen.SpiderListene
for (GalleryProviderListener l : mGalleryProviderListeners) {
l.onGetSize(pages);
}
for (SpiderQueen.SpiderListener l : mSpiderListeners) {
l.onGetPages(pages);
}
}
});
}
@@ -277,17 +309,21 @@ public class GallerySpider implements GalleryProvider, SpiderQueen.SpiderListene
for (GalleryProviderListener l : mGalleryProviderListeners) {
l.onPagePercent(index, 0.0f);
}
for (SpiderQueen.SpiderListener l : mSpiderListeners) {
l.onSpiderStart(index, totalSize);
}
}
});
}
@Override
public void onSpiderPage(final int index, long receivedSize) {
public void onSpiderPage(final int index, final long receivedSize, final long singleReceivedSize) {
if (mReceivedSizes != null && index >= 0 && index < mReceivedSizes.length()) {
mReceivedSizes.lazySet(index, receivedSize);
}
long totalSize= mTotalSizes.get(index);
final long totalSize= mTotalSizes.get(index);
final float percent = totalSize == 0 ? 0.0f :
(float) receivedSize / (float) totalSize;
@@ -299,6 +335,10 @@ public class GallerySpider implements GalleryProvider, SpiderQueen.SpiderListene
for (GalleryProviderListener l : mGalleryProviderListeners) {
l.onPagePercent(index, percent);
}
for (SpiderQueen.SpiderListener l : mSpiderListeners) {
l.onSpiderPage(index, receivedSize, singleReceivedSize);
}
}
});
}
@@ -311,6 +351,10 @@ public class GallerySpider implements GalleryProvider, SpiderQueen.SpiderListene
for (GalleryProviderListener l : mGalleryProviderListeners) {
l.onPageSucceed(index);
}
for (SpiderQueen.SpiderListener l : mSpiderListeners) {
l.onSpiderSucceed(index);
}
}
});
}
@@ -323,10 +367,21 @@ public class GallerySpider implements GalleryProvider, SpiderQueen.SpiderListene
for (GalleryProviderListener l : mGalleryProviderListeners) {
l.onPageFailed(index, e);
}
for (SpiderQueen.SpiderListener l : mSpiderListeners) {
l.onSpiderFailed(index, e);
}
}
});
}
@Override
public void onSpiderCancelled(final int index) {
for (SpiderQueen.SpiderListener l : mSpiderListeners) {
l.onSpiderCancelled(index);
}
}
@Override
public void onGetImage(final int index, final Object obj) {
SimpleHandler.getInstance().post(new Runnable() {
@@ -250,6 +250,31 @@ public class ImageHandler {
}
}
public void removeFromRead(int index) {
sDiskCache.remove(EhCacheKeyFactory.getImageKey(mGalleryBase.gid, index));
}
public void removeFromDownload(int index) {
if (mDownloadDir == null) {
return;
}
for (String extension : POSSIBLE_IMAGE_EXTENSIONS) {
String filename = getImageFilename(index, extension);
UniFile file = mDownloadDir.findFile(filename);
if (file != null) {
file.delete();
}
}
}
public void remove(int index) {
removeFromRead(index);
if (mMode == Mode.DOWNLOAD) {
removeFromDownload(index);
}
}
public void save(HttpClient httpClient, String url, int index, SaveHelper helper) throws Exception {
OutputStreamPipe osPipe = null;
@@ -328,8 +353,8 @@ public class ImageHandler {
}
@Override
public void onDonwlad(long receivedSize) {
mHelper.onSave(receivedSize);
public void onDonwlad(long receivedSize, long singleReceivedSize) {
mHelper.onSave(receivedSize, singleReceivedSize);
}
@Override
@@ -442,7 +467,7 @@ public class ImageHandler {
public abstract void onStartSaving(long totalSize);
public abstract void onSave(long receivedSize);
public abstract void onSave(long receivedSize, long singleReceivedSize);
public boolean isCancelled() {
return mCancel;
@@ -20,12 +20,13 @@ import android.util.Log;
import com.hippo.ehviewer.client.EhConfig;
import com.hippo.ehviewer.client.data.GalleryBase;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.AutoExpandArray;
import com.hippo.yorozuya.IOUtils;
import java.io.BufferedInputStream;
import java.io.EOFException;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -72,119 +73,86 @@ public class SpiderInfo {
}
GalleryBase gb = galleryBase;
OutputStreamWriter osWrite = new OutputStreamWriter(os, "UTF-8");
osWrite.append(Integer.toString(VERSION)).append('\n')
.append(Integer.toString(gb.gid)).append('\n')
.append(gb.token).append('\n')
.append(stringToHexString(gb.title)).append('\n')
.append(stringToHexString(gb.titleJpn)).append('\n')
.append(gb.thumb).append('\n')
.append(Integer.toString(gb.category)).append('\n')
.append(gb.posted).append('\n')
.append(gb.uploader).append('\n')
.append(Float.toString(gb.rating)).append('\n')
.append(Integer.toString(pages)).append('\n')
.append(previewSize).append('\n')
.append(Integer.toString(previewPages)).append('\n')
.append(Integer.toString(previewCountPerPage)).append('\n');
JSONObject jo;
try {
jo = new JSONObject();
jo.put("version", VERSION);
jo.put("gid", gb.gid);
jo.put("token", gb.token);
jo.put("title", gb.title);
jo.put("titleJpn", gb.titleJpn);
jo.put("thumb", gb.thumb);
jo.put("category", gb.category);
jo.put("posted", gb.posted);
jo.put("uploader", gb.uploader);
jo.put("rating", gb.rating);
jo.put("pages", pages);
jo.put("previewSize", previewSize);
jo.put("previewPages", previewPages);
jo.put("previewCountPerPage", previewCountPerPage);
if (tokens != null) {
JSONArray tokenJa = new JSONArray();
int length = Math.min(tokens.maxValidIndex() + 1, pages);
for (int i = 0; i < length; i++) {
String token = tokens.get(i);
if (token != null) {
osWrite.append(Integer.toString(i)).append(':')
.append(token).append('\n');
}
tokenJa.put(tokens.get(i));
}
jo.put("tokens", tokenJa);
} catch (JSONException e) {
Log.e(TAG, "Can't create SpiderInfo JSON to write", e);
jo = null;
}
osWrite.flush();
osWrite.close();
if (jo != null) {
OutputStreamWriter osWrite = new OutputStreamWriter(os, "UTF-8");
osWrite.append(jo.toString());
osWrite.flush();
osWrite.close();
}
}
public static SpiderInfo read(InputStream is) throws Exception {
BufferedInputStream bis = new BufferedInputStream(is);
SpiderInfo spiderInfo = new SpiderInfo();
String str = IOUtils.readString(is, "UTF-8");
int version = Integer.parseInt(IOUtils.readAsciiLine(bis));
JSONObject jo = new JSONObject(str);
int version = jo.optInt("version", 0);
if (VERSION != version){
throw new IllegalStateException("Version should be " + VERSION +
", but it is " + version);
}
SpiderInfo spiderInfo = new SpiderInfo();
GalleryBase gb = new GalleryBase();
gb.gid = Integer.parseInt(IOUtils.readAsciiLine(bis));
gb.token = IOUtils.readAsciiLine(bis);
gb.title = hexStringToString(IOUtils.readAsciiLine(bis));
gb.titleJpn = hexStringToString(IOUtils.readAsciiLine(bis));
gb.thumb = IOUtils.readAsciiLine(bis);
gb.category = Integer.parseInt(IOUtils.readAsciiLine(bis));
gb.posted = IOUtils.readAsciiLine(bis);
gb.uploader = IOUtils.readAsciiLine(bis);
gb.rating = Float.parseFloat(IOUtils.readAsciiLine(bis));
gb.gid = jo.getInt("gid");
gb.token = jo.getString("token");
gb.title = jo.getString("title");
gb.titleJpn = jo.optString("titleJpn", null);
gb.thumb = jo.getString("thumb");
gb.category = jo.getInt("category");
gb.posted = jo.getString("posted");
gb.uploader = jo.getString("uploader");
gb.rating = (float) jo.getDouble("rating");
spiderInfo.galleryBase = gb;
spiderInfo.pages = Integer.parseInt(IOUtils.readAsciiLine(bis));
spiderInfo.previewSize = IOUtils.readAsciiLine(bis);
spiderInfo.pages = jo.getInt("pages");
spiderInfo.previewSize = jo.getString("previewSize");
if (!spiderInfo.previewSize.equals(EhConfig.PREVIEW_SIZE_NORMAL) &&
!spiderInfo.previewSize.equals(EhConfig.PREVIEW_SIZE_LARGE)) {
throw new IllegalStateException("Unknown preview size " + spiderInfo.previewSize);
}
spiderInfo.previewPages = Integer.parseInt(IOUtils.readAsciiLine(bis));
spiderInfo.previewCountPerPage = Integer.parseInt(IOUtils.readAsciiLine(bis));
spiderInfo.previewPages = jo.getInt("previewPages");
spiderInfo.previewCountPerPage = jo.getInt("previewCountPerPage");
AutoExpandArray<String> tokens = new AutoExpandArray<>(spiderInfo.pages);
try {
//noinspection InfiniteLoopStatement
for (;;) {
String line = IOUtils.readAsciiLine(is);
int pos = line.indexOf(":");
if (pos < 0) {
Log.e(TAG, "Can't parse line " + line);
continue;
}
try {
int index = Integer.parseInt(line.substring(0, pos));
String token = line.substring(pos + 1);
tokens.set(index, token);
} catch (NumberFormatException e) {
// Empty
Log.e(TAG, "Can't parse line " + line);
}
}
} catch (EOFException e) {
// Empty
}
JSONArray ja = jo.getJSONArray("tokens");
AutoExpandArray<String> tokens = new AutoExpandArray<>(ja.length());
spiderInfo.tokens = tokens;
for (int i = 0, n = ja.length(); i < n; i++) {
tokens.set(i, ja.getString(i));
}
return spiderInfo;
}
public static String stringToHexString(String str) {
StringBuilder sb = new StringBuilder(320);
int length = str.length();
for (int i = 0; i < length; i++) {
char ch = str.charAt(i);
String s = Integer.toString((int) ch, 16);
int length2 = 4 - s.length();
for (int j = 0; j < length2; j++) {
sb.append('0');
}
sb.append(s);
}
return sb.toString();
}
public static String hexStringToString(String str) {
AssertUtils.assertEquals("Hex String's length must be a multiple of 4.", str.length() % 4, 0);
StringBuilder sb = new StringBuilder(80);
int length = str.length();
for (int i = 0; i < length; i += 4) {
sb.append((char) Integer.parseInt(str.substring(i, i + 4), 16));
}
return sb.toString();
}
}
@@ -39,7 +39,6 @@ import com.hippo.yorozuya.Say;
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.LinkedList;
@@ -47,8 +46,6 @@ import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.concurrent.locks.ReentrantLock;
public class SpiderQueen implements Runnable {
@@ -79,9 +76,11 @@ public class SpiderQueen implements Runnable {
private SpiderListener mSpiderListener;
public SpiderInfo mSpiderInfo;
public boolean mHasCheckIndexAgain = true;
public volatile int mNextIndex;
public AtomicIntegerArray mPageStates;
public int[] mPageStates;
public LinkedList<Integer> mRequestIndex = new LinkedList<>();
private volatile HttpRequest mCurrentHttpRequest;
@@ -100,8 +99,8 @@ public class SpiderQueen implements Runnable {
// The lock to make worker sleep
private final Object mWorkerLock = new Object();
// The lock for next index
private final Object mNextIndexLock = new Object();
private final ReentrantLock mWorkerArrayLock = new ReentrantLock();
private final Object mIndexLock = new Object();
private final Object mWorkerArrayLock = new Object();
// Lock for mRequestTokens and mSpiderInfo.tokens
private final Object mTokenLock = new Object();
// The lock to make sure onGetSize call first
@@ -142,7 +141,7 @@ public class SpiderQueen implements Runnable {
return GalleryProvider.RESULT_OUT_OF_RANGE;
}
int state = mPageStates.get(index);
int state = getPageState(index);
switch (state) {
case PAGE_STATE_NONE:
// TODO set next index
@@ -169,11 +168,8 @@ public class SpiderQueen implements Runnable {
if (index < 0 || index >= mSpiderInfo.pages) {
return GalleryProvider.RESULT_OUT_OF_RANGE;
}
// TODO
throw new IllegalStateException();
addRequestIndex(index);
return GalleryProvider.RESULT_WAIT;
} else {
return GalleryProvider.RESULT_OUT_OF_RANGE;
}
@@ -188,7 +184,7 @@ public class SpiderQueen implements Runnable {
public void stop() {
mStop = true;
if (mCurrentHttpRequest != null) {
mCurrentHttpRequest.disconnect();
mCurrentHttpRequest.cancel();
mCurrentHttpRequest = null;
}
synchronized (mQueenLock) {
@@ -211,7 +207,7 @@ public class SpiderQueen implements Runnable {
}
public int pages() {
if (mSpiderInfo != null) {
if (mSpiderInfo != null && mGetSizeFinished) {
return mSpiderInfo.pages;
} else {
return -1;
@@ -567,17 +563,19 @@ public class SpiderQueen implements Runnable {
return;
}
setGetSizeFinished(false);
// 5. Perpare
// Update EhConfig preview size
ehConfig.previewSize = spiderInfo.previewSize;
ehConfig.setDirty();
mSpiderInfo = spiderInfo;
mNextIndex = 0;
// Init AtomicIntegerArray stuff
// Init int array stuff
int length = spiderInfo.pages;
mPageStates = new AtomicIntegerArray(length);
mPageStates = new int[length];
for (int i = 0; i < length; i++) {
mPageStates.lazySet(i, PAGE_STATE_NONE);
mPageStates[i] = PAGE_STATE_NONE;
}
mSpiderWorkers = new SpiderWorker[3];
mImageHandler = new ImageHandler(gb, spiderInfo.pages, mMode, mDownloadDirparent, mDownloadDirname);
@@ -587,7 +585,6 @@ public class SpiderQueen implements Runnable {
}
// 6. Make workers and render to work
setGetSizeFinished(false);
ensureWorker();
mRender = new Render();
Thread thread = new PriorityThread(mRender, "Render", Process.THREAD_PRIORITY_BACKGROUND);
@@ -601,42 +598,30 @@ public class SpiderQueen implements Runnable {
// 8. Get tokens
handleToken(spiderInfo, ehConfig);
// 5. The queen dies, workers die, render dies
mWorkerArrayLock.lock();
for (SpiderWorker worker : mSpiderWorkers) {
if (worker != null) {
worker.stop();
// 9. The queen dies, workers die, render dies
synchronized (mWorkerArrayLock) {
for (SpiderWorker worker : mSpiderWorkers) {
if (worker != null) {
worker.stop();
}
}
synchronized (mWorkerLock) {
mWorkerLock.notifyAll();
}
}
synchronized (mWorkerLock) {
mWorkerLock.notifyAll();
}
mWorkerArrayLock.unlock();
mRender.stop();
}
private void ensureWorker() {
mWorkerArrayLock.lock();
int length = mSpiderWorkers.length;
for (int i = 0; i < length; i++) {
if (mSpiderWorkers[i] == null) {
SpiderWorker worker = new SpiderWorker(i);
mSpiderWorkers[i] = worker;
mThreadFactory.newThread(worker).start();
}
}
mWorkerArrayLock.unlock();
}
private void setNextIndex(int index) {
synchronized (mNextIndexLock) {
if (mSpiderInfo != null && index < mSpiderInfo.pages) {
mNextIndex = index;
synchronized (mWorkerArrayLock) {
int length = mSpiderWorkers.length;
for (int i = 0; i < length; i++) {
if (mSpiderWorkers[i] == null) {
SpiderWorker worker = new SpiderWorker(i);
mSpiderWorkers[i] = worker;
mThreadFactory.newThread(worker).start();
}
}
}
}
@@ -656,29 +641,79 @@ public class SpiderQueen implements Runnable {
}
}
// TODO request index may
private void setNextIndex(int index) {
synchronized (mIndexLock) {
if (mSpiderInfo != null && index < mSpiderInfo.pages) {
mNextIndex = index;
}
}
}
private int getPageState(int index) {
synchronized (mIndexLock) {
return mPageStates[index];
}
}
private void setPageState(int index, int state) {
synchronized (mIndexLock) {
mPageStates[index] = state;
}
}
// it to request queue
private void addRequestIndex(int index) {
synchronized (mTokenLock) {
if (mPageStates[index] != PAGE_STATE_SPIDER) {
mPageStates[index] = PAGE_STATE_NONE;
mRequestIndex.add(index);
mImageHandler.remove(index);
}
}
}
// Get first PAGE_STATE_NONE index and set state PAGE_STATE_SPIDER
private int getRequestIndex() {
synchronized (mNextIndexLock) {
synchronized (mIndexLock) {
int length = mSpiderInfo.pages;
for (;;) {
if (mNextIndex >= length) {
if (!mHasCheckIndexAgain) {
mHasCheckIndexAgain = true;
mNextIndex = 0;
} else {
return -1;
int index;
if (mRequestIndex.isEmpty()) {
if (mNextIndex >= length) {
if (!mHasCheckIndexAgain) {
mHasCheckIndexAgain = true;
mNextIndex = 0;
} else {
return -1;
}
}
index = mNextIndex++;
} else {
index = mRequestIndex.remove(0);
}
int index = mNextIndex;
if (mPageStates.get(mNextIndex++) == PAGE_STATE_NONE) {
if (mPageStates[index] == PAGE_STATE_NONE) {
mPageStates[index] = PAGE_STATE_SPIDER;
return index;
}
}
}
}
private int getLegacy() {
synchronized (mIndexLock) {
int legacy = 0;
for (int i = 0, n = mPageStates.length; i < n; i++) {
int state = mPageStates[i];
if (state != PAGE_STATE_SUCCEED) {
legacy++;
}
}
return legacy;
}
}
class SpiderWorker implements Runnable {
private String TAG = SpiderWorker.class.getSimpleName();
@@ -698,7 +733,7 @@ public class SpiderQueen implements Runnable {
public void stop() {
mStop = true;
if (mCurrentHttpRequest != null) {
mCurrentHttpRequest.disconnect();
mCurrentHttpRequest.cancel();
mCurrentHttpRequest = null;
}
if (mCurrentSaveHelper != null) {
@@ -738,6 +773,13 @@ public class SpiderQueen implements Runnable {
Exception exception = null;
for (int i = 0; i < 2; i++) {
// Check stop
if (mStop) {
setPageState(index, PAGE_STATE_NONE);
mSpiderListener.onSpiderCancelled(index);
return;
}
// Get image url
GalleryPageParser.Result result;
try {
@@ -749,6 +791,13 @@ public class SpiderQueen implements Runnable {
imageUrl = result.imageUrl;
skipHathKey = result.skipHathKey;
// Check stop
if (mStop) {
setPageState(index, PAGE_STATE_NONE);
mSpiderListener.onSpiderCancelled(index);
return;
}
// Download image
try {
mCurrentSaveHelper = new ImageHandler.SaveHelper() {
@@ -758,18 +807,18 @@ public class SpiderQueen implements Runnable {
}
@Override
public void onSave(long receivedSize) {
mSpiderListener.onSpiderPage(index, receivedSize);
public void onSave(long receivedSize, long singleReceivedSize) {
mSpiderListener.onSpiderPage(index, receivedSize, singleReceivedSize);
}
};
mImageHandler.save(mHttpClient, imageUrl, index, mCurrentSaveHelper);
mPageStates.set(index, PAGE_STATE_SUCCEED);
setPageState(index, PAGE_STATE_SUCCEED);
mSpiderListener.onSpiderSucceed(index);
return;
} catch (Exception e) {
if (mCurrentSaveHelper != null && mCurrentSaveHelper.isCancelled()) {
mPageStates.lazySet(index, PAGE_STATE_FAILED);
mSpiderListener.onSpiderFailed(index, new IOException("Cancelled"));
setPageState(index, PAGE_STATE_NONE);
mSpiderListener.onSpiderCancelled(index);
return;
} else {
exception = e;
@@ -779,12 +828,10 @@ public class SpiderQueen implements Runnable {
}
}
mPageStates.lazySet(index, PAGE_STATE_FAILED);
setPageState(index, PAGE_STATE_FAILED);
mSpiderListener.onSpiderFailed(index, exception);
}
@Override
public void run() {
TAG = Thread.currentThread().getName();
@@ -803,14 +850,12 @@ public class SpiderQueen implements Runnable {
if (index == -1) {
// There is no more
break;
} else {
mPageStates.lazySet(index, PAGE_STATE_SPIDER);
}
// Check contain
if (handler.contain(index)) {
// Find it, no need to download
mPageStates.lazySet(index, PAGE_STATE_SUCCEED);
setPageState(index, PAGE_STATE_SUCCEED);
// Listener
mSpiderListener.onSpiderSucceed(index);
continue;
@@ -835,19 +880,26 @@ public class SpiderQueen implements Runnable {
break;
}
}
// For stop
if (mStop) {
mPageStates.lazySet(index, PAGE_STATE_NONE);
break;
}
downloadPage(gb.gid, index, token);
}
// TODO this lock is not right, we need to lock when before check it is over
mWorkerArrayLock.lock();
mSpiderWorkers[mIndex] = null;
mWorkerArrayLock.unlock();
synchronized (mWorkerArrayLock) {
mSpiderWorkers[mIndex] = null;
boolean allDie = true;
for (SpiderWorker worker : mSpiderWorkers) {
if (worker != null) {
allDie = false;
break;
}
}
if (allDie) {
// Get legacy
mSpiderListener.onDone(getLegacy());
}
}
Say.d(TAG, Thread.currentThread().getName() + " dies");
}
@@ -943,22 +995,20 @@ public class SpiderQueen implements Runnable {
void onPartlyFailed(Exception e);
/**
* The whole gallery has been spidered
* TODO
*/
void onDone();
void onDone(int legacy);
void onGetPages(int pages);
void onSpiderStart(int index, long totalSize);
void onSpiderPage(int index, long receivedSize);
void onSpiderPage(int index, long receivedSize, long singleReceivedSize);
void onSpiderSucceed(int index);
void onSpiderFailed(int index, Exception e);
void onSpiderCancelled(int index);
void onGetImage(int index, Object obj);
}
}
@@ -0,0 +1,595 @@
/*
* Copyright 2015 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.service;
import android.os.SystemClock;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import com.hippo.ehviewer.Constants;
import com.hippo.ehviewer.client.data.DownloadInfo;
import com.hippo.ehviewer.client.data.GalleryBase;
import com.hippo.ehviewer.gallery.GallerySpider;
import com.hippo.ehviewer.gallery.ImageHandler;
import com.hippo.ehviewer.gallery.SpiderQueen;
import com.hippo.ehviewer.util.DBUtils;
import com.hippo.ehviewer.util.EhUtils;
import com.hippo.yorozuya.Messenger;
import com.hippo.yorozuya.SimpleHandler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class DownloadManager {
private static final String TAG = DownloadManager.class.getSimpleName();
public static final int OPS_SHIFT = 30;
public static final int OPS_MASK = 0x3 << OPS_SHIFT;
public static final int OPS_ADD = 0 << OPS_SHIFT;
public static final int OPS_REMOVE = 1 << OPS_SHIFT;
public static final int OPS_UPDATE = 2 << OPS_SHIFT;
//
public static final int OPS_ALL_CHANGE = 3 << OPS_SHIFT;
public static int getOps(int mix) {
return (mix & OPS_MASK);
}
public static int getGid(int mix) {
return (mix & ~OPS_MASK);
}
public static int makeMix(int gid, int ops) {
return (gid & ~OPS_MASK) | (ops & OPS_MASK);
}
private List<DownloadInfo> mDownloadInfos;
private Map<String, List<DownloadInfo>> mDownloadInfoMap;
private List<DownloadInfo> mDefaultDownloadInfos;
private List<DownloadInfo> mWaitList;
private @Nullable DownloadInfo mCurrentTask;
private GallerySpider mCurrentSpider;
private SpeedReminder mSpeedReminder;
private DownloadSpiderListener mDownloadSpiderListener;
private @Nullable DownloadUpdateListener mDownloadUpdateListener;
private DownloadUpdateInfo mUpdateInfo;
private static DownloadManager sInstance;
public static void initialize() {
sInstance = new DownloadManager();
}
public static DownloadManager getInstance() {
return sInstance;
}
public DownloadManager() {
List<String> tags = DBUtils.getAllDownloadTag();
List<DownloadInfo> downloadInfos = DBUtils.getAllDownloadInfo();
mDownloadInfos = downloadInfos;
mDownloadInfoMap = new HashMap<>();
// Create list for each tag
for (String tag : tags) {
mDownloadInfoMap.put(tag, new ArrayList<DownloadInfo>());
}
// Create default for non tag
mDefaultDownloadInfos = new ArrayList<>();
// Fill each list with DownloadInfo
for (DownloadInfo info : downloadInfos) {
// Fix download state
if (info.state == DownloadInfo.STATE_DOWNLOAD || info.state == DownloadInfo.STATE_WAIT) {
info.state = DownloadInfo.STATE_NONE;
}
List<DownloadInfo> list;
if (TextUtils.isEmpty(info.tag)) {
list = mDefaultDownloadInfos;
} else {
list = mDownloadInfoMap.get(info.tag);
}
if (list == null) {
list = mDefaultDownloadInfos;
}
list.add(info);
}
mWaitList = new LinkedList<>();
mSpeedReminder = new SpeedReminder();
mDownloadSpiderListener = new DownloadSpiderListener();
mUpdateInfo = new DownloadUpdateInfo();
}
public void setDownloadUpdateListener(@Nullable DownloadUpdateListener listener) {
mDownloadUpdateListener = listener;
}
public List<DownloadInfo> getDownloadList(String tag) {
return getDownloadListInternal(tag, false);
}
private List<DownloadInfo> getDownloadListInternal(String tag, boolean create) {
if (TextUtils.isEmpty(tag)) {
return mDefaultDownloadInfos;
} else {
List<DownloadInfo> list = mDownloadInfoMap.get(tag);
if (list == null && create) {
list = new ArrayList<>();
mDownloadInfoMap.put(tag, list);
// notify
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_DOWNLOAD_TAG, null);
// Save tag to DB
DBUtils.addDownloadTag(tag);
}
return list;
}
}
// Make sure is downloading
private void ensureDownload() {
if (mCurrentTask != null) {
// Only one download
return;
}
Log.v(TAG, "ensureDownload");
// get download from wait list
if (!mWaitList.isEmpty()) {
DownloadInfo info = mWaitList.remove(0);
GallerySpider spider = null;
try {
spider = GallerySpider.obtain(info.galleryBase, ImageHandler.Mode.DOWNLOAD);
} catch (IOException e) {
e.printStackTrace();
}
if (spider != null) {
int gid = info.galleryBase.gid;
mCurrentTask = info;
mCurrentSpider = spider;
info.state = DownloadInfo.STATE_DOWNLOAD;
info.speed = -1;
// Fill Download update info
DownloadUpdateInfo updateInfo = mUpdateInfo;
updateInfo.gid = gid;
updateInfo.title = EhUtils.getSuitableTitle(info.galleryBase);
updateInfo.pages = spider.size();
updateInfo.downloadedPages = 0;
updateInfo.speed = -1;
updateInfo.legacy = Integer.MAX_VALUE;
// Notify
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_DOWNLOAD,
makeMix(gid, OPS_UPDATE));
// Listener
if (mDownloadUpdateListener != null) {
mDownloadUpdateListener.onStart(updateInfo);
}
// Update DownloadInfo to DB
DBUtils.updateDownloadInfo(info);
info.total = spider.size();
mDownloadSpiderListener.start();
spider.addSpiderSpiderListener(mDownloadSpiderListener);
mSpeedReminder.start();
} else {
// Get error when getting spider
info.state = DownloadInfo.STATE_FINISH;
info.legacy = Integer.MAX_VALUE;
// Fill Download update info
DownloadUpdateInfo updateInfo = mUpdateInfo;
updateInfo.gid = info.galleryBase.gid;
updateInfo.title = EhUtils.getSuitableTitle(info.galleryBase);
updateInfo.pages = -1;
updateInfo.downloadedPages = 0;
updateInfo.speed = -1;
updateInfo.legacy = Integer.MAX_VALUE;
// Notify
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_DOWNLOAD,
makeMix(info.galleryBase.gid, OPS_UPDATE));
// Listener
if (mDownloadUpdateListener != null) {
mDownloadUpdateListener.onFinish(updateInfo);
}
// Update DownloadInfo to DB
DBUtils.updateDownloadInfo(info);
// Continue
ensureDownload();
}
}
// No gallery to download
}
/**
* @param tag null for default
*/
public void startDownload(GalleryBase galleryBase, @Nullable String tag) {
try {
if (mCurrentTask != null && mCurrentTask.galleryBase.gid == galleryBase.gid) {
Log.v(TAG, "It is current task");
return;
}
// Check in download list
for (DownloadInfo info: mDownloadInfos) {
if (info.galleryBase.gid == galleryBase.gid) {
Log.v(TAG, "It is in download list");
if (info.state == DownloadInfo.STATE_FINISH || info.state == DownloadInfo.STATE_NONE) {
info.state = DownloadInfo.STATE_WAIT;
// Add to wait list
mWaitList.add(info);
// Notify
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_DOWNLOAD,
makeMix(galleryBase.gid, OPS_UPDATE));
// Update DownloadInfo to DB
DBUtils.updateDownloadInfo(info);
} else {
// No need to update
}
return;
}
}
Log.v(TAG, "It is not in download list");
// It is new download info
DownloadInfo downloadInfo = new DownloadInfo();
downloadInfo.galleryBase = galleryBase;
downloadInfo.tag = tag;
downloadInfo.state = DownloadInfo.STATE_WAIT;
List<DownloadInfo> list = getDownloadListInternal(tag, true);
list.add(downloadInfo);
mDownloadInfos.add(downloadInfo);
// Add to wait list
mWaitList.add(downloadInfo);
// Notify
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_DOWNLOAD,
makeMix(galleryBase.gid, OPS_ADD));
// Save DownloadInfo to DB
DBUtils.addDownloadInfo(downloadInfo);
} finally {
ensureDownload();
}
}
public void startAllDownload() {
for (DownloadInfo info : mDownloadInfos) {
if (info.state == DownloadInfo.STATE_NONE ||
(info.state == DownloadInfo.STATE_FINISH && info.legacy != 0)) {
info.state = DownloadInfo.STATE_WAIT;
// Add to wait list
mWaitList.add(info);
// Notify
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_DOWNLOAD,
makeMix(info.galleryBase.gid, OPS_UPDATE));
// Update DownloadInfo to DB
DBUtils.updateDownloadInfo(info);
}
}
ensureDownload();
}
public void stopDownload(int gid) {
// Check current task
if (mCurrentTask != null && mCurrentTask.galleryBase.gid == gid) {
// Stop current
stopCurrentDownloadInternal();
// Continue
ensureDownload();
return;
}
// Check in wait list
if (!mWaitList.isEmpty()) {
for (Iterator<DownloadInfo> iter = mWaitList.iterator(); iter.hasNext();) {
DownloadInfo info = iter.next();
if (info.galleryBase.gid == gid) {
// Remove from wait list
iter.remove();
info.state = DownloadInfo.STATE_NONE;
// Notify
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_DOWNLOAD,
makeMix(gid, OPS_UPDATE));
// Update in DB
DBUtils.updateDownloadInfo(info);
return;
}
}
}
}
public void stopCurrentDownload() {
stopCurrentDownloadInternal();
ensureDownload();
}
public void stopAllDownload() {
// Stop all in wait list
for (DownloadInfo info : mWaitList) {
info.state = DownloadInfo.STATE_NONE;
// Notify
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_DOWNLOAD,
makeMix(info.galleryBase.gid, OPS_UPDATE));
// Update in DB
DBUtils.updateDownloadInfo(info);
}
mWaitList.clear();
// Stop current
stopCurrentDownloadInternal();
}
private void stopCurrentDownloadInternal() {
if (mCurrentTask == null) {
return;
}
mCurrentTask.state = DownloadInfo.STATE_NONE;
int gid = mCurrentTask.galleryBase.gid;
// Notify
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_DOWNLOAD,
makeMix(gid, OPS_UPDATE));
// Listener
if (mDownloadUpdateListener != null) {
mDownloadUpdateListener.onCancel(mUpdateInfo);
}
// Update in DB
DBUtils.updateDownloadInfo(mCurrentTask);
// Release
releaseCurrent();
}
public boolean hasWaitingDownloadInfo() {
return !mWaitList.isEmpty();
}
public boolean isDownloading() {
return mCurrentTask != null || hasWaitingDownloadInfo();
}
private void releaseCurrent() {
if (mCurrentTask != null) {
mCurrentTask = null;
mCurrentSpider.removeSpiderSpiderListener(mDownloadSpiderListener);
GallerySpider.release(mCurrentSpider);
mCurrentSpider = null;
mDownloadSpiderListener.reset();
mSpeedReminder.stop();
}
}
private class DownloadSpiderListener implements SpiderQueen.SpiderListener {
private long mReceived;
private long mTime;
public void start() {
// reset
mReceived = 0;
mTime = SystemClock.uptimeMillis();
}
public void reset() {
// reset
mReceived = 0;
}
// -1 for can't get speed
public long getSpeed() {
if (mCurrentTask == null || mCurrentTask.total == -1) {
return -1;
} else {
long time = SystemClock.uptimeMillis();
long speed = mReceived * 1000 / (time - mTime);
mTime = time;
mReceived = 0;
return speed;
}
}
@Override
public void onTotallyFailed(Exception e) {
onDone(Integer.MAX_VALUE); // TODO
}
@Override
public void onPartlyFailed(Exception e) {
onDone(Integer.MAX_VALUE); // TODO
}
@Override
public void onDone(int legacy) {
if (mCurrentTask == null) {
return;
}
mCurrentTask.state = DownloadInfo.STATE_FINISH;
mCurrentTask.legacy = legacy;
int gid = mCurrentTask.galleryBase.gid;
// Fill download update info
mUpdateInfo.legacy = legacy;
// Notify
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_DOWNLOAD,
makeMix(gid, OPS_UPDATE));
// Listener
if (mDownloadUpdateListener != null) {
mDownloadUpdateListener.onFinish(mUpdateInfo);
}
// Update to DB
DBUtils.updateDownloadInfo(mCurrentTask);
// Release
releaseCurrent();
}
@Override
public void onGetPages(int pages) {
if (mCurrentTask == null) {
return;
}
mCurrentTask.total = pages;
int gid = mCurrentTask.galleryBase.gid;
// Fill download update info
mUpdateInfo.pages = pages;
// Notify
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_DOWNLOAD,
makeMix(gid, OPS_UPDATE));
// Listener
if (mDownloadUpdateListener != null) {
mDownloadUpdateListener.onGetPages(mUpdateInfo);
}
}
@Override
public void onSpiderStart(int index, long totalSize) {
}
@Override
public void onSpiderPage(int index, long receivedSize, long singleReceivedSize) {
mReceived += singleReceivedSize;
}
@Override
public void onSpiderSucceed(int index) {
// TODO force request may onSpiderSucceed more than once
if (mCurrentTask == null) {
return;
}
mCurrentTask.download++;
int gid = mCurrentTask.galleryBase.gid;
// Fill download update info
mUpdateInfo.downloadedPages++;
// Notify
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_DOWNLOAD,
makeMix(gid, OPS_UPDATE));
// Listener
if (mDownloadUpdateListener != null) {
mDownloadUpdateListener.onDownloadPage(mUpdateInfo);
}
}
@Override
public void onSpiderFailed(int index, Exception e) {
}
@Override
public void onSpiderCancelled(int index) {
}
@Override
public void onGetImage(int index, Object obj) {
}
}
class SpeedReminder implements Runnable {
private boolean mStop = true;
public void start() {
if (mStop) {
mStop = false;
SimpleHandler.getInstance().post(this);
}
}
public void stop() {
if (!mStop) {
mStop = true;
SimpleHandler.getInstance().removeCallbacks(this);
}
}
@Override
public void run() {
if (mCurrentTask != null && mCurrentTask.total >= 0) {
long speed = mDownloadSpiderListener.getSpeed();
// Fill download update info
mUpdateInfo.speed = speed;
mCurrentTask.speed = speed;
if (speed != -1) {
// Notify
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_DOWNLOAD,
makeMix(mCurrentTask.galleryBase.gid, OPS_UPDATE));
// Listener
if (mDownloadUpdateListener != null) {
mDownloadUpdateListener.onDownload(mUpdateInfo);
}
}
}
if (!mStop) {
SimpleHandler.getInstance().postDelayed(this, 2000);
}
}
}
public interface DownloadUpdateListener {
void onStart(DownloadUpdateInfo info);
void onGetPages(DownloadUpdateInfo info);
// Update download pages
void onDownloadPage(DownloadUpdateInfo info);
// Update speed
void onDownload(DownloadUpdateInfo info);
void onFinish(DownloadUpdateInfo info);
void onCancel(DownloadUpdateInfo info);
}
public class DownloadUpdateInfo {
public int gid;
public String title;
public int pages;
public int downloadedPages;
public long speed;
public int legacy;
}
}
@@ -0,0 +1,273 @@
/*
* Copyright 2015 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.service;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.data.GalleryBase;
import com.hippo.yorozuya.FileUtils;
public class DownloadService extends Service implements DownloadManager.DownloadUpdateListener {
private static final String TAG = DownloadService.class.getSimpleName();
public static final String KEY_GALLERY_BASE = "gallery_base";
public static final String KEY_TAG = "tag";
public static final String KEY_GID = "gid";
public static final String ACTION_START = "com.hippo.ehviewer.service.DownloadService.START";
public static final String ACTION_START_ALL = "com.hippo.ehviewer.service.DownloadService.START_ALL";
public static final String ACTION_STOP = "com.hippo.ehviewer.service.DownloadService.STOP";
public static final String ACTION_STOP_CURRENT = "com.hippo.ehviewer.service.DownloadService.STOP_CURRENT";
public static final String ACTION_STOP_ALL = "com.hippo.ehviewer.service.DownloadService.STOP_ALL";
public static final String ACTION_DELETE = "com.hippo.ehviewer.service.DownloadService.DELETE";
public static final String ACTION_CLEAR = "com.hippo.ehviewer.service.DownloadService.CLEAR";
private static final int NOTIFY_ID_DOWNLOADING = -1;
private static final int NOTIFY_ID_DOWNLOAD = -2;
private NotificationManager mNotifyManager;
private NotificationCompat.Builder mDownloadingBuilder;
private NotificationCompat.Builder mDownloadedBuilder;
private static int sSucceeCount = 0;
private static int sFailedCount = 0;
private DownloadManager mDownloadManager;
@Override
public void onCreate() {
super.onCreate();
mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mDownloadManager = DownloadManager.getInstance();
mDownloadManager.setDownloadUpdateListener(this);
}
@Override
public void onDestroy() {
super.onDestroy();
mDownloadManager.setDownloadUpdateListener(null);
mDownloadManager = null;
mNotifyManager = null;
mDownloadingBuilder = null;
mDownloadedBuilder = null;
}
@Override
public @Nullable IBinder onBind(Intent intent) {
throw new IllegalStateException("No bindService");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
handleIntent(intent);
return START_STICKY;
}
private void handleIntent(Intent intent) {
String action = null;
if (intent != null) {
action = intent.getAction();
}
boolean checkStop = false;
if (ACTION_START.equals(action)) {
GalleryBase gb = intent.getParcelableExtra(KEY_GALLERY_BASE);
String tag = intent.getStringExtra(KEY_TAG);
if (gb != null) {
mDownloadManager.startDownload(gb, tag);
}
} else if (ACTION_START_ALL.equals(action)) {
mDownloadManager.startAllDownload();
} else if (ACTION_STOP.equals(action)) {
int gid = intent.getIntExtra(KEY_GID, 0);
if (gid != 0) {
mDownloadManager.stopDownload(gid);
}
checkStop = true;
} else if (ACTION_STOP_CURRENT.equals(action)) {
mDownloadManager.stopCurrentDownload();
checkStop = true;
} else if (ACTION_STOP_ALL.equals(action)) {
mDownloadManager.stopAllDownload();
checkStop = true;
} else if (ACTION_DELETE.equals(action)) {
// TODO
} else if (ACTION_CLEAR.equals(action)) {
sSucceeCount = 0;
sFailedCount = 0;
} else {
checkStop = true;
}
if (checkStop) {
if (!mDownloadManager.isDownloading()) {
stopForeground(true);
stopSelf();
}
}
}
private void ensureDownloadingNotification() {
if (mDownloadingBuilder != null) {
return;
}
mDownloadingBuilder = new NotificationCompat.Builder(getApplicationContext());
mDownloadingBuilder.setSmallIcon(android.R.drawable.stat_sys_download);
mDownloadingBuilder.setOngoing(true).setAutoCancel(false);
// Add action
Intent stopIntent = new Intent(this, DownloadService.class);
stopIntent.setAction(ACTION_STOP_CURRENT);
PendingIntent piStop = PendingIntent.getService(this, 0, stopIntent, 0);
mDownloadingBuilder.addAction(R.drawable.ic_pause_x24, getString(R.string.stop), piStop);
Intent stopAllIntent = new Intent(this, DownloadService.class);
stopAllIntent.setAction(ACTION_STOP_ALL);
PendingIntent piStopAll = PendingIntent.getService(this, 0, stopAllIntent, 0);
mDownloadingBuilder.addAction(R.drawable.ic_close_x24, getString(R.string.stop_all), piStopAll);
}
private void ensureDownloadedNotification() {
if (mDownloadedBuilder != null) {
return;
}
Intent clearIntent = new Intent(this, DownloadService.class);
clearIntent.setAction(ACTION_CLEAR);
PendingIntent piClear = PendingIntent.getService(this, 0, clearIntent, 0);
mDownloadedBuilder = new NotificationCompat.Builder(getApplicationContext());
mDownloadedBuilder.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setDeleteIntent(piClear).setOngoing(false).setAutoCancel(true);
}
private void setPendingIntent() {
// TODO
}
@Override
public void onStart(DownloadManager.DownloadUpdateInfo info) {
ensureDownloadingNotification();
setPendingIntent();
mDownloadingBuilder.setContentTitle(info.title)
.setContentText(null)
.setProgress(0, 0, true);
startForeground(NOTIFY_ID_DOWNLOADING, mDownloadingBuilder.build());
}
@Override
public void onGetPages(DownloadManager.DownloadUpdateInfo info) {
ensureDownloadingNotification();
setPendingIntent();
mDownloadingBuilder.setContentTitle(info.title)
.setContentText(null)
.setProgress(info.pages, info.downloadedPages, false);
startForeground(NOTIFY_ID_DOWNLOADING, mDownloadingBuilder.build());
}
@Override
public void onDownloadPage(DownloadManager.DownloadUpdateInfo info) {
ensureDownloadingNotification();
setPendingIntent();
String text = FileUtils.humanReadableByteCount(info.speed, false) + "/S";
mDownloadingBuilder.setContentTitle(info.title)
.setContentText(text)
.setProgress(info.pages, info.downloadedPages, false);
startForeground(NOTIFY_ID_DOWNLOADING, mDownloadingBuilder.build());
}
@Override
public void onDownload(DownloadManager.DownloadUpdateInfo info) {
ensureDownloadingNotification();
setPendingIntent();
String text = FileUtils.humanReadableByteCount(info.speed, false) + "/S";
mDownloadingBuilder.setContentTitle(info.title)
.setContentText(text)
.setProgress(info.pages, info.downloadedPages, false);
startForeground(NOTIFY_ID_DOWNLOADING, mDownloadingBuilder.build());
}
@Override
public void onFinish(DownloadManager.DownloadUpdateInfo info) {
mNotifyManager.cancel(NOTIFY_ID_DOWNLOADING);
ensureDownloadedNotification();
if (info.legacy == 0) {
sSucceeCount++;
} else {
sFailedCount++;
}
if (sSucceeCount + sFailedCount == 1) {
mDownloadedBuilder.setContentTitle(info.title);
if (sSucceeCount == 1) {
mDownloadedBuilder.setContentText(getResources().getString(R.string.successfully_downloaded));
} else {
mDownloadedBuilder.setContentText(getResources().getString(R.string.incompletely_downloaded));
}
} else {
mDownloadedBuilder.setContentTitle(getResources().getString(R.string.downloading_complete));
StringBuilder sb = new StringBuilder();
if (sSucceeCount != 0) {
sb.append(getResources().getQuantityString(R.plurals.item_successfully_downloaded, sSucceeCount, sSucceeCount));
}
if (sFailedCount != 0) {
if (sSucceeCount != 0) {
sb.append("\n");
}
sb.append(getResources().getQuantityString(R.plurals.item_incompletely_downloaded, sFailedCount, sFailedCount));
}
mDownloadedBuilder.setContentText(sb.toString());
}
mNotifyManager.notify(NOTIFY_ID_DOWNLOAD, mDownloadedBuilder.build());
checkStopSelf();
}
@Override
public void onCancel(DownloadManager.DownloadUpdateInfo info) {
mNotifyManager.cancel(NOTIFY_ID_DOWNLOADING);
checkStopSelf();
}
private void checkStopSelf() {
if (!mDownloadManager.hasWaitingDownloadInfo()) {
stopForeground(true);
stopSelf();
}
}
}
@@ -28,6 +28,7 @@ import android.widget.AdapterView;
import com.hippo.ehviewer.Constants;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.ui.scene.DownloadScene;
import com.hippo.ehviewer.ui.scene.DrawerProvider;
import com.hippo.ehviewer.ui.scene.GalleryListScene;
import com.hippo.ehviewer.ui.scene.MainSettingsScene;
@@ -42,6 +43,7 @@ import com.hippo.scene.Scene;
import com.hippo.scene.SceneManager;
import com.hippo.scene.StageActivity;
import com.hippo.scene.StageLayout;
import com.hippo.unifile.UniFile;
import com.hippo.vectorold.content.VectorContext;
import com.hippo.widget.DrawerLayout;
import com.hippo.widget.DrawerListView;
@@ -88,9 +90,9 @@ public final class ContentActivity extends StageActivity
case DRAWER_LIST_FAVORITE:
break;
case DRAWER_LIST_DOWNLOAD:
startScene(DownloadScene.class, null, new OffsetCurtain(OffsetCurtain.DIRECTION_BOTTOM));
break;
case DRAWER_LIST_SETTINGS:
mDrawerLayout.closeDrawers();
startScene(MainSettingsScene.class, null, new OffsetCurtain(OffsetCurtain.DIRECTION_BOTTOM));
break;
}
@@ -171,6 +173,14 @@ public final class ContentActivity extends StageActivity
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_content);
// TODO check here ?
UniFile uniFile = Settings.getImageDownloadLocation();
if (uniFile != null) {
uniFile.createFile(".nomedia");
} else {
// TODO
}
addSceneStateListener(mSceneStateListener);
mResources = getResources();
@@ -26,7 +26,6 @@ import android.widget.TextView;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.SwipeDismissItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.decoration.SimpleListDividerDecorator;
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter;
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange;
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager;
@@ -39,6 +38,8 @@ import com.hippo.ehviewer.R;
import com.hippo.rippleold.RippleSalon;
import com.hippo.scene.AppbarScene;
import com.hippo.widget.recyclerview.EasyRecyclerView;
import com.hippo.widget.recyclerview.LinearDividerItemDecoration;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.ViewUtils;
import java.util.List;
@@ -99,7 +100,12 @@ public abstract class AbsDragSortScene extends AppbarScene {
mRecyclerView.setOnItemClickListener((EasyRecyclerView.OnItemClickListener) mAdapter);
// additional decorations
mRecyclerView.addItemDecoration(new SimpleListDividerDecorator(resources.getDrawable(R.drawable.list_divider), true));
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL,
resources.getColor(R.color.divider_light),
LayoutUtils.dp2pix(getStageActivity(), 1));
decoration.setOverlap(true);
mRecyclerView.addItemDecoration(decoration);
// additional selector
mRecyclerView.setSelector(RippleSalon.generateRippleDrawable(false));
@@ -16,11 +16,434 @@
package com.hippo.ehviewer.ui.scene;
import android.content.Intent;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.hippo.conaco.Conaco;
import com.hippo.ehviewer.Constants;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhCacheKeyFactory;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.data.DownloadInfo;
import com.hippo.ehviewer.client.data.GalleryBase;
import com.hippo.ehviewer.service.DownloadManager;
import com.hippo.ehviewer.service.DownloadService;
import com.hippo.ehviewer.ui.ContentActivity;
import com.hippo.ehviewer.util.EhUtils;
import com.hippo.ehviewer.widget.LoadImageView;
import com.hippo.ehviewer.widget.SimpleRatingView;
import com.hippo.rippleold.RippleSalon;
import com.hippo.scene.Scene;
import com.hippo.widget.recyclerview.EasyRecyclerView;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.Messenger;
public class DownloadScene extends Scene {
import java.util.List;
public class DownloadScene extends Scene implements DrawerProvider,
EasyRecyclerView.OnItemClickListener, Messenger.Receiver {
private EasyRecyclerView mRecyclerView;
private DownloadAdapter mAdapter;
private List<DownloadInfo> mDownloadInfos;
private GidPositionMap mGidPositionMap;
private int mLastUpdateSize = -1;
@Override
protected void onInit() {
super.onInit();
mDownloadInfos = DownloadManager.getInstance().getDownloadList(null);
mGidPositionMap = new GidPositionMap();
Messenger.getInstance().register(Constants.MESSENGER_ID_UPDATE_DOWNLOAD, this);
}
@Override
protected void onDie() {
super.onDie();
Messenger.getInstance().unregister(Constants.MESSENGER_ID_UPDATE_DOWNLOAD, this);
}
@Override
protected void onCreate(boolean rebirth) {
super.onCreate(rebirth);
setContentView(R.layout.scene_download);
((ContentActivity) getStageActivity()).setDrawerListActivatedPosition(ContentActivity.DRAWER_LIST_DOWNLOAD);
mRecyclerView = (EasyRecyclerView) findViewById(R.id.recycler_view);
mAdapter = new DownloadAdapter();
mAdapter.setHasStableIds(true);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getStageActivity()));
mRecyclerView.setOnItemClickListener(this);
mRecyclerView.setHasFixedSize(true);
}
@Override
protected void onResume() {
super.onResume();
((ContentActivity) getStageActivity()).setDrawerListActivatedPosition(ContentActivity.DRAWER_LIST_DOWNLOAD);
}
@Override
public boolean showLeftDrawer() {
return true;
}
@Override
public void bindRightDrawer(ContentActivity activity) {
// TODO
activity.clearRightDrawerView();
}
@Override
public boolean onItemClick(EasyRecyclerView parent, View view, int position, long id) {
return false;
}
private View.OnClickListener mStartListener = new View.OnClickListener() {
private int getAdapterPosition(View startView) {
EasyRecyclerView recyclerView = mRecyclerView;
for (int i = 0, n = recyclerView.getChildCount(); i < n; i++) {
View child = recyclerView.getChildAt(i);
DownloadHolder holder = (DownloadHolder) recyclerView.getChildViewHolder(child);
if (startView == holder.start) {
return holder.getAdapterPosition();
}
}
return -1;
}
@Override
public void onClick(View v) {
int position = getAdapterPosition(v);
if (position >= 0) {
// Add to cache
DownloadInfo info = mDownloadInfos.get(position);
mGidPositionMap.add(info.galleryBase.gid, position);
Intent intent = new Intent(getStageActivity(), DownloadService.class);
intent.setAction(DownloadService.ACTION_START);
intent.putExtra(DownloadService.KEY_GALLERY_BASE, info.galleryBase);
getStageActivity().startService(intent);
}
}
};
private View.OnClickListener mStopListener = new View.OnClickListener() {
private int getAdapterPosition(View stopView) {
EasyRecyclerView recyclerView = mRecyclerView;
for (int i = 0, n = recyclerView.getChildCount(); i < n; i++) {
View child = recyclerView.getChildAt(i);
DownloadHolder holder = (DownloadHolder) recyclerView.getChildViewHolder(child);
if (stopView == holder.stop) {
return holder.getAdapterPosition();
}
}
return -1;
}
@Override
public void onClick(View v) {
int position = getAdapterPosition(v);
if (position >= 0) {
// Add to cache
DownloadInfo info = mDownloadInfos.get(position);
mGidPositionMap.add(info.galleryBase.gid, position);
Intent intent = new Intent(getStageActivity(), DownloadService.class);
intent.setAction(DownloadService.ACTION_STOP);
intent.putExtra(DownloadService.KEY_GID, info.galleryBase.gid);
getStageActivity().startService(intent);
}
}
};
private View.OnClickListener mDeleteListener = new View.OnClickListener() {
private int getAdapterPosition(View deleteView) {
EasyRecyclerView recyclerView = mRecyclerView;
for (int i = 0, n = recyclerView.getChildCount(); i < n; i++) {
View child = recyclerView.getChildAt(i);
DownloadHolder holder = (DownloadHolder) recyclerView.getChildViewHolder(child);
if (deleteView == holder.delete) {
return holder.getAdapterPosition();
}
}
return -1;
}
@Override
public void onClick(View v) {
int position = getAdapterPosition(v);
if (position >= 0) {
// Add to cache
DownloadInfo info = mDownloadInfos.get(position);
mGidPositionMap.add(info.galleryBase.gid, position);
Intent intent = new Intent(getStageActivity(), DownloadService.class);
intent.setAction(DownloadService.ACTION_DELETE);
intent.putExtra(DownloadService.KEY_GID, info.galleryBase.gid);
getStageActivity().startService(intent);
}
}
};
private int getPositionForGid(int gid) {
if (mDownloadInfos == null) {
return -1;
}
int position = mGidPositionMap.getPosition(gid);
if (position == -1) {
for (int i = 0, n = mDownloadInfos.size(); i < n; i++) {
DownloadInfo info = mDownloadInfos.get(i);
if (info.galleryBase.gid == gid) {
position = i;
break;
}
}
}
return position;
}
@Override
public void onReceive(int id, Object obj) {
if (id == Constants.MESSENGER_ID_UPDATE_DOWNLOAD) {
if (!(obj instanceof Integer)) {
return;
}
if (mAdapter == null || mDownloadInfos == null) {
return;
}
int mix = (int) obj;
int ops = DownloadManager.getOps(mix);
int gid = DownloadManager.getGid(mix);
if (ops == DownloadManager.OPS_ALL_CHANGE) {
mAdapter.notifyDataSetChanged();
return;
}
int position = getPositionForGid(gid);
if (position == -1 && mLastUpdateSize != mAdapter.getItemCount()) {
// Can't get position, notifyDataSetChanged for safe
mAdapter.notifyDataSetChanged();
return;
}
if (ops == DownloadManager.OPS_ADD) {
mAdapter.notifyItemInserted(position);
} else if (ops == DownloadManager.OPS_REMOVE) {
mAdapter.notifyItemRemoved(position);
} else if (ops == DownloadManager.OPS_UPDATE) {
RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForAdapterPosition(position);
if (holder != null) {
bindHolder((DownloadHolder) holder, mDownloadInfos.get(position));
}
}
}
}
private class DownloadHolder extends RecyclerView.ViewHolder {
public LoadImageView thumb;
public TextView title;
public TextView uploader;
public SimpleRatingView rating;
public TextView category;
public View start;
public View stop;
public View delete;
public TextView state;
public ProgressBar progressBar;
public TextView percent;
public TextView speed;
public DownloadHolder(View itemView) {
super(itemView);
thumb = (LoadImageView) itemView.findViewById(R.id.thumb);
title = (TextView) itemView.findViewById(R.id.title);
uploader = (TextView) itemView.findViewById(R.id.uploader);
rating = (SimpleRatingView) itemView.findViewById(R.id.rating);
category = (TextView) itemView.findViewById(R.id.category);
start = itemView.findViewById(R.id.start);
stop = itemView.findViewById(R.id.stop);
delete = itemView.findViewById(R.id.delete);
state = (TextView) itemView.findViewById(R.id.state);
progressBar = (ProgressBar) itemView.findViewById(R.id.progress_bar);
percent = (TextView) itemView.findViewById(R.id.percent);
speed = (TextView) itemView.findViewById(R.id.speed);
RippleSalon.addRipple(start, false);
RippleSalon.addRipple(stop, false);
RippleSalon.addRipple(delete, false);
start.setOnClickListener(mStartListener);
stop.setOnClickListener(mStopListener);
delete.setOnClickListener(mDeleteListener);
}
}
private void bindState(DownloadHolder holder, DownloadInfo info, String state) {
GalleryBase galleryBase = info.galleryBase;
EhApplication.getConaco(getStageActivity()).load(holder.thumb,
EhCacheKeyFactory.getThumbKey(galleryBase.gid), galleryBase.thumb);
holder.title.setText(EhUtils.getSuitableTitle(galleryBase));
holder.uploader.setVisibility(View.VISIBLE);
holder.rating.setVisibility(View.VISIBLE);
holder.category.setVisibility(View.VISIBLE);
holder.state.setVisibility(View.VISIBLE);
holder.uploader.setText(galleryBase.uploader);
holder.rating.setRating(galleryBase.rating);
TextView category = holder.category;
String newCategoryText = EhUtils.getCategory(galleryBase.category);
if (!newCategoryText.equals(category.getText())) {
category.setText(newCategoryText);
category.setBackgroundColor(EhUtils.getCategoryColor(galleryBase.category));
}
holder.state.setText(state);
holder.progressBar.setVisibility(View.INVISIBLE);
holder.percent.setVisibility(View.INVISIBLE);
holder.speed.setVisibility(View.INVISIBLE);
}
private void bindProgress(DownloadHolder holder, DownloadInfo info) {
GalleryBase galleryBase = info.galleryBase;
EhApplication.getConaco(getStageActivity()).load(holder.thumb,
EhCacheKeyFactory.getThumbKey(info.galleryBase.gid), galleryBase.thumb);
holder.title.setText(EhUtils.getSuitableTitle(galleryBase));
holder.uploader.setVisibility(View.INVISIBLE);
holder.rating.setVisibility(View.INVISIBLE);
holder.category.setVisibility(View.INVISIBLE);
holder.state.setVisibility(View.INVISIBLE);
holder.progressBar.setVisibility(View.VISIBLE);
if (info.total <= 0 || info.speed < 0) {
holder.progressBar.setIndeterminate(true);
holder.percent.setVisibility(View.INVISIBLE);
holder.speed.setVisibility(View.INVISIBLE);
} else {
holder.progressBar.setIndeterminate(false);
holder.progressBar.setMax(info.total);
holder.progressBar.setProgress(info.download);
holder.percent.setVisibility(View.VISIBLE);
holder.speed.setVisibility(View.VISIBLE);
holder.percent.setText(info.download + "/" + info.total);
holder.speed.setText(FileUtils.humanReadableByteCount(info.speed, false) + "/S");
}
}
private void bindHolder(DownloadHolder holder, DownloadInfo info) {
switch (info.state) {
case DownloadInfo.STATE_NONE:
default:
bindState(holder, info, getContext().getResources().getString(R.string.download_state_none));
break;
case DownloadInfo.STATE_WAIT:
bindState(holder, info, getContext().getResources().getString(R.string.download_state_wait));
break;
case DownloadInfo.STATE_DOWNLOAD:
bindProgress(holder, info);
break;
case DownloadInfo.STATE_FINISH:
if (info.legacy == 0) {
bindState(holder, info, getContext().getResources().getString(R.string.download_state_done));
} else if (info.legacy > 0 && info.legacy != Integer.MAX_VALUE) {
bindState(holder, info, getContext().getResources().getString(R.string.download_state_incomplete, info.legacy));
} else {
bindState(holder, info, getContext().getResources().getString(R.string.download_state_incomplete));
}
break;
}
}
private class DownloadAdapter extends RecyclerView.Adapter<DownloadHolder> {
Conaco mConaco;
public DownloadAdapter() {
mConaco = EhApplication.getConaco(getStageActivity());
}
@Override
public DownloadHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new DownloadHolder(getStageActivity().getLayoutInflater().inflate(R.layout.item_download, parent, false));
}
@Override
public void onBindViewHolder(DownloadHolder holder, int position) {
DownloadInfo info = mDownloadInfos.get(position);
bindHolder(holder, info);
}
@Override
public long getItemId(int position) {
return mDownloadInfos.get(position).galleryBase.gid;
}
@Override
public int getItemCount() {
mLastUpdateSize = mDownloadInfos != null ? mDownloadInfos.size() : 0;
return mLastUpdateSize;
}
}
private static class GidPositionMap {
private static final int SIZE = 12;
private int[] mGids = new int[SIZE];
private int[] mPosition = new int[SIZE];
private int mStartIndex = 0;
private int mSize = 0;
public void add(int gid, int position) {
int index = (mStartIndex + mSize) % SIZE;
mGids[index] = gid;
mPosition[index] = position;
// Update start index size and
mSize++;
if (mSize > SIZE) {
mStartIndex = (mStartIndex + mSize) % SIZE;
mSize = Math.min(mSize, SIZE);
}
}
public void clear() {
mSize = 0;
}
public int getPosition(int gid) {
for (int i = 0; i < mSize; i++) {
int index = (mStartIndex + i) % SIZE;
if (gid == mGids[index]) {
return mPosition[index];
}
}
return -1;
}
}
}
@@ -46,6 +46,7 @@ import com.hippo.ehviewer.client.data.GalleryBase;
import com.hippo.ehviewer.client.data.GalleryDetail;
import com.hippo.ehviewer.client.data.PreviewSet;
import com.hippo.ehviewer.client.data.TagGroup;
import com.hippo.ehviewer.service.DownloadService;
import com.hippo.ehviewer.ui.ContentActivity;
import com.hippo.ehviewer.ui.GalleryActivity;
import com.hippo.ehviewer.util.EhUtils;
@@ -558,7 +559,10 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener,
requestGalleryDetail(mGid, mToken);
}
} else if (mDownload == v) {
// TODO
Intent intent = new Intent(getStageActivity(), DownloadService.class);
intent.setAction(DownloadService.ACTION_START);
intent.putExtra(DownloadService.KEY_GALLERY_BASE, mGalleryDetail);
getStageActivity().startService(intent);
} else if (mRead == v) {
GalleryBase galleryBase = mGalleryDetail;
if (galleryBase == null) {
@@ -75,7 +75,9 @@ import com.hippo.util.AnimationUtils;
import com.hippo.widget.FabLayout;
import com.hippo.widget.FloatingActionButton;
import com.hippo.widget.recyclerview.EasyRecyclerView;
import com.hippo.widget.recyclerview.LinearDividerItemDecoration;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.Messenger;
import com.hippo.yorozuya.SimpleHandler;
@@ -303,27 +305,6 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe
mSearchBarOriginalTop = lp.topMargin;
mSearchBarOriginalBottom = lp.topMargin + mSearchBar.getMeasuredHeight();
/*
mSearchBar.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
ViewUtils.removeOnGlobalLayoutListener(mSearchBar.getViewTreeObserver(), this);
mSearchBarOriginalTop = mSearchBar.getTop();
mSearchBarOriginalBottom = mSearchBar.getBottom();
int fitPaddingTop = mSearchBar.getHeight() + mResources.getDimensionPixelOffset(R.dimen.search_bar_padding_vertical);
mContentLayout.setFitPaddingTop(fitPaddingTop);
mSearchLayout.setFitPaddingTop(fitPaddingTop);
Log.d("TAG", "fitPaddingTop = " + fitPaddingTop);
Log.d("TAG", "mSearchBarOriginalTop = " + mSearchBarOriginalTop);
Log.d("TAG", "mSearchBarOriginalBottom = " + mSearchBarOriginalBottom);
}
});
*/
// Content Layout
// onRebirth may create new GalleryListHelper
if (mGalleryListHelper == null) {
@@ -365,6 +346,12 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe
mQuickSearchView.setOnItemClickListener(new QuickSearchClickListener());
mQuickSearchView.hasFixedSize();
mQuickSearchView.setClipToPadding(false);
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL,
mResources.getColor(R.color.divider_light),
LayoutUtils.dp2pix(getStageActivity(), 1));
decoration.setOverlap(true);
mQuickSearchView.addItemDecoration(decoration);
// When scene start
if (!rebirth) {
@@ -1279,6 +1266,9 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe
showSearchBar(true);
mGalleryListHelper.refresh();
((ContentActivity) getStageActivity()).closeDrawers();
mDrawerListActivatedPosition = ContentActivity.DRAWER_LIST_NONE;
mActivity.setDrawerListActivatedPosition(ContentActivity.DRAWER_LIST_NONE);
return true;
}
}
@@ -196,7 +196,7 @@ public class ModifyQuickSearchScene extends AppbarScene implements SearchLayout.
// New name
mQuickSearch.name = mNewName;
}
DBUtils.modifyQuickSearch(mQuickSearch);
DBUtils.updateQuickSearch(mQuickSearch);
Messenger.getInstance().notify(Constants.MESSENGER_ID_UPDATE_QUICK_SEARCH, null);
if (newName) {
@@ -20,6 +20,7 @@ import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import com.hippo.ehviewer.client.data.DownloadInfo;
import com.hippo.ehviewer.client.data.GalleryBase;
import com.hippo.ehviewer.client.data.ListUrlBuilder;
import com.hippo.ehviewer.client.data.QuickSearch;
@@ -27,6 +28,10 @@ import com.hippo.ehviewer.dao.DaoMaster;
import com.hippo.ehviewer.dao.DaoSession;
import com.hippo.ehviewer.dao.DirnameObj;
import com.hippo.ehviewer.dao.DirnameObjDao;
import com.hippo.ehviewer.dao.DownloadInfoObj;
import com.hippo.ehviewer.dao.DownloadInfoObjDao;
import com.hippo.ehviewer.dao.DownloadTagObj;
import com.hippo.ehviewer.dao.DownloadTagObjDao;
import com.hippo.ehviewer.dao.GalleryBaseObj;
import com.hippo.ehviewer.dao.GalleryBaseObjDao;
import com.hippo.ehviewer.dao.QuickSearchObj;
@@ -84,6 +89,26 @@ public class DBUtils {
}
}
public static GalleryBase getGalleryBase(int gid) {
GalleryBaseObjDao dao = sDaoSession.getGalleryBaseObjDao();
GalleryBaseObj obj = dao.load((long) gid);
if (obj != null) {
GalleryBase galleryBase = new GalleryBase();
galleryBase.gid = (int) (long) obj.getGid();
galleryBase.token = obj.getToken();
galleryBase.title = obj.getTitle();
galleryBase.titleJpn = obj.getTitleJpn();
galleryBase.thumb = obj.getThumb();
galleryBase.category = obj.getCategory();
galleryBase.posted = obj.getPosted();
galleryBase.uploader = obj.getUploader();
galleryBase.rating = obj.getRating();
return galleryBase;
} else {
return null;
}
}
public static String getDirname(int gid) {
DirnameObjDao dirnameObjDao = sDaoSession.getDirnameObjDao();
DirnameObj dirnameObj = dirnameObjDao.load((long) gid);
@@ -147,7 +172,7 @@ public class DBUtils {
dao.update(to);
}
public static void modifyQuickSearch(QuickSearch quickSearch) {
public static void updateQuickSearch(QuickSearch quickSearch) {
QuickSearchObjDao dao = sDaoSession.getQuickSearchObjDao();
QuickSearchObj obj = dao.load(quickSearch.id);
obj.setName(quickSearch.name);
@@ -172,4 +197,51 @@ public class DBUtils {
}
return result;
}
public static List<DownloadInfo> getAllDownloadInfo() {
DownloadInfoObjDao dao = sDaoSession.getDownloadInfoObjDao();
List<DownloadInfoObj> list = dao.queryBuilder().orderAsc(DownloadInfoObjDao.Properties.Time).list();
List<DownloadInfo> result = new ArrayList<>(list.size());
for (DownloadInfoObj obj : list) {
result.add(DownloadInfo.fromDownloadInfoObj(obj));
}
return result;
}
public static void addDownloadInfo(DownloadInfo downloadInfo) {
DownloadInfoObj obj = new DownloadInfoObj();
obj.setGid((long) downloadInfo.galleryBase.gid);
obj.setTag(downloadInfo.tag);
obj.setState(downloadInfo.state);
obj.setLegacy(downloadInfo.legacy);
obj.setTime(System.currentTimeMillis());
sDaoSession.getDownloadInfoObjDao().insert(obj);
addGalleryBase(downloadInfo.galleryBase);
}
public static void updateDownloadInfo(DownloadInfo downloadInfo) {
DownloadInfoObjDao dao = sDaoSession.getDownloadInfoObjDao();
DownloadInfoObj obj = dao.load((long) downloadInfo.galleryBase.gid);
obj.setState(downloadInfo.state);
obj.setLegacy(downloadInfo.legacy);
dao.update(obj);
}
public static List<String> getAllDownloadTag() {
DownloadTagObjDao dao = sDaoSession.getDownloadTagObjDao();
List<DownloadTagObj> list = dao.queryBuilder().list();
List<String> result = new ArrayList<>(list.size());
for (DownloadTagObj obj : list) {
result.add(obj.getTag());
}
return result;
}
public static void addDownloadTag(String tag) {
DownloadTagObj obj = new DownloadTagObj();
obj.setTag(tag);
sDaoSession.getDownloadTagObjDao().insert(obj);
}
}
@@ -51,7 +51,7 @@ public class DownloadClient {
out.write(data, 0, bytesRead);
receivedSize += bytesRead;
if (listener != null) {
listener.onDonwlad(receivedSize);
listener.onDonwlad(receivedSize, bytesRead);
}
}
@@ -207,7 +207,7 @@ public class DownloadClient {
}
@Override
public void onDonwlad(long receivedSize) {
public void onDonwlad(long receivedSize, long singleReceivedSize) {
}
@Override
@@ -227,7 +227,7 @@ public class DownloadClient {
void onConnect(long totalSize);
void onDonwlad(long receivedSize);
void onDonwlad(long receivedSize, long singleReceivedSize);
void onFailed(Exception e);
@@ -61,7 +61,7 @@ public class DownloadRequest {
public void cancel() {
if (mHttpRequest != null) {
mHttpRequest.disconnect();
mHttpRequest.cancel();
}
}
}
@@ -22,6 +22,7 @@ import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
@@ -29,6 +30,7 @@ import android.view.ViewParent;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import com.hippo.ehviewer.client.data.DownloadInfo;
import com.hippo.yorozuya.AssertUtils;
/**
@@ -100,6 +102,13 @@ public abstract class Scene {
return stageActivity;
}
/**
* Aliases of {@link #getStageActivity()}
*/
public Context getContext() {
return getStageActivity();
}
public int getLaunchMode() {
return LAUNCH_MODE_STANDARD;
}
@@ -24,6 +24,7 @@ import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
// Add overlap
/**
* Only work for {@link android.support.v7.widget.LinearLayoutManager}.<br>
* Show divider between item, just like
@@ -44,6 +45,8 @@ public class LinearDividerItemDecoration extends RecyclerView.ItemDecoration {
private int mPaddingStart = 0;
private int mPaddingEnd = 0;
private boolean mOverlap = false;
private ShowDividerHelper mShowDividerHelper;
public LinearDividerItemDecoration(int orientation, int color, int thickness) {
@@ -95,9 +98,23 @@ public class LinearDividerItemDecoration extends RecyclerView.ItemDecoration {
mPaddingEnd = paddingEnd;
}
public void setOverlap(boolean overlap) {
mOverlap = overlap;
}
@Override
public void getItemOffsets(Rect outRect, View view,
RecyclerView parent, RecyclerView.State state) {
if (parent.getAdapter() == null) {
outRect.set(0, 0, 0, 0);
return;
}
if (mOverlap) {
outRect.set(0, 0, 0, 0);
return;
}
final int position = parent.getChildLayoutPosition(view);
final int itemCount = parent.getAdapter().getItemCount();
@@ -147,6 +164,7 @@ public class LinearDividerItemDecoration extends RecyclerView.ItemDecoration {
}
int itemCount = adapter.getItemCount();
boolean overlap = mOverlap;
if (mOrientation == VERTICAL) {
final boolean isRtl = ViewCompat.getLayoutDirection(parent) == ViewCompat.LAYOUT_DIRECTION_RTL;
@@ -176,7 +194,10 @@ public class LinearDividerItemDecoration extends RecyclerView.ItemDecoration {
show = position != itemCount - 1 || mShowLastDivider;
}
if (show) {
final int top = child.getBottom() + lp.bottomMargin;
int top = child.getBottom() + lp.bottomMargin;
if (overlap) {
top -= mThickness;
}
final int bottom = top + mThickness;
mRect.set(left, top, right, bottom);
c.drawRect(mRect, mPaint);
@@ -189,7 +210,10 @@ public class LinearDividerItemDecoration extends RecyclerView.ItemDecoration {
show = mShowFirstDivider;
}
if (show) {
final int bottom = child.getTop() + lp.topMargin;
int bottom = child.getTop() + lp.topMargin;
if (overlap) {
bottom += mThickness;
}
final int top = bottom - mThickness;
mRect.set(left, top, right, bottom);
c.drawRect(mRect, mPaint);
@@ -213,7 +237,10 @@ public class LinearDividerItemDecoration extends RecyclerView.ItemDecoration {
show = position != itemCount - 1 || mShowLastDivider;
}
if (show) {
final int left = child.getRight() + lp.rightMargin;
int left = child.getRight() + lp.rightMargin;
if (overlap) {
left -= mThickness;
}
final int right = left + mThickness;
mRect.set(left, top, right, bottom);
c.drawRect(mRect, mPaint);
@@ -226,7 +253,10 @@ public class LinearDividerItemDecoration extends RecyclerView.ItemDecoration {
show = mShowFirstDivider;
}
if (show) {
final int right = child.getLeft() + lp.leftMargin;
int right = child.getLeft() + lp.leftMargin;
if (overlap) {
right += mThickness;
}
final int left = right - mThickness;
mRect.set(left, top, right, bottom);
c.drawRect(mRect, mPaint);
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2015 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.
-->
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:auto="http://schemas.android.com/apk/res-auto"
tools:targetApi="21"
auto:width="24dp"
auto:height="24dp"
auto:viewportWidth="24"
auto:viewportHeight="24"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
auto:pathData="@string/pd_close"
auto:fillColor="@color/primary_drawable_light"
android:pathData="@string/pd_close"
android:fillColor="@color/primary_drawable_light"/>
</vector>
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2015 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.
-->
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:auto="http://schemas.android.com/apk/res-auto"
tools:targetApi="21"
auto:width="24dp"
auto:height="24dp"
auto:viewportWidth="24"
auto:viewportHeight="24"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
auto:pathData="@string/pd_pause"
auto:fillColor="@color/primary_drawable_light"
android:pathData="@string/pd_pause"
android:fillColor="@color/primary_drawable_light"/>
</vector>
+40
View File
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2015 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.
-->
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:auto="http://schemas.android.com/apk/res-auto"
tools:targetApi="21"
auto:width="24dp"
auto:height="24dp"
auto:viewportWidth="24"
auto:viewportHeight="24"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
auto:pathData="@string/pd_play"
auto:fillColor="@color/primary_drawable_light"
android:pathData="@string/pd_play"
android:fillColor="@color/primary_drawable_light"/>
</vector>
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2015 Haruki Hasegawa
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.
-->
<shape
android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android">
<size android:height="1dp"/>
<solid android:color="#20000000"/>
</shape>
+151
View File
@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2015 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.
-->
<com.hippo.cardsalon.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:auto="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
auto:cardElevation="4dp"
auto:forceNoElevation="true">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.hippo.ehviewer.widget.LoadImageView
android:id="@+id/thumb"
android:layout_width="@dimen/list_thumb_width"
android:layout_height="@dimen/list_thumb_height"
auto:retryType="click"/>
<TextView
android:id="@+id/title"
style="@style/CardTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="4dp"
android:layout_toRightOf="@id/thumb"/>
<TextView
android:id="@+id/uploader"
style="@style/CardMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/title"
android:layout_below="@id/title"
android:layout_marginTop="2dp"/>
<com.hippo.ehviewer.widget.SimpleRatingView
android:id="@+id/rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/title"
android:layout_below="@id/uploader"
android:layout_marginTop="2dp"/>
<TextView
android:id="@+id/category"
style="@style/CardMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/title"
android:layout_alignBottom="@id/thumb"
android:layout_marginBottom="8dp"
android:paddingBottom="2dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingTop="2dp"
android:textColor="@android:color/white"
android:textAllCaps="true"/>
<LinearLayout
android:id="@+id/actions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/thumb"
android:layout_alignParentRight="true"
android:layout_marginRight="8dp"
android:orientation="horizontal">
<com.hippo.widget.SimpleImageView
android:id="@+id/start"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="8dp"
android:src="@drawable/ic_play_x24"/>
<com.hippo.widget.SimpleImageView
android:id="@+id/stop"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:padding="8dp"
android:src="@drawable/ic_pause_x24"/>
<com.hippo.widget.SimpleImageView
android:id="@+id/delete"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="8dp"
android:src="@drawable/ic_close_x24"/>
</LinearLayout>
<TextView
android:id="@+id/state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/actions"
android:layout_marginBottom="4dp"
android:layout_alignParentRight="true"
android:layout_marginRight="16dp"
android:textColor="?attr/colorAccent"/>
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/title"
android:layout_marginRight="8dp"
android:layout_above="@id/actions"
android:indeterminate="false"/>
<TextView
android:id="@+id/percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/progress_bar"
android:layout_alignLeft="@id/title"
android:textSize="@dimen/text_super_small"/>
<TextView
android:id="@+id/speed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/progress_bar"
android:layout_alignParentRight="true"
android:layout_marginRight="8dp"
android:textSize="@dimen/text_super_small"/>
</RelativeLayout>
</com.hippo.cardsalon.CardView>
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2015 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.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:auto="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.hippo.widget.recyclerview.MonoRecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:clipChildren="false"
auto:space="@dimen/search_layout_space"
auto:spaceLeft="@dimen/search_layout_space_horizontal"
auto:spaceRight="@dimen/search_layout_space_horizontal"/>
</FrameLayout>
+3
View File
@@ -48,6 +48,9 @@
<string name="pd_url" translatable="false">M11.6,7v7.1c0,1-0.3,1.8-0.9,2.4c-0.6,0.6-1.5,1-2.5,1.1l-0.4,0c-1.1,0-2.1-0.3-2.7-0.9c-0.7-0.6-1-1.5-1-2.6V7h1.4v7.1c0,0.8,0.2,1.3,0.6,1.8c0.4,0.4,1,0.6,1.8,0.6c0.8,0,1.4-0.2,1.8-0.6c0.4-0.4,0.6-1,0.6-1.8V7H11.6M17.3,10.9c-0.2,0-0.4-0.1-0.7-0.1c-0.9,0-1.5,0.4-1.8,1.1v5.5h-1.3V9.7h1.3l0,0.9c0.4-0.7,1.1-1,1.9-1c0.3,0,0.5,0,0.6,0.1V10.9M20,17.5h-1.3V6.4H20V17.5z</string>
<string name="pd_book_open" translatable="false">M11,19V9A2,2 0 0,0 9,7H5V17H9A2,2 0 0,1 11,19M13,9V19A2,2 0 0,1 15,17H19V7H15A2,2 0 0,0 13,9M21,19H15A2,2 0 0,0 13,21H11A2,2 0 0,0 9,19H3V5H9A2,2 0 0,1 11,7H13A2,2 0 0,1 15,5H21V19Z</string>
<string name="pd_delete" translatable="false">M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z</string>
<string name="pd_play" translatable="false">M8,5.14V19.14L19,12.14L8,5.14Z</string>
<string name="pd_pause" translatable="false">M14,19.14H18V5.14H14M6,19.14H10V5.14H6V19.14Z</string>
<string name="pd_close" translatable="false">M19,7.1L16.9 5 12 9.9 7.1 5 5 7.1 9.9 12 5 16.9 7.1 19 12 14.1 16.9 19 19 16.9 14.1 12 z</string>
<string name="pd_slider_bubble" translatable="false">M13,0c7.2,0,13,5.9,13,13.2c0,3.7-2,7.6-7.3,12.9L13,32l-5.8-5.8C2,20.8,0,16.9,0,13.3C0,5.9,5.8,0,13,0z</string>
+21
View File
@@ -216,6 +216,27 @@
<string name="line_dean">\"What ?\"</string>
<string name="line_sam">\"I lose my panda …\"</string>
<!-- Download Server -->
<string name="download_server">Download server</string>
<string name="stop">Stop</string>
<string name="stop_all">Stop all</string>
<string name="successfully_downloaded">Successfully downloaded</string>
<string name="incompletely_downloaded">Incompletely downloaded</string>
<string name="downloading_complete">Downloading complete</string>
<plurals name="item_successfully_downloaded">
<item quantity="one">One item successfully downloaded</item>
<item quantity="other">%d items successfully downloaded</item>
</plurals>
<plurals name="item_incompletely_downloaded">
<item quantity="one">One item incompletely downloaded</item>
<item quantity="other">%d items incompletely downloaded</item>
</plurals>
<string name="download_state_none">IDLE</string>
<string name="download_state_wait">Waiting</string>
<string name="download_state_finish">Done</string>
<string name="download_state_done">Done</string>
<string name="download_state_incomplete">Incomplete</string>
<string name="keyword">Keyword</string>
<string name="search">Search</string>
<string name="retry">Retry</string>
+5
View File
@@ -97,6 +97,7 @@
<!-- Card -->
<style name="TextAppearance.CardTitle">
<item name="@android:textSize">@dimen/text_little_small</item>
<item name="@android:textColor">@color/primary_text_light</item>
</style>
<style name="CardTitle">
@@ -106,6 +107,10 @@
<item name="@android:lineSpacingExtra">0sp</item>
</style>
<style name="CardTitle.Download">
<item name="@android:maxLines">1</item>
</style>
<style name="TextAppearance.CardMessage">
<item name="@android:textColor">@color/secondary_text_light</item>
</style>
+1 -1
Submodule conaco updated: 6bd8937391...5c1fea7a11.
@@ -37,6 +37,7 @@ public class EhDaoGenerator {
addGalleryBase(schema);
addQuickSearch(schema);
addDownloadInfo(schema);
addDownloadTag(schema);
addDirname(schema);
addReadInfo(schema);
new DaoGenerator().generateAll(schema, OUT_DIR);
@@ -71,12 +72,20 @@ public class EhDaoGenerator {
}
public static void addDownloadInfo(Schema schema) {
Entity downloadInfo = schema.addEntity("DownloadInfoObj");
downloadInfo.setTableName("DOWNLOAD_INFO");
downloadInfo.addLongProperty("gid").primaryKey();
downloadInfo.addIntProperty("state");
downloadInfo.addIntProperty("legacy");
downloadInfo.addLongProperty("time");
Entity entity = schema.addEntity("DownloadInfoObj");
entity.setTableName("DOWNLOAD_INFO");
entity.addLongProperty("gid").primaryKey();
entity.addStringProperty("tag");
entity.addIntProperty("state");
entity.addIntProperty("legacy");
entity.addLongProperty("time");
}
public static void addDownloadTag(Schema schema) {
Entity entity = schema.addEntity("DownloadTagObj");
entity.setTableName("DOWNLOAD_TAG");
entity.addIdProperty();
entity.addStringProperty("tag");
}
public static void addDirname(Schema schema) {
+1 -1
Submodule hotspot updated: 04129925be...2783f8b9c0.
+1 -1
Submodule unifile updated: f068bc951b...60e3145f85.
Submodule yorozuya updated: cd4d1b981c...805f0c5f7a.