From 4a80ea7dcdeafee47ebfc7da7dd8349e61065c07 Mon Sep 17 00:00:00 2001 From: seven332 Date: Wed, 19 Aug 2015 22:20:03 +0800 Subject: [PATCH] Add download --- app/build.gradle | 10 +- app/src/main/AndroidManifest.xml | 5 + .../java/com/hippo/ehviewer/Constants.java | 4 + .../com/hippo/ehviewer/EhApplication.java | 2 + .../com/hippo/ehviewer/client/EhClient.java | 6 +- .../com/hippo/ehviewer/client/EhEngine.java | 14 +- .../ehviewer/client/data/DownloadInfo.java | 54 ++ .../hippo/ehviewer/gallery/GallerySpider.java | 67 +- .../hippo/ehviewer/gallery/ImageHandler.java | 31 +- .../hippo/ehviewer/gallery/SpiderInfo.java | 148 ++--- .../hippo/ehviewer/gallery/SpiderQueen.java | 218 ++++--- .../ehviewer/service/DownloadManager.java | 595 ++++++++++++++++++ .../ehviewer/service/DownloadService.java | 273 ++++++++ .../hippo/ehviewer/ui/ContentActivity.java | 12 +- .../ehviewer/ui/scene/AbsDragSortScene.java | 10 +- .../ehviewer/ui/scene/DownloadScene.java | 425 ++++++++++++- .../ehviewer/ui/scene/GalleryDetailScene.java | 6 +- .../ehviewer/ui/scene/GalleryListScene.java | 32 +- .../ui/scene/ModifyQuickSearchScene.java | 2 +- .../java/com/hippo/ehviewer/util/DBUtils.java | 74 ++- .../com/hippo/network/DownloadClient.java | 6 +- .../com/hippo/network/DownloadRequest.java | 2 +- app/src/main/java/com/hippo/scene/Scene.java | 9 + .../LinearDividerItemDecoration.java | 38 +- app/src/main/res/drawable/ic_close_x24.xml | 40 ++ app/src/main/res/drawable/ic_pause_x24.xml | 40 ++ app/src/main/res/drawable/ic_play_x24.xml | 40 ++ app/src/main/res/drawable/list_divider.xml | 22 - app/src/main/res/layout/item_download.xml | 151 +++++ app/src/main/res/layout/scene_download.xml | 33 + app/src/main/res/values/pathdata.xml | 3 + app/src/main/res/values/strings.xml | 21 + app/src/main/res/values/styles.xml | 5 + beerbelly | 2 +- cardsalon | 2 +- conaco | 2 +- .../com/hippo/ehviewer/EhDaoGenerator.java | 21 +- hotspot | 2 +- httpclient | 2 +- rippleold | 2 +- unifile | 2 +- vectorold | 2 +- yorozuya | 2 +- 43 files changed, 2167 insertions(+), 270 deletions(-) create mode 100644 app/src/main/java/com/hippo/ehviewer/client/data/DownloadInfo.java create mode 100644 app/src/main/java/com/hippo/ehviewer/service/DownloadManager.java create mode 100644 app/src/main/java/com/hippo/ehviewer/service/DownloadService.java create mode 100644 app/src/main/res/drawable/ic_close_x24.xml create mode 100644 app/src/main/res/drawable/ic_pause_x24.xml create mode 100644 app/src/main/res/drawable/ic_play_x24.xml delete mode 100644 app/src/main/res/drawable/list_divider.xml create mode 100644 app/src/main/res/layout/item_download.xml create mode 100644 app/src/main/res/layout/scene_download.xml diff --git a/app/build.gradle b/app/build.gradle index 59cf0dee1..945c9ebb3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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']) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e09a9e938..c3ad241bc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -51,6 +51,11 @@ + + diff --git a/app/src/main/java/com/hippo/ehviewer/Constants.java b/app/src/main/java/com/hippo/ehviewer/Constants.java index a48b91cda..41a9ebc38 100644 --- a/app/src/main/java/com/hippo/ehviewer/Constants.java +++ b/app/src/main/java/com/hippo/ehviewer/Constants.java @@ -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(); } } diff --git a/app/src/main/java/com/hippo/ehviewer/EhApplication.java b/app/src/main/java/com/hippo/ehviewer/EhApplication.java index c31dc8ca0..d29cdfc0f 100644 --- a/app/src/main/java/com/hippo/ehviewer/EhApplication.java +++ b/app/src/main/java/com/hippo/ehviewer/EhApplication.java @@ -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) { diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java index 16ca9ca73..49c7f6e63 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java @@ -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(); + } } } } diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java index e4c3e512d..e526a7664 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java @@ -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; diff --git a/app/src/main/java/com/hippo/ehviewer/client/data/DownloadInfo.java b/app/src/main/java/com/hippo/ehviewer/client/data/DownloadInfo.java new file mode 100644 index 000000000..5e34666a9 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/client/data/DownloadInfo.java @@ -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; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/GallerySpider.java b/app/src/main/java/com/hippo/ehviewer/gallery/GallerySpider.java index fe0047f27..788048311 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/GallerySpider.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/GallerySpider.java @@ -58,7 +58,8 @@ public class GallerySpider implements GalleryProvider, SpiderQueen.SpiderListene private AtomicLongArray mReceivedSizes; private AtomicReferenceArray mPercents; - private List mGalleryProviderListeners = new ArrayList<>(); + private List mGalleryProviderListeners = new ArrayList<>(2); + private List 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() { diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ImageHandler.java b/app/src/main/java/com/hippo/ehviewer/gallery/ImageHandler.java index 064520166..844d4f242 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ImageHandler.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ImageHandler.java @@ -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; diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/SpiderInfo.java b/app/src/main/java/com/hippo/ehviewer/gallery/SpiderInfo.java index 4e0b9b5d1..e708833c2 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/SpiderInfo.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/SpiderInfo.java @@ -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 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 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(); - } } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/SpiderQueen.java b/app/src/main/java/com/hippo/ehviewer/gallery/SpiderQueen.java index b8596b679..9a3f47142 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/SpiderQueen.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/SpiderQueen.java @@ -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 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); } } diff --git a/app/src/main/java/com/hippo/ehviewer/service/DownloadManager.java b/app/src/main/java/com/hippo/ehviewer/service/DownloadManager.java new file mode 100644 index 000000000..d1d2c8c91 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/service/DownloadManager.java @@ -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 mDownloadInfos; + private Map> mDownloadInfoMap; + private List mDefaultDownloadInfos; + + private List 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 tags = DBUtils.getAllDownloadTag(); + List downloadInfos = DBUtils.getAllDownloadInfo(); + mDownloadInfos = downloadInfos; + mDownloadInfoMap = new HashMap<>(); + + // Create list for each tag + for (String tag : tags) { + mDownloadInfoMap.put(tag, new ArrayList()); + } + // 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 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 getDownloadList(String tag) { + return getDownloadListInternal(tag, false); + } + + private List getDownloadListInternal(String tag, boolean create) { + if (TextUtils.isEmpty(tag)) { + return mDefaultDownloadInfos; + } else { + List 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 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 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; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/service/DownloadService.java b/app/src/main/java/com/hippo/ehviewer/service/DownloadService.java new file mode 100644 index 000000000..79a8b350b --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/service/DownloadService.java @@ -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(); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/ContentActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/ContentActivity.java index dbd168438..6ee4aeca9 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/ContentActivity.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/ContentActivity.java @@ -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(); diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/AbsDragSortScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/AbsDragSortScene.java index 1dac34814..7657f1fbb 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/AbsDragSortScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/AbsDragSortScene.java @@ -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)); diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadScene.java index c53f22431..d6db5891d 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadScene.java @@ -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 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 { + + 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; + } + } } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java index ac05e27af..76cb8e0bc 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java @@ -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) { diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java index 155f997b4..c33472b17 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java @@ -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; } } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/ModifyQuickSearchScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/ModifyQuickSearchScene.java index 4bbcf9e8c..a5866a72b 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/ModifyQuickSearchScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/ModifyQuickSearchScene.java @@ -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) { diff --git a/app/src/main/java/com/hippo/ehviewer/util/DBUtils.java b/app/src/main/java/com/hippo/ehviewer/util/DBUtils.java index ca6fdbedf..d8c46f6ba 100644 --- a/app/src/main/java/com/hippo/ehviewer/util/DBUtils.java +++ b/app/src/main/java/com/hippo/ehviewer/util/DBUtils.java @@ -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 getAllDownloadInfo() { + DownloadInfoObjDao dao = sDaoSession.getDownloadInfoObjDao(); + List list = dao.queryBuilder().orderAsc(DownloadInfoObjDao.Properties.Time).list(); + List 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 getAllDownloadTag() { + DownloadTagObjDao dao = sDaoSession.getDownloadTagObjDao(); + List list = dao.queryBuilder().list(); + List 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); + } } diff --git a/app/src/main/java/com/hippo/network/DownloadClient.java b/app/src/main/java/com/hippo/network/DownloadClient.java index 587479978..6db8718d3 100644 --- a/app/src/main/java/com/hippo/network/DownloadClient.java +++ b/app/src/main/java/com/hippo/network/DownloadClient.java @@ -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); diff --git a/app/src/main/java/com/hippo/network/DownloadRequest.java b/app/src/main/java/com/hippo/network/DownloadRequest.java index 2f818c6a3..c2281f8e5 100644 --- a/app/src/main/java/com/hippo/network/DownloadRequest.java +++ b/app/src/main/java/com/hippo/network/DownloadRequest.java @@ -61,7 +61,7 @@ public class DownloadRequest { public void cancel() { if (mHttpRequest != null) { - mHttpRequest.disconnect(); + mHttpRequest.cancel(); } } } diff --git a/app/src/main/java/com/hippo/scene/Scene.java b/app/src/main/java/com/hippo/scene/Scene.java index ec1e17da7..f6d2d0745 100644 --- a/app/src/main/java/com/hippo/scene/Scene.java +++ b/app/src/main/java/com/hippo/scene/Scene.java @@ -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; } diff --git a/app/src/main/java/com/hippo/widget/recyclerview/LinearDividerItemDecoration.java b/app/src/main/java/com/hippo/widget/recyclerview/LinearDividerItemDecoration.java index d290bf032..8817fb62e 100644 --- a/app/src/main/java/com/hippo/widget/recyclerview/LinearDividerItemDecoration.java +++ b/app/src/main/java/com/hippo/widget/recyclerview/LinearDividerItemDecoration.java @@ -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}.
* 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); diff --git a/app/src/main/res/drawable/ic_close_x24.xml b/app/src/main/res/drawable/ic_close_x24.xml new file mode 100644 index 000000000..02e8b4398 --- /dev/null +++ b/app/src/main/res/drawable/ic_close_x24.xml @@ -0,0 +1,40 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_pause_x24.xml b/app/src/main/res/drawable/ic_pause_x24.xml new file mode 100644 index 000000000..5c5ec333d --- /dev/null +++ b/app/src/main/res/drawable/ic_pause_x24.xml @@ -0,0 +1,40 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_play_x24.xml b/app/src/main/res/drawable/ic_play_x24.xml new file mode 100644 index 000000000..49fe1d385 --- /dev/null +++ b/app/src/main/res/drawable/ic_play_x24.xml @@ -0,0 +1,40 @@ + + + + + + + diff --git a/app/src/main/res/drawable/list_divider.xml b/app/src/main/res/drawable/list_divider.xml deleted file mode 100644 index a7e38fe80..000000000 --- a/app/src/main/res/drawable/list_divider.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/item_download.xml b/app/src/main/res/layout/item_download.xml new file mode 100644 index 000000000..23ca52b6f --- /dev/null +++ b/app/src/main/res/layout/item_download.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/scene_download.xml b/app/src/main/res/layout/scene_download.xml new file mode 100644 index 000000000..8dbd40aab --- /dev/null +++ b/app/src/main/res/layout/scene_download.xml @@ -0,0 +1,33 @@ + + + + + + + diff --git a/app/src/main/res/values/pathdata.xml b/app/src/main/res/values/pathdata.xml index bb35d60de..5f90920d2 100644 --- a/app/src/main/res/values/pathdata.xml +++ b/app/src/main/res/values/pathdata.xml @@ -48,6 +48,9 @@ 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 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 M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z + M8,5.14V19.14L19,12.14L8,5.14Z + M14,19.14H18V5.14H14M6,19.14H10V5.14H6V19.14Z + 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 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 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 60636f3ff..1fc5265bc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -216,6 +216,27 @@ \"What ?\" \"I lose my panda …\" + + Download server + Stop + Stop all + Successfully downloaded + Incompletely downloaded + Downloading complete + + One item successfully downloaded + %d items successfully downloaded + + + One item incompletely downloaded + %d items incompletely downloaded + + IDLE + Waiting + Done + Done + Incomplete + Keyword Search Retry diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 66b19a3b4..923d12ae7 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -97,6 +97,7 @@ + + diff --git a/beerbelly b/beerbelly index f2b6766de..2b2da1551 160000 --- a/beerbelly +++ b/beerbelly @@ -1 +1 @@ -Subproject commit f2b6766de55bdbe46d09be40179f8fa602567783 +Subproject commit 2b2da15514cd0b1ec50625f16dfcd7a137b90848 diff --git a/cardsalon b/cardsalon index 97d1d3082..18444fc75 160000 --- a/cardsalon +++ b/cardsalon @@ -1 +1 @@ -Subproject commit 97d1d3082284eac5e36543303ee6433e3e2bbe33 +Subproject commit 18444fc75ef70a335884f395d1ddf5080e216ed5 diff --git a/conaco b/conaco index 6bd893739..5c1fea7a1 160000 --- a/conaco +++ b/conaco @@ -1 +1 @@ -Subproject commit 6bd89373911617204a925839e627a32578fa9cb2 +Subproject commit 5c1fea7a11296df6f16126dbeb0b52f906e629d8 diff --git a/daogenerator/src/main/java/com/hippo/ehviewer/EhDaoGenerator.java b/daogenerator/src/main/java/com/hippo/ehviewer/EhDaoGenerator.java index 4f2a6d208..98647ca62 100644 --- a/daogenerator/src/main/java/com/hippo/ehviewer/EhDaoGenerator.java +++ b/daogenerator/src/main/java/com/hippo/ehviewer/EhDaoGenerator.java @@ -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) { diff --git a/hotspot b/hotspot index 04129925b..2783f8b9c 160000 --- a/hotspot +++ b/hotspot @@ -1 +1 @@ -Subproject commit 04129925be33a1c84b3df51c8a8cb2b22fb907a6 +Subproject commit 2783f8b9c0dc42cacdd887703be64fdd5a8bdcbf diff --git a/httpclient b/httpclient index c4288ea90..005aad851 160000 --- a/httpclient +++ b/httpclient @@ -1 +1 @@ -Subproject commit c4288ea904a7e386205dbc98b5897509dd39cb6d +Subproject commit 005aad85170c0405f73bc4388b3f2e92149bc92d diff --git a/rippleold b/rippleold index f6aeb8cb1..53a806d99 160000 --- a/rippleold +++ b/rippleold @@ -1 +1 @@ -Subproject commit f6aeb8cb1c4aac5a7a0149263e6c7334d439ca43 +Subproject commit 53a806d99dbc351ffd01a13df9f3bea77fca8873 diff --git a/unifile b/unifile index f068bc951..60e3145f8 160000 --- a/unifile +++ b/unifile @@ -1 +1 @@ -Subproject commit f068bc951b2fc9045d50a375b97530b95df49013 +Subproject commit 60e3145f858f7047549faa2c1be07f62a4d10272 diff --git a/vectorold b/vectorold index 34d314b58..b07793e9c 160000 --- a/vectorold +++ b/vectorold @@ -1 +1 @@ -Subproject commit 34d314b584741a703ba7e17cf3f7f123bd3ade40 +Subproject commit b07793e9c539eb9f5ce5b7e306e5b067f93ad74b diff --git a/yorozuya b/yorozuya index cd4d1b981..805f0c5f7 160000 --- a/yorozuya +++ b/yorozuya @@ -1 +1 @@ -Subproject commit cd4d1b981cd1205efe3159425fb094dae8be64b0 +Subproject commit 805f0c5f7aa80d82ddde2360e9f9b6d27f797124