/*
* Copyright (C) 2010-2011 Geometer Plus <contact@geometerplus.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
package org.geometerplus.fbreader.network.opds;
import java.util.*;
import org.geometerplus.zlibrary.core.constants.XMLNamespaces;
import org.geometerplus.zlibrary.core.constants.MimeTypes;
import org.geometerplus.zlibrary.core.filesystem.ZLResourceFile;
import org.geometerplus.zlibrary.core.util.ZLNetworkUtil;
import org.geometerplus.zlibrary.core.xml.ZLStringMap;
import org.geometerplus.fbreader.network.INetworkLink;
import org.geometerplus.fbreader.network.NetworkLibrary;
import org.geometerplus.fbreader.network.NetworkCatalogItem;
import org.geometerplus.fbreader.network.UrlInfo;
import org.geometerplus.fbreader.network.atom.ATOMLink;
import org.geometerplus.fbreader.network.atom.ATOMUpdated;
import org.geometerplus.fbreader.network.authentication.NetworkAuthenticationManager;
import org.geometerplus.fbreader.network.authentication.litres.LitResAuthenticationManager;
class OPDSLinkXMLReader extends OPDSXMLReader implements OPDSConstants, MimeTypes {
private static class LinkReader implements OPDSFeedReader {
private NetworkLibrary.OnNewLinkListener myListener;
private String myAuthenticationType;
private boolean myHasStableIdentifiers;
private final LinkedList<URLRewritingRule> myUrlRewritingRules = new LinkedList<URLRewritingRule>();
private final HashMap<RelationAlias, String> myRelationAliases = new HashMap<RelationAlias, String>();
private final LinkedHashMap<String,String> myExtraData = new LinkedHashMap<String,String>();
private ATOMUpdated myUpdatedTime;
private ATOMUpdated myReadAfterTime;
public LinkReader(NetworkLibrary.OnNewLinkListener listener, ATOMUpdated readAfter) {
myListener = listener;
myReadAfterTime = readAfter;
}
public void setAuthenticationType(String type) {
myAuthenticationType = type;
}
public void setHasStableIdentifiers(boolean value) {
myHasStableIdentifiers = value;
}
public void addUrlRewritingRule(URLRewritingRule rule) {
myUrlRewritingRules.add(rule);
}
public void addRelationAlias(RelationAlias alias, String relation) {
myRelationAliases.put(alias, relation);
}
public void putExtraData(String name, String value) {
myExtraData.put(name, value);
}
public void clear() {
myAuthenticationType = null;
myHasStableIdentifiers = false;
myUrlRewritingRules.clear();
myRelationAliases.clear();
myExtraData.clear();
}
public ATOMUpdated getUpdatedTime() {
return myUpdatedTime;
}
private static final String ENTRY_ID_PREFIX = "urn:fbreader-org-catalog:";
public boolean processFeedEntry(OPDSEntry entry) {
final String id = entry.Id.Uri;
if (id == null || id.length() <= ENTRY_ID_PREFIX.length()
|| !id.startsWith(ENTRY_ID_PREFIX)) {
return false;
}
final String siteName = id.substring(ENTRY_ID_PREFIX.length());
final String title = entry.Title;
final String summary = entry.Content;
final String language = entry.DCLanguage;
final HashMap<String,UrlInfo> infos = new HashMap<String,UrlInfo>();
final HashMap<String,NetworkCatalogItem.Accessibility> urlConditions =
new HashMap<String,NetworkCatalogItem.Accessibility>();
for (ATOMLink link: entry.Links) {
final String href = link.getHref();
final String type = ZLNetworkUtil.filterMimeType(link.getType());
final String rel = link.getRel();
if (rel == REL_IMAGE_THUMBNAIL || rel == REL_THUMBNAIL) {
if (type == MIME_IMAGE_PNG || type == MIME_IMAGE_JPEG) {
infos.put(INetworkLink.URL_ICON, new UrlInfo(href));
}
} else if ((rel != null && rel.startsWith(REL_IMAGE_PREFIX)) || rel == REL_COVER) {
if (infos.get(INetworkLink.URL_ICON) == null &&
(type == MIME_IMAGE_PNG || type == MIME_IMAGE_JPEG)) {
infos.put(INetworkLink.URL_ICON, new UrlInfo(href));
}
} else if (rel == null) {
if (type == MIME_APP_ATOM) {
infos.put(INetworkLink.URL_MAIN, new UrlInfo(href));
}
} else if (rel == "search") {
if (type == MIME_APP_ATOM) {
final OpenSearchDescription descr = OpenSearchDescription.createDefault(href);
if (descr.isValid()) {
// TODO: May be do not use '%s'??? Use Description instead??? (this needs to rewrite SEARCH engine logic a little)
infos.put(INetworkLink.URL_SEARCH, new UrlInfo(descr.makeQuery("%s")));
}
}
} else if (rel == REL_LINK_SIGN_IN) {
infos.put(INetworkLink.URL_SIGN_IN, new UrlInfo(href));
} else if (rel == REL_LINK_SIGN_OUT) {
infos.put(INetworkLink.URL_SIGN_OUT, new UrlInfo(href));
} else if (rel == REL_LINK_SIGN_UP) {
infos.put(INetworkLink.URL_SIGN_UP, new UrlInfo(href));
} else if (rel == REL_LINK_REFILL_ACCOUNT) {
infos.put(INetworkLink.URL_REFILL_ACCOUNT, new UrlInfo(href));
} else if (rel == REL_LINK_RECOVER_PASSWORD) {
infos.put(INetworkLink.URL_RECOVER_PASSWORD, new UrlInfo(href));
} else if (rel == REL_CONDITION_NEVER) {
urlConditions.put(href, NetworkCatalogItem.Accessibility.NEVER);
} else if (rel == REL_CONDITION_SIGNED_IN) {
urlConditions.put(href, NetworkCatalogItem.Accessibility.SIGNED_IN);
} else if (rel == REL_CONDITION_HAS_BOOKS) {
urlConditions.put(href, NetworkCatalogItem.Accessibility.HAS_BOOKS);
}
}
final String sslCertificate;
final String path = "network/" + siteName + ".crt";
if (ZLResourceFile.createResourceFile(path).exists()) {
sslCertificate = path;
} else {
sslCertificate = null;
}
INetworkLink result = link(siteName, title, summary, language, infos, urlConditions, sslCertificate);
if (result != null) {
myListener.onNewLink(result);
}
return false;
}
private INetworkLink link(
String siteName,
String title,
String summary,
String language,
Map<String,UrlInfo> infos,
HashMap<String,NetworkCatalogItem.Accessibility> urlConditions,
String sslCertificate
) {
if (siteName == null || title == null || infos.get(INetworkLink.URL_MAIN) == null) {
return null;
}
OPDSNetworkLink opdsLink = new OPDSNetworkLink(
siteName,
title,
summary,
language,
infos,
myHasStableIdentifiers
);
/*if (!mySearchType.empty()) {
opdsLink.setupAdvancedSearch(
mySearchType,
mySearchFields["titleOrSeries"],
mySearchFields["author"],
mySearchFields["tag"],
mySearchFields["annotation"]
);
}*/
opdsLink.setRelationAliases(myRelationAliases);
opdsLink.setUrlConditions(urlConditions);
opdsLink.setUrlRewritingRules(myUrlRewritingRules);
opdsLink.setExtraData(myExtraData);
NetworkAuthenticationManager authManager = null;
if (myAuthenticationType == "basic") {
//authManager = NetworkAuthenticationManager.createManager(opdsLink, sslCertificate, BasicAuthenticationManager.class);
} else if (myAuthenticationType == "litres") {
authManager = NetworkAuthenticationManager.createManager(opdsLink, sslCertificate, LitResAuthenticationManager.class);
}
opdsLink.setAuthenticationManager(authManager);
return opdsLink;
}
public boolean processFeedMetadata(OPDSFeedMetadata feed, boolean beforeEntries) {
myUpdatedTime = feed.Updated;
if (myUpdatedTime != null && myReadAfterTime != null
&& myUpdatedTime.compareTo(myReadAfterTime) <= 0) {
return true;
}
return myListener == null; // no listener -- no need to proceed
}
public void processFeedStart() {
myUpdatedTime = null;
}
public void processFeedEnd() {
}
}
public OPDSLinkXMLReader() {
super(new LinkReader(null, null));
}
public OPDSLinkXMLReader(NetworkLibrary.OnNewLinkListener listener, ATOMUpdated readAfter) {
super(new LinkReader(listener, readAfter));
}
public ATOMUpdated getUpdatedTime() {
return ((LinkReader) myFeedReader).getUpdatedTime();
}
private String myFBReaderNamespaceId;
@Override
public void namespaceMapChangedHandler(Map<String, String> namespaceMap) {
super.namespaceMapChangedHandler(namespaceMap);
myFBReaderNamespaceId = null;
for (Map.Entry<String,String> entry : namespaceMap.entrySet()) {
final String value = entry.getValue();
if (value == XMLNamespaces.FBReaderCatalogMetadata) {
myFBReaderNamespaceId = intern(entry.getKey());
}
}
}
private static final String FBREADER_ADVANCED_SEARCH = "advancedSearch";
private static final String FBREADER_AUTHENTICATION = "authentication";
private static final String FBREADER_STABLE_IDENTIFIERS = "hasStableIdentifiers";
private static final String FBREADER_REWRITING_RULE = "urlRewritingRule";
private static final String FBREADER_RELATION_ALIAS = "relationAlias";
private static final String FBREADER_EXTRA = "extra";
@Override
public boolean startElementHandler(final String tagPrefix, final String tag,
final ZLStringMap attributes, final String bufferContent) {
switch (getState()) {
case FEED:
if (tagPrefix == myAtomNamespaceId && tag == TAG_ENTRY) {
((LinkReader) myFeedReader).clear();
}
break;
case F_ENTRY:
if (tagPrefix == myFBReaderNamespaceId) {
if (tag == FBREADER_ADVANCED_SEARCH) {
return false;
} else if (tag == FBREADER_AUTHENTICATION) {
final String type = attributes.getValue("type");
((LinkReader) myFeedReader).setAuthenticationType(type);
return false;
} else if (tag == FBREADER_RELATION_ALIAS) {
final String name = attributes.getValue("name");
final String type = attributes.getValue("type");
String alias = attributes.getValue("alias");
if (alias != null && name != null) {
if (alias.length() == 0) {
alias = null;
}
((LinkReader) myFeedReader).addRelationAlias(new RelationAlias(alias, type), name);
}
return false;
} else if (tag == FBREADER_REWRITING_RULE) {
((LinkReader)myFeedReader).addUrlRewritingRule(new URLRewritingRule(attributes));
return false;
} else if (tag == FBREADER_STABLE_IDENTIFIERS) {
((LinkReader)myFeedReader).setHasStableIdentifiers(true);
return false;
} else if (tag == FBREADER_EXTRA) {
final String name = attributes.getValue("name");
final String value = attributes.getValue("value");
if (name != null && value != null) {
((LinkReader) myFeedReader).putExtraData(name, value);
}
}
}
break;
}
return super.startElementHandler(tagPrefix, tag, attributes, bufferContent);
}
}