diff --git a/src/org/geometerplus/android/fbreader/network/NetworkView.java b/src/org/geometerplus/android/fbreader/network/NetworkView.java index 61ec6dc94..e12cb4e53 100644 --- a/src/org/geometerplus/android/fbreader/network/NetworkView.java +++ b/src/org/geometerplus/android/fbreader/network/NetworkView.java @@ -58,6 +58,8 @@ class NetworkView { } public void initialize() { + new SQLiteNetworkDatabase(); + NetworkLibrary.Instance().synchronize(); myActions.add(new NetworkBookActions()); diff --git a/src/org/geometerplus/android/fbreader/network/SQLiteNetworkDatabase.java b/src/org/geometerplus/android/fbreader/network/SQLiteNetworkDatabase.java index c8cfc996f..ea52460ce 100644 --- a/src/org/geometerplus/android/fbreader/network/SQLiteNetworkDatabase.java +++ b/src/org/geometerplus/android/fbreader/network/SQLiteNetworkDatabase.java @@ -19,18 +19,43 @@ package org.geometerplus.android.fbreader.network; +import java.util.HashMap; +import java.util.List; + import android.content.Context; +import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; import org.geometerplus.zlibrary.ui.android.library.ZLAndroidApplication; +import org.geometerplus.fbreader.network.ICustomNetworkLink; import org.geometerplus.fbreader.network.NetworkDatabase; -public class SQLiteNetworkDatabase extends NetworkDatabase { +class SQLiteNetworkDatabase extends NetworkDatabase { private final SQLiteDatabase myDatabase; - public SQLiteNetworkDatabase() { + SQLiteNetworkDatabase() { myDatabase = ZLAndroidApplication.Instance().openOrCreateDatabase("network.db", Context.MODE_PRIVATE, null); + migrate(); + } + + private void migrate() { + final int version = myDatabase.getVersion(); + final int currentCodeVersion = 1; + if (version >= currentCodeVersion) { + return; + } + myDatabase.beginTransaction(); + switch (version) { + case 0: + createTables(); + } + myDatabase.setTransactionSuccessful(); + myDatabase.endTransaction(); + + myDatabase.execSQL("VACUUM"); + myDatabase.setVersion(currentCodeVersion); } protected void executeAsATransaction(Runnable actions) { @@ -42,4 +67,141 @@ public class SQLiteNetworkDatabase extends NetworkDatabase { myDatabase.endTransaction(); } } + + private static void bindString(SQLiteStatement statement, int index, String value) { + if (value != null) { + statement.bindString(index, value); + } else { + statement.bindNull(index); + } + } + + @Override + protected void loadCustomLinks(List links, ICustomLinksFactory factory) { + final Cursor cursor = myDatabase.rawQuery("SELECT link_id,title,site_name,summary,icon FROM CustomLinks", null); + final HashMap linksMap = new HashMap(); + while (cursor.moveToNext()) { + final int id = cursor.getInt(0); + final String title = cursor.getString(1); + final String siteName = cursor.getString(2); + final String summary = cursor.getString(3); + final String icon = cursor.getString(4); + + linksMap.clear(); + final Cursor linksCursor = myDatabase.rawQuery("SELECT key,url FROM CustomLinkUrls WHERE link_id = " + id, null); + while (linksCursor.moveToNext()) { + linksMap.put(linksCursor.getString(0), linksCursor.getString(1)); + } + linksCursor.close(); + + final ICustomNetworkLink newLink = factory.createCustomLink(id, siteName, title, summary, icon, linksMap); + if (newLink != null) { + links.add(newLink); + } + } + cursor.close(); + } + + private SQLiteStatement myInsertCustomLinkStatement; + private SQLiteStatement myUpdateCustomLinkStatement; + private SQLiteStatement myInsertCustomLinkUrlStatement; + private SQLiteStatement myUpdateCustomLinkUrlStatement; + private SQLiteStatement myDeleteCustomLinkUrlStatement; + @Override + protected void saveCustomLink(final ICustomNetworkLink link) { + executeAsATransaction(new Runnable() { + public void run() { + final SQLiteStatement statement; + if (link.getId() == ICustomNetworkLink.INVALID_ID) { + if (myInsertCustomLinkStatement == null) { + myInsertCustomLinkStatement = myDatabase.compileStatement( + "INSERT INTO CustomLinks (title,site_name,summary,icon) VALUES (?,?,?,?)" + ); + } + statement = myInsertCustomLinkStatement; + } else { + if (myUpdateCustomLinkStatement == null) { + myUpdateCustomLinkStatement = myDatabase.compileStatement( + "UPDATE CustomLinks SET title = ?, site_name = ?, summary =?, icon = ? " + + "WHERE link_id = ?" + ); + } + statement = myUpdateCustomLinkStatement; + } + + statement.bindString(1, link.getTitle()); + statement.bindString(2, link.getSiteName()); + bindString(statement, 3, link.getSummary()); + bindString(statement, 4, link.getIcon()); + + final long id; + final HashMap linksMap = new HashMap(); + + if (statement == myInsertCustomLinkStatement) { + id = statement.executeInsert(); + link.setId((int) id); + } else { + id = link.getId(); + statement.bindLong(5, id); + statement.execute(); + + final Cursor linksCursor = myDatabase.rawQuery("SELECT key,url FROM CustomLinkUrls WHERE link_id = " + link.getId(), null); + while (linksCursor.moveToNext()) { + linksMap.put(linksCursor.getString(0), linksCursor.getString(1)); + } + linksCursor.close(); + } + + for (String key: link.getLinkKeys()) { + final String value = link.getLink(key); + final String dbValue = linksMap.remove(key); + final SQLiteStatement urlStatement; + if (dbValue == null) { + if (myInsertCustomLinkUrlStatement == null) { + myInsertCustomLinkUrlStatement = myDatabase.compileStatement( + "INSERT INTO CustomLinkUrls(url,link_id,key) VALUES (?,?,?)"); + } + urlStatement = myInsertCustomLinkUrlStatement; + } else if (!value.equals(dbValue)) { + if (myUpdateCustomLinkUrlStatement == null) { + myUpdateCustomLinkUrlStatement = myDatabase.compileStatement( + "UPDATE CustomLinkUrls SET url = ? WHERE link_id = ? AND key = ?"); + } + urlStatement = myUpdateCustomLinkUrlStatement; + } else { + continue; + } + urlStatement.bindString(1, value); + urlStatement.bindLong(2, id); + urlStatement.bindString(3, key); + urlStatement.execute(); + } + for (String key: linksMap.keySet()) { + if (myDeleteCustomLinkUrlStatement == null) { + myDeleteCustomLinkUrlStatement = myDatabase.compileStatement( + "DELETE FROM CustomLinkUrls WHERE link_id = ? AND key = ?"); + } + myDeleteCustomLinkUrlStatement.bindLong(1, id); + myDeleteCustomLinkUrlStatement.bindString(2, key); + myDeleteCustomLinkUrlStatement.execute(); + } + } + }); + } + + private void createTables() { + myDatabase.execSQL( + "CREATE TABLE CustomLinks(" + + "link_id INTEGER PRIMARY KEY," + + "title TEXT UNIQUE NOT NULL," + + "site_name TEXT NOT NULL," + + "summary TEXT," + + "icon TEXT)"); + myDatabase.execSQL( + "CREATE TABLE CustomLinkUrls(" + + "key TEXT NOT NULL," + + "link_id INTEGER NOT NULL REFERENCES CustomLinks(link_id)," + + "url TEXT NOT NULL," + + "CONSTRAINT CustomLinkUrls_PK PRIMARY KEY (key, link_id))"); + } } diff --git a/src/org/geometerplus/fbreader/network/AbstractNetworkLink.java b/src/org/geometerplus/fbreader/network/AbstractNetworkLink.java index dbaddf60e..05ac8fb47 100644 --- a/src/org/geometerplus/fbreader/network/AbstractNetworkLink.java +++ b/src/org/geometerplus/fbreader/network/AbstractNetworkLink.java @@ -20,6 +20,7 @@ package org.geometerplus.fbreader.network; import java.util.Map; +import java.util.Set; import java.util.TreeMap; @@ -68,4 +69,8 @@ public abstract class AbstractNetworkLink implements INetworkLink { public String getLink(String urlKey) { return myLinks.get(urlKey); } + + public Set getLinkKeys() { + return myLinks.keySet(); + } } diff --git a/src/org/geometerplus/fbreader/network/ICustomNetworkLink.java b/src/org/geometerplus/fbreader/network/ICustomNetworkLink.java index cfc0f7c3c..60f505776 100644 --- a/src/org/geometerplus/fbreader/network/ICustomNetworkLink.java +++ b/src/org/geometerplus/fbreader/network/ICustomNetworkLink.java @@ -21,6 +21,18 @@ package org.geometerplus.fbreader.network; public interface ICustomNetworkLink extends INetworkLink { + public static final int INVALID_ID = -1; + + int getId(); + void setId(int id); + + interface SaveLinkListener { + void onSaveLink(ICustomNetworkLink link); + } + + void setSaveLinkListener(SaveLinkListener listener); + void saveLink(); + void setSiteName(String name); void setTitle(String title); void setSummary(String summary); diff --git a/src/org/geometerplus/fbreader/network/INetworkLink.java b/src/org/geometerplus/fbreader/network/INetworkLink.java index 28244251f..18a2354e9 100644 --- a/src/org/geometerplus/fbreader/network/INetworkLink.java +++ b/src/org/geometerplus/fbreader/network/INetworkLink.java @@ -19,6 +19,8 @@ package org.geometerplus.fbreader.network; +import java.util.Set; + import org.geometerplus.zlibrary.core.network.ZLNetworkRequest; import org.geometerplus.fbreader.network.authentication.NetworkAuthenticationManager; @@ -40,6 +42,7 @@ public interface INetworkLink { String getIcon(); String getLink(String urlKey); + Set getLinkKeys(); ZLNetworkRequest simpleSearchRequest(String pattern, NetworkOperationData data); ZLNetworkRequest resume(NetworkOperationData data); diff --git a/src/org/geometerplus/fbreader/network/NetworkDatabase.java b/src/org/geometerplus/fbreader/network/NetworkDatabase.java index 300ab666d..2f7bbf03d 100644 --- a/src/org/geometerplus/fbreader/network/NetworkDatabase.java +++ b/src/org/geometerplus/fbreader/network/NetworkDatabase.java @@ -19,6 +19,9 @@ package org.geometerplus.fbreader.network; +import java.util.List; +import java.util.Map; + public abstract class NetworkDatabase { private static NetworkDatabase ourInstance; @@ -32,4 +35,10 @@ public abstract class NetworkDatabase { protected abstract void executeAsATransaction(Runnable actions); + public interface ICustomLinksFactory { + ICustomNetworkLink createCustomLink(int id, String siteName, String title, String summary, String icon, Map links); + } + + protected abstract void loadCustomLinks(List links, ICustomLinksFactory factory); + protected abstract void saveCustomLink(ICustomNetworkLink link); } diff --git a/src/org/geometerplus/fbreader/network/NetworkLibrary.java b/src/org/geometerplus/fbreader/network/NetworkLibrary.java index ea1738e81..2dbc7e9f4 100644 --- a/src/org/geometerplus/fbreader/network/NetworkLibrary.java +++ b/src/org/geometerplus/fbreader/network/NetworkLibrary.java @@ -213,7 +213,6 @@ public class NetworkLibrary { private final ArrayList myLoadedLinks = new ArrayList(); private final ArrayList myCustomLinks = new ArrayList(); - private final CompositeList myLinks; private final RootTree myRootTree = new RootTree(); @@ -223,7 +222,7 @@ public class NetworkLibrary { private NetworkLibrary() { LinkedList catalogs = readCatalogFileNames(); - OPDSLinkReader reader = new OPDSLinkReader(); + final OPDSLinkReader reader = new OPDSLinkReader(); for (String fileName: catalogs) { INetworkLink link = reader.readDocument(ZLResourceFile.createResourceFile("data/network/" + fileName)); if (link != null) { @@ -231,17 +230,31 @@ public class NetworkLibrary { } } - Map links; - - links = new TreeMap(); + /*final HashMap links = new HashMap(); links.put(INetworkLink.URL_MAIN, "http://bookserver.archive.org/catalog/"); - myCustomLinks.add( reader.createCustomLink("archive.org", - "Internet Archive Catalog", null, null, links)); - - links = new TreeMap(); + NetworkDatabase.Instance().saveCustomLink( + reader.createCustomLink( + ICustomNetworkLink.INVALID_ID, + "archive.org", "Internet Archive Catalog", null, null, links + ) + ); + links.clear(); links.put(INetworkLink.URL_MAIN, "http://pragprog.com/magazines.opds"); - myCustomLinks.add( reader.createCustomLink("pragprog.com", - "PragPub Magazine", "The Pragmatic Bookshelf", null, links)); + NetworkDatabase.Instance().saveCustomLink( + reader.createCustomLink( + ICustomNetworkLink.INVALID_ID, + "pragprog.com", "PragPub Magazine", "The Pragmatic Bookshelf", null, links + ) + );*/ + + NetworkDatabase.Instance().loadCustomLinks(myCustomLinks, + new NetworkDatabase.ICustomLinksFactory() { + public ICustomNetworkLink createCustomLink(int id, String siteName, + String title, String summary, String icon, Map links) { + return reader.createCustomLink(id, siteName, title, summary, icon, links); + } + } + ); LinksComparator comparator = new LinksComparator(); Collections.sort(myLoadedLinks, comparator); @@ -275,9 +288,11 @@ public class NetworkLibrary { public String rewriteUrl(String url, boolean externalUrl) { final String host = ZLNetworkUtil.hostFromUrl(url).toLowerCase(); - for (INetworkLink link: myLinks) { - if (host.contains(link.getSiteName())) { - url = link.rewriteUrl(url, externalUrl); + synchronized (myLinks) { + for (INetworkLink link: myLinks) { + if (host.contains(link.getSiteName())) { + url = link.rewriteUrl(url, externalUrl); + } } } return url; @@ -298,50 +313,52 @@ public class NetworkLibrary { FBTree currentNode = null; int nodeCount = 0; - ListIterator it = myLinks.listIterator(); - while (it.hasNext()) { - INetworkLink link = it.next(); - /*if (!link.OnOption.getValue()) { - continue; - }*/ - boolean processed = false; - while (currentNode != null || nodeIterator.hasNext()) { - if (currentNode == null) { - currentNode = nodeIterator.next(); - } - if (!(currentNode instanceof NetworkCatalogTree)) { - currentNode = null; - ++nodeCount; + synchronized (myLinks) { + ListIterator it = myLinks.listIterator(); + while (it.hasNext()) { + INetworkLink link = it.next(); + /*if (!link.OnOption.getValue()) { continue; - } - final INetworkLink nodeLink = ((NetworkCatalogTree)currentNode).Item.Link; - if (nodeLink == link) { - currentNode = null; - ++nodeCount; - processed = true; - break; - } else { - boolean found = false; - ListIterator jt = myLinks.listIterator(it); - while (jt.hasNext()) { - if (nodeLink == jt.next()) { - found = true; + }*/ + boolean processed = false; + while (currentNode != null || nodeIterator.hasNext()) { + if (currentNode == null) { + currentNode = nodeIterator.next(); + } + if (!(currentNode instanceof NetworkCatalogTree)) { + currentNode = null; + ++nodeCount; + continue; + } + final INetworkLink nodeLink = ((NetworkCatalogTree) currentNode).Item.Link; + if (nodeLink == link) { + currentNode = null; + ++nodeCount; + processed = true; + break; + } else { + boolean found = false; + ListIterator jt = myLinks.listIterator(it); + while (jt.hasNext()) { + if (nodeLink == jt.next()) { + found = true; + break; + } + } + if (!found) { + toRemove.add(currentNode); + currentNode = null; + ++nodeCount; + } else { break; } } - if (!found) { - toRemove.add(currentNode); - currentNode = null; - ++nodeCount; - } else { - break; - } } - } - final int nextIndex = nodeIterator.nextIndex(); - if (!processed) { - new NetworkCatalogRootTree(myRootTree, link, nodeCount++).Item.onDisplayItem(); - nodeIterator = myRootTree.subTrees().listIterator(nextIndex + 1); + final int nextIndex = nodeIterator.nextIndex(); + if (!processed) { + new NetworkCatalogRootTree(myRootTree, link, nodeCount++).Item.onDisplayItem(); + nodeIterator = myRootTree.subTrees().listIterator(nextIndex + 1); + } } } @@ -351,7 +368,6 @@ public class NetworkLibrary { } toRemove.add(currentNode); currentNode = null; - //++nodeCount; // TODO: where to increment??? } for (FBTree tree: toRemove) { @@ -396,15 +412,17 @@ public class NetworkLibrary { } }; - for (INetworkLink link: myLoadedLinks) { - //if (link.OnOption.getValue()) { - // execute next code only if link is enabled - //} - NetworkOperationData data = new NetworkOperationData(link, synchronizedListener); - ZLNetworkRequest request = link.simpleSearchRequest(pattern, data); - if (request != null) { - dataList.add(data); - requestList.add(request); + synchronized (myLinks) { + for (INetworkLink link: myLinks) { + //if (link.OnOption.getValue()) { + // execute next code only if link is enabled + //} + NetworkOperationData data = new NetworkOperationData(link, synchronizedListener); + ZLNetworkRequest request = link.simpleSearchRequest(pattern, data); + if (request != null) { + dataList.add(data); + requestList.add(request); + } } } @@ -426,4 +444,34 @@ public class NetworkLibrary { return null; } + + private ICustomNetworkLink.SaveLinkListener myChangesListener = new ICustomNetworkLink.SaveLinkListener() { + public void onSaveLink(ICustomNetworkLink link) { + NetworkDatabase.Instance().saveCustomLink(link); + } + }; + + /** + * @return false if this library already contains link with the specified title + * true if links has been inserted into this library + */ + public boolean addCustomLink(ICustomNetworkLink link) { + final int index = Collections.binarySearch(myCustomLinks, link, new LinksComparator()); + if (index >= 0) { + return false; + } + final int insertAt = -index - 1; + myCustomLinks.add(insertAt, link); + link.setSaveLinkListener(myChangesListener); + link.saveLink(); + return true; + } + + public int getCustomLinksNumber() { + return myCustomLinks.size(); + } + + public ICustomNetworkLink getCustomLink(int index) { + return myCustomLinks.get(index); + } } diff --git a/src/org/geometerplus/fbreader/network/opds/OPDSCustomLink.java b/src/org/geometerplus/fbreader/network/opds/OPDSCustomLink.java index f6f5423fe..98cf381a5 100644 --- a/src/org/geometerplus/fbreader/network/opds/OPDSCustomLink.java +++ b/src/org/geometerplus/fbreader/network/opds/OPDSCustomLink.java @@ -26,8 +26,30 @@ import org.geometerplus.fbreader.network.ICustomNetworkLink; class OPDSCustomLink extends OPDSLink implements ICustomNetworkLink { - OPDSCustomLink(String siteName, String title, String summary, String icon, Map links) { + private int myId; + private SaveLinkListener myListener; + + OPDSCustomLink(int id, String siteName, String title, String summary, String icon, Map links) { super(siteName, title, summary, icon, links); + myId = id; + } + + public int getId() { + return myId; + } + + public void setId(int id) { + myId = id; + } + + public void setSaveLinkListener(SaveLinkListener listener) { + myListener = listener; + } + + public void saveLink() { + if (myListener != null) { + myListener.onSaveLink(this); + } } public final void setIcon(String icon) { diff --git a/src/org/geometerplus/fbreader/network/opds/OPDSLinkReader.java b/src/org/geometerplus/fbreader/network/opds/OPDSLinkReader.java index eb47eab82..6bd749c45 100644 --- a/src/org/geometerplus/fbreader/network/opds/OPDSLinkReader.java +++ b/src/org/geometerplus/fbreader/network/opds/OPDSLinkReader.java @@ -87,14 +87,15 @@ public class OPDSLinkReader extends ZLXMLReaderAdapter { return opdsLink; } - public ICustomNetworkLink createCustomLink(String siteName, String title, String summary, String icon, Map links) { + public ICustomNetworkLink createCustomLink(int id, String siteName, String title, String summary, String icon, Map links) { if (siteName == null || title == null || links.get(INetworkLink.URL_MAIN) == null) { return null; } - OPDSCustomLink link = new OPDSCustomLink(siteName, title, summary, icon, links); + OPDSCustomLink link = new OPDSCustomLink(id, siteName, title, summary, icon, links); // TODO: read common OPDSLink attributes from special custom.xml file + // Does this additional info have to override duplicated settings, received from user??? return link; }