diff --git a/TODO.network b/TODO.network index 3a0fd6ed2..b1b54b9ce 100644 --- a/TODO.network +++ b/TODO.network @@ -16,8 +16,8 @@ DONE Network library: Объединять книги по сериям NP: а где это происходит??? ** изменить вид библиотеки -- вместо дерева -- последовательные view без сдвига вправо (как в маркете) DONE сделать пункт Search в дереве - ** при нажатии на кнопку 'Esc' -- возвращаться к предыдущему view + прерывать загрузку, если она еще идет; - ** прерывание вручную умирает => загружать заново после прерывания + DONE при нажатии на кнопку 'Esc' -- возвращаться к предыдущему view + прерывать загрузку, если она еще идет; + DONE прерывание вручную умирает => загружать заново после прерывания * соответственно, каталог должен иметь не 2 состояния -- загружен/не загружен, а загружен полностью/не полностью/не загружен; в случае загрузки не полностью можно при повторном открытии продолжать загрузку с точки, где прервались (для тех каталогов, что подделены на куски по 20 (или сколько-то) книг ** сделать действия в меню (заполнение через NetworkTreeActions) diff --git a/src/org/geometerplus/android/fbreader/network/ItemsLoadingHandler.java b/src/org/geometerplus/android/fbreader/network/ItemsLoadingHandler.java index e1eade661..1fb055fbc 100644 --- a/src/org/geometerplus/android/fbreader/network/ItemsLoadingHandler.java +++ b/src/org/geometerplus/android/fbreader/network/ItemsLoadingHandler.java @@ -36,13 +36,17 @@ abstract class ItemsLoadingHandler extends Handler { private final LinkedList myItems = new LinkedList(); private final Object myItemsMonitor = new Object(); + private volatile boolean myFinishProcessed; + private final Object myFinishMonitor = new Object(); + + public final void addItem(NetworkLibraryItem item) { synchronized (myItemsMonitor) { myItems.add(item); } } - public final void ensureFinish() { + public final void ensureItemsProcessed() { synchronized (myItemsMonitor) { while (myItems.size() > 0) { try { @@ -53,27 +57,52 @@ abstract class ItemsLoadingHandler extends Handler { } } - public final void sendUpdateItems() { - sendEmptyMessage(WHAT_UPDATE_ITEMS); - } - - public final void sendFinish(String errorMessage) { - sendMessage(obtainMessage(WHAT_FINISHED, errorMessage)); - } - - public abstract void onUpdateItems(List items); - public abstract void afterUpdateItems(); - public abstract void onFinish(String errorMessage); - private final void doUpdateItems() { synchronized (myItemsMonitor) { onUpdateItems(myItems); myItems.clear(); - myItemsMonitor.notifyAll(); // wake up process, that waits for all items to be displayed (see ensureFinish() method) + myItemsMonitor.notifyAll(); // wake up process, that waits for finish condition (see ensureFinish() method) } afterUpdateItems(); } + public final void ensureFinishProcessed() { + synchronized (myFinishMonitor) { + while (!myFinishProcessed) { + try { + myFinishMonitor.wait(); + } catch (InterruptedException e) { + } + } + } + } + + private final void doProcessFinish(String errorMessage, boolean interrupted) { + synchronized (myFinishMonitor) { + onFinish(errorMessage, interrupted); + myFinishProcessed = true; + myFinishMonitor.notifyAll(); // wake up process, that waits for finish condition (see ensureFinish() method) + } + } + + + // sending messages methods + public final void sendUpdateItems() { + sendEmptyMessage(WHAT_UPDATE_ITEMS); + } + + public final void sendFinish(String errorMessage, boolean interrupted) { + int arg1 = interrupted ? 1 : 0; + sendMessage(obtainMessage(WHAT_FINISHED, arg1, 0, errorMessage)); + } + + + // callbacks + public abstract void onUpdateItems(List items); + public abstract void afterUpdateItems(); + public abstract void onFinish(String errorMessage, boolean interrupted); + + @Override public final void handleMessage(Message message) { switch (message.what) { @@ -81,7 +110,7 @@ abstract class ItemsLoadingHandler extends Handler { doUpdateItems(); break; case WHAT_FINISHED: - onFinish((String) message.obj); + doProcessFinish((String) message.obj, message.arg1 != 0); break; } } diff --git a/src/org/geometerplus/android/fbreader/network/ItemsLoadingRunnable.java b/src/org/geometerplus/android/fbreader/network/ItemsLoadingRunnable.java index e613d7cbf..e50c6f32e 100644 --- a/src/org/geometerplus/android/fbreader/network/ItemsLoadingRunnable.java +++ b/src/org/geometerplus/android/fbreader/network/ItemsLoadingRunnable.java @@ -19,7 +19,8 @@ package org.geometerplus.android.fbreader.network; -import java.util.concurrent.atomic.AtomicBoolean; +import android.os.Message; +import android.os.Handler; import org.geometerplus.fbreader.network.NetworkOperationData; import org.geometerplus.fbreader.network.NetworkLibraryItem; @@ -30,7 +31,6 @@ abstract class ItemsLoadingRunnable implements Runnable { public static final int CATALOG_LOADING = 0; public static final int NETWORK_SEARCH = 1; - public final AtomicBoolean InterruptFlag = new AtomicBoolean(); public final int Type; private final ItemsLoadingHandler myHandler; @@ -38,6 +38,47 @@ abstract class ItemsLoadingRunnable implements Runnable { private final long myUpdateInterval; // in milliseconds private final int myItemsLimit; + private boolean myInterruptRequested; + private boolean myInterruptConfirmed; + private Object myInterruptLock = new Object(); + + + private boolean myFinished; + private Handler myFinishedHandler; + private Object myFinishedLock = new Object(); + + + public void interrupt() { + synchronized (myInterruptLock) { + myInterruptRequested = true; + } + } + + private boolean confirmInterrupt() { + synchronized (myInterruptLock) { + if (myInterruptRequested) { + myInterruptConfirmed = true; + } + return myInterruptConfirmed; + } + } + + public boolean tryResume() { + synchronized (myInterruptLock) { + if (!myInterruptConfirmed) { + myInterruptRequested = false; + } + return !myInterruptRequested; + } + } + + private boolean isInterrupted() { + synchronized (myInterruptLock) { + return myInterruptConfirmed; + } + } + + public ItemsLoadingRunnable(ItemsLoadingHandler handler, int type) { this(handler, type, 1000, 500); } @@ -59,7 +100,7 @@ abstract class ItemsLoadingRunnable implements Runnable { public final void run() { String err = doBefore(); if (err != null) { - myHandler.sendFinish(err); + myHandler.sendFinish(err, false); return; } err = doLoading(new NetworkOperationData.OnNewItemListener() { @@ -67,7 +108,8 @@ abstract class ItemsLoadingRunnable implements Runnable { private int myItemsNumber; public boolean onNewItem(NetworkLibraryItem item) { myHandler.addItem(item); - if (InterruptFlag.get() || myItemsNumber++ >= myItemsLimit) { + final boolean interrupted = confirmInterrupt(); + if (interrupted || myItemsNumber++ >= myItemsLimit) { return true; } final long now = System.currentTimeMillis(); @@ -79,7 +121,31 @@ abstract class ItemsLoadingRunnable implements Runnable { } }); myHandler.sendUpdateItems(); - myHandler.ensureFinish(); - myHandler.sendFinish(err); + myHandler.ensureItemsProcessed(); + myHandler.sendFinish(err, isInterrupted()); + myHandler.ensureFinishProcessed(); + synchronized (myFinishedLock) { + if (myFinishedHandler != null) { + myFinishedHandler.sendEmptyMessage(0); + } + myFinished = true; + } + } + + public void runOnFinish(final Runnable runnable) { + if (myFinishedHandler != null) { + return; + } + synchronized (myFinishedLock) { + if (myFinished) { + runnable.run(); + } else { + myFinishedHandler = new Handler() { + public void handleMessage(Message message) { + runnable.run(); + } + }; + } + } } } diff --git a/src/org/geometerplus/android/fbreader/network/NetworkCatalogActions.java b/src/org/geometerplus/android/fbreader/network/NetworkCatalogActions.java index 17ef738bd..3fe2ce45f 100644 --- a/src/org/geometerplus/android/fbreader/network/NetworkCatalogActions.java +++ b/src/org/geometerplus/android/fbreader/network/NetworkCatalogActions.java @@ -48,7 +48,6 @@ class NetworkCatalogActions extends NetworkTreeActions { public static final int SIGNIN_ITEM_ID = 4; public static final int SIGNOUT_ITEM_ID = 5; public static final int REFILL_ACCOUNT_ITEM_ID = 6; - public static final int STOP_LOADING_ITEM_ID = 7; @Override @@ -77,17 +76,17 @@ class NetworkCatalogActions extends NetworkTreeActions { if (catalogUrl != null) { addMenuItem(menu, OPEN_CATALOG_ITEM_ID, "openCatalog"); - if (isLoading) { + /*if (isLoading) { if (catalogRunnable.InterruptFlag.get()) { addMenuItem(menu, TREE_NO_ACTION, "stoppingCatalogLoading"); } else { - addMenuItem(menu, STOP_LOADING_ITEM_ID, "stopLoading"); + addMenuItem(menu, TREE_NO_ACTION, "stopLoading"); } } else { if (catalogTree.hasChildren()) { addMenuItem(menu, RELOAD_ITEM_ID, "reload"); } - } + }*/ } if (tree instanceof NetworkCatalogRootTree) { @@ -164,9 +163,6 @@ class NetworkCatalogActions extends NetworkTreeActions { ((NetworkCatalogTree)tree).Item.Link.authenticationManager().refillAccountLink() ); return true; - case STOP_LOADING_ITEM_ID: - doStopLoading((NetworkCatalogTree)tree); - return true; } return false; } @@ -195,11 +191,16 @@ class NetworkCatalogActions extends NetworkTreeActions { } } - public void onFinish(String errorMessage) { - afterUpdateCatalog(errorMessage, myTree.ChildrenItems.size() == 0); - final NetworkLibrary library = NetworkLibrary.Instance(); - library.invalidateAccountDependents(); - library.synchronize(); + public void onFinish(String errorMessage, boolean interrupted) { + if (interrupted) { + myTree.ChildrenItems.clear(); + myTree.clear(); + } else { + afterUpdateCatalog(errorMessage, myTree.ChildrenItems.size() == 0); + final NetworkLibrary library = NetworkLibrary.Instance(); + library.invalidateAccountDependents(); + library.synchronize(); + } if (NetworkView.Instance().isInitialized()) { NetworkView.Instance().fireModelChanged(); } @@ -279,23 +280,26 @@ class NetworkCatalogActions extends NetworkTreeActions { } } - public void doExpandCatalog(NetworkBaseActivity activity, final NetworkCatalogTree tree) { + public void doExpandCatalog(final NetworkBaseActivity activity, final NetworkCatalogTree tree) { final String url = tree.Item.URLByType.get(NetworkCatalogItem.URL_CATALOG); if (url == null) { throw new RuntimeException("That's impossible!!!"); } - if (tree.hasChildren() - || NetworkView.Instance().containsItemsLoadingRunnable(url)) { - NetworkView.Instance().openTree(activity, tree, url); - return; - } - final ExpandCatalogHandler handler = new ExpandCatalogHandler(tree, url); - NetworkView.Instance().startItemsLoading( - activity, - url, - new ExpandCatalogRunnable(handler, tree, true) - ); - NetworkView.Instance().openTree(activity, tree, url); + NetworkView.Instance().tryResumeLoading(activity, tree, url, new Runnable() { + public void run() { + if (tree.hasChildren()) { + NetworkView.Instance().openTree(activity, tree, url); + return; + } + final ExpandCatalogHandler handler = new ExpandCatalogHandler(tree, url); + NetworkView.Instance().startItemsLoading( + activity, + url, + new ExpandCatalogRunnable(handler, tree, true) + ); + NetworkView.Instance().openTree(activity, tree, url); + } + }); } public void doReloadCatalog(NetworkBaseActivity activity, final NetworkCatalogTree tree) { @@ -318,17 +322,6 @@ class NetworkCatalogActions extends NetworkTreeActions { NetworkView.Instance().openTree(activity, tree, url); } - private void doStopLoading(NetworkCatalogTree tree) { - final String url = tree.Item.URLByType.get(NetworkCatalogItem.URL_CATALOG); - if (url == null) { - throw new RuntimeException("That's impossible!!!"); - } - final ItemsLoadingRunnable runnable = NetworkView.Instance().getItemsLoadingRunnable(url); - if (runnable != null) { - runnable.InterruptFlag.set(true); - } - } - private void doSignOut(NetworkBaseActivity activity, NetworkCatalogTree tree) { final Handler handler = new Handler() { public void handleMessage(Message message) { diff --git a/src/org/geometerplus/android/fbreader/network/NetworkCatalogActivity.java b/src/org/geometerplus/android/fbreader/network/NetworkCatalogActivity.java index 0ac3d9f21..c540330a6 100644 --- a/src/org/geometerplus/android/fbreader/network/NetworkCatalogActivity.java +++ b/src/org/geometerplus/android/fbreader/network/NetworkCatalogActivity.java @@ -150,4 +150,30 @@ public class NetworkCatalogActivity extends NetworkBaseActivity { public void onModelChanged() { getListView().invalidateViews(); } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { + doStopLoading(); + } + return super.onKeyDown(keyCode, event); + } + + private void doStopLoading() { + ItemsLoadingRunnable runnable = null; + if (myTree instanceof NetworkCatalogTree) { + final String url = ((NetworkCatalogTree) myTree).Item.URLByType.get(NetworkCatalogItem.URL_CATALOG); + if (url == null) { + throw new RuntimeException("That's impossible!!!"); + } + runnable = NetworkView.Instance().getItemsLoadingRunnable(url); + } else if (myTree instanceof SearchItemTree) { + if (NetworkView.Instance().isInitialized()) { + runnable = NetworkView.Instance().getItemsLoadingRunnable(NetworkSearchActivity.SEARCH_RUNNABLE_KEY); + } + } + if (runnable != null) { + runnable.interrupt(); + } + } } diff --git a/src/org/geometerplus/android/fbreader/network/NetworkSearchActions.java b/src/org/geometerplus/android/fbreader/network/NetworkSearchActions.java index 2060f77e1..23bd17473 100644 --- a/src/org/geometerplus/android/fbreader/network/NetworkSearchActions.java +++ b/src/org/geometerplus/android/fbreader/network/NetworkSearchActions.java @@ -39,7 +39,6 @@ import org.geometerplus.fbreader.network.*; class NetworkSearchActions extends NetworkTreeActions { public static final int OPEN_RESULTS_ITEM_ID = 0; - public static final int STOP_LOADING_ITEM_ID = 1; public static final int RUN_SEARCH_ITEM_ID = 2; @@ -72,13 +71,13 @@ class NetworkSearchActions extends NetworkTreeActions { if (isLoading || tree.hasChildren()) { addMenuItem(menu, OPEN_RESULTS_ITEM_ID, "showResults"); } - if (isLoading) { + /*if (isLoading) { if (searchRunnable.InterruptFlag.get()) { addMenuItem(menu, TREE_NO_ACTION, "stoppingNetworkSearch"); } else { - addMenuItem(menu, STOP_LOADING_ITEM_ID, "stopSearching"); + addMenuItem(menu, TREE_NO_ACTION, "stopSearching"); } - } + }*/ } @Override @@ -101,9 +100,6 @@ class NetworkSearchActions extends NetworkTreeActions { case OPEN_RESULTS_ITEM_ID: doExpandCatalog(activity, (SearchItemTree)tree); return true; - case STOP_LOADING_ITEM_ID: - doStopLoading((SearchItemTree)tree); - return true; case RUN_SEARCH_ITEM_ID: activity.onSearchRequested(); return true; @@ -112,20 +108,16 @@ class NetworkSearchActions extends NetworkTreeActions { } - public void doExpandCatalog(NetworkBaseActivity activity, final SearchItemTree tree) { + public void doExpandCatalog(final NetworkBaseActivity activity, final SearchItemTree tree) { if (!NetworkView.Instance().isInitialized()) { return; } - NetworkView.Instance().openTree(activity, tree, NetworkSearchActivity.SEARCH_RUNNABLE_KEY); - } - - private void doStopLoading(SearchItemTree tree) { - if (!NetworkView.Instance().isInitialized()) { - return; - } - final ItemsLoadingRunnable runnable = NetworkView.Instance().getItemsLoadingRunnable(NetworkSearchActivity.SEARCH_RUNNABLE_KEY); - if (runnable != null) { - runnable.InterruptFlag.set(true); - } + NetworkView.Instance().tryResumeLoading(activity, tree, NetworkSearchActivity.SEARCH_RUNNABLE_KEY, new Runnable() { + public void run() { + if (tree.hasChildren()) { + NetworkView.Instance().openTree(activity, tree, NetworkSearchActivity.SEARCH_RUNNABLE_KEY); + } + } + }); } } diff --git a/src/org/geometerplus/android/fbreader/network/NetworkSearchActivity.java b/src/org/geometerplus/android/fbreader/network/NetworkSearchActivity.java index c6d788465..19ef4aaa2 100644 --- a/src/org/geometerplus/android/fbreader/network/NetworkSearchActivity.java +++ b/src/org/geometerplus/android/fbreader/network/NetworkSearchActivity.java @@ -81,12 +81,16 @@ public class NetworkSearchActivity extends Activity { } } - public void onFinish(String errorMessage) { - myTree.updateSubTrees(); + public void onFinish(String errorMessage, boolean interrupted) { + if (interrupted) { + myTree.setSearchResult(null); + } else { + myTree.updateSubTrees(); + afterUpdateCatalog(errorMessage, myTree.getSearchResult().empty()); + } if (NetworkView.Instance().isInitialized()) { NetworkView.Instance().fireModelChanged(); } - afterUpdateCatalog(errorMessage, myTree.getSearchResult().empty()); } private void afterUpdateCatalog(String errorMessage, boolean childrenEmpty) { diff --git a/src/org/geometerplus/android/fbreader/network/NetworkView.java b/src/org/geometerplus/android/fbreader/network/NetworkView.java index f6f8af2da..e2c05d72a 100644 --- a/src/org/geometerplus/android/fbreader/network/NetworkView.java +++ b/src/org/geometerplus/android/fbreader/network/NetworkView.java @@ -122,6 +122,18 @@ class NetworkView { return getItemsLoadingRunnable(key) != null; } + public void tryResumeLoading(NetworkBaseActivity activity, NetworkTree tree, String key, Runnable expandRunnable) { + final ItemsLoadingRunnable runnable = NetworkView.Instance().getItemsLoadingRunnable(key); + if (runnable != null && runnable.tryResume()) { + openTree(activity, tree, key); + return; + } + if (runnable == null) { + expandRunnable.run(); + } else { + runnable.runOnFinish(expandRunnable); + } + } /* * Loading covers