/* * 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 org.geometerplus.zlibrary.core.util.MimeType; import org.geometerplus.zlibrary.core.util.ZLNetworkUtil; import org.geometerplus.fbreader.network.*; import org.geometerplus.fbreader.network.atom.*; import org.geometerplus.fbreader.network.authentication.litres.LitResBookshelfItem; import org.geometerplus.fbreader.network.authentication.litres.LitResRecommendationsItem; import org.geometerplus.fbreader.network.urlInfo.*; class OPDSFeedHandler implements ATOMFeedHandler<OPDSFeedMetadata,OPDSEntry>, OPDSConstants { private final OPDSCatalogItem myCatalog; private final String myBaseURL; private final OPDSCatalogItem.State myData; private int myIndex; private String myNextURL; private String mySkipUntilId; private boolean myFoundNewIds; private int myItemsToLoad = -1; /** * Creates new OPDSFeedHandler instance that can be used to get NetworkItem objects from OPDS feeds. * * @param baseURL string that contains URL of the OPDS feed, that will be read using this instance of the reader * @param result network results buffer. Must be created using OPDSNetworkLink corresponding to the OPDS feed, * that will be read using this instance of the reader. */ OPDSFeedHandler(OPDSCatalogItem catalog, String baseURL, OPDSCatalogItem.State result) { myCatalog = catalog; myBaseURL = baseURL; myData = result; mySkipUntilId = myData.LastLoadedId; myFoundNewIds = mySkipUntilId != null; if (!(result.Link instanceof OPDSNetworkLink)) { throw new IllegalArgumentException("Parameter `result` has invalid `Link` field value: result.Link must be an instance of OPDSNetworkLink class."); } } public void processFeedStart() { myData.ResumeURI = myBaseURL; } public boolean processFeedMetadata(OPDSFeedMetadata feed, boolean beforeEntries) { if (beforeEntries) { myIndex = feed.OpensearchStartIndex - 1; if (feed.OpensearchItemsPerPage > 0) { myItemsToLoad = feed.OpensearchItemsPerPage; final int len = feed.OpensearchTotalResults - myIndex; if (len > 0 && len < myItemsToLoad) { myItemsToLoad = len; } } if (myCatalog != null) { if ("series".equals(feed.ViewType)) { myCatalog.setFlags(myCatalog.getFlags() & ~OPDSCatalogItem.FLAGS_GROUP); } else if ("authors".equals(feed.ViewType)) { myCatalog.setFlags(myCatalog.getFlags() & ~OPDSCatalogItem.FLAG_SHOW_AUTHOR); } } } else { final OPDSNetworkLink opdsLink = (OPDSNetworkLink)myData.Link; for (ATOMLink link : feed.Links) { final MimeType type = MimeType.get(link.getType()); final String rel = opdsLink.relation(link.getRel(), type); if (MimeType.APP_ATOM.weakEquals(type) && "next".equals(rel)) { myNextURL = ZLNetworkUtil.url(myBaseURL, link.getHref()); } } } return false; } public void processFeedEnd() { if (mySkipUntilId != null) { // Last loaded element was not found => resume error => DO NOT RESUME // TODO: notify user about error??? // TODO: do reload??? myNextURL = null; } myData.ResumeURI = myFoundNewIds ? myNextURL : null; myData.LastLoadedId = null; } private boolean tryInterrupt() { final int noninterruptableRemainder = 10; return (myItemsToLoad < 0 || myItemsToLoad > noninterruptableRemainder) && myData.Listener.confirmInterrupt(); } private String calculateEntryId(OPDSEntry entry) { if (entry.Id != null) { return entry.Id.Uri; } String id = null; int idType = 0; final OPDSNetworkLink opdsLink = (OPDSNetworkLink)myData.Link; for (ATOMLink link : entry.Links) { final MimeType type = MimeType.get(link.getType()); final String rel = opdsLink.relation(link.getRel(), type); if (rel == null && MimeType.APP_ATOM.weakEquals(type)) { return ZLNetworkUtil.url(myBaseURL, link.getHref()); } int relType = BookUrlInfo.Format.NONE; if (rel == null || rel.startsWith(REL_ACQUISITION_PREFIX) || rel.startsWith(REL_FBREADER_ACQUISITION_PREFIX)) { relType = OPDSBookItem.formatByMimeType(type); } if (relType != BookUrlInfo.Format.NONE && (id == null || idType < relType || (idType == relType && REL_ACQUISITION.equals(rel)))) { id = ZLNetworkUtil.url(myBaseURL, link.getHref()); idType = relType; } } return id; } public boolean processFeedEntry(OPDSEntry entry) { if (myItemsToLoad >= 0) { --myItemsToLoad; } if (entry.Id == null) { final String id = calculateEntryId(entry); if (id == null) { return tryInterrupt(); } entry.Id = new ATOMId(); entry.Id.Uri = id; } if (mySkipUntilId != null) { if (mySkipUntilId.equals(entry.Id.Uri)) { mySkipUntilId = null; } return tryInterrupt(); } myData.LastLoadedId = entry.Id.Uri; if (!myFoundNewIds && !myData.LoadedIds.contains(entry.Id.Uri)) { myFoundNewIds = true; } myData.LoadedIds.add(entry.Id.Uri); final OPDSNetworkLink opdsLink = (OPDSNetworkLink)myData.Link; boolean hasBookLink = false; for (ATOMLink link: entry.Links) { final MimeType type = MimeType.get(link.getType()); final String rel = opdsLink.relation(link.getRel(), type); if (rel == null ? (OPDSBookItem.formatByMimeType(type) != BookUrlInfo.Format.NONE) : (rel.startsWith(REL_ACQUISITION_PREFIX) || rel.startsWith(REL_FBREADER_ACQUISITION_PREFIX))) { hasBookLink = true; break; } } NetworkItem item; if (hasBookLink) { item = new OPDSBookItem((OPDSNetworkLink)myData.Link, entry, myBaseURL, myIndex++); } else { item = readCatalogItem(entry); } if (item != null) { myData.Listener.onNewItem(myData.Link, item); } return tryInterrupt(); } private NetworkItem readCatalogItem(OPDSEntry entry) { final OPDSNetworkLink opdsLink = (OPDSNetworkLink)myData.Link; final UrlInfoCollection<UrlInfo> urlMap = new UrlInfoCollection<UrlInfo>(); boolean urlIsAlternate = false; String litresRel = null; for (ATOMLink link : entry.Links) { final String href = ZLNetworkUtil.url(myBaseURL, link.getHref()); final MimeType type = MimeType.get(link.getType()); final String rel = opdsLink.relation(link.getRel(), type); if (MimeType.IMAGE_PNG.weakEquals(type) || MimeType.IMAGE_JPEG.weakEquals(type)) { if (REL_IMAGE_THUMBNAIL.equals(rel) || REL_THUMBNAIL.equals(rel)) { urlMap.addInfo(new UrlInfo(UrlInfo.Type.Thumbnail, href)); } else if (REL_COVER.equals(rel) || (rel != null && rel.startsWith(REL_IMAGE_PREFIX))) { urlMap.addInfo(new UrlInfo(UrlInfo.Type.Image, href)); } } else if (MimeType.APP_ATOM.weakEquals(type)) { final boolean hasCatalogUrl = urlMap.getInfo(UrlInfo.Type.Catalog) != null; if (REL_ALTERNATE.equals(rel)) { if (!hasCatalogUrl) { urlMap.addInfo(new UrlInfo(UrlInfo.Type.Catalog, href)); urlIsAlternate = true; } } else if (!hasCatalogUrl || rel == null || REL_SUBSECTION.equals(rel)) { urlMap.addInfo(new UrlInfo(UrlInfo.Type.Catalog, href)); urlIsAlternate = false; } } else if (MimeType.TEXT_HTML.weakEquals(type)) { if (REL_ACQUISITION.equals(rel) || REL_ACQUISITION_OPEN.equals(rel) || REL_ALTERNATE.equals(rel) || rel == null) { urlMap.addInfo(new UrlInfo(UrlInfo.Type.HtmlPage, href)); } } else if (MimeType.APP_LITRES.weakEquals(type)) { urlMap.addInfo(new UrlInfo(UrlInfo.Type.Catalog, href)); litresRel = rel; } } if (urlMap.getInfo(UrlInfo.Type.Catalog) == null && urlMap.getInfo(UrlInfo.Type.HtmlPage) == null) { return null; } if (urlMap.getInfo(UrlInfo.Type.Catalog) != null && !urlIsAlternate) { urlMap.removeAllInfos(UrlInfo.Type.HtmlPage); } final CharSequence annotation; if (entry.Summary != null) { annotation = entry.Summary; } else if (entry.Content != null) { annotation = entry.Content; } else { annotation = null; } if (litresRel != null) { if (REL_BOOKSHELF.equals(litresRel)) { return new LitResBookshelfItem( opdsLink, entry.Title, annotation, urlMap ); } else if (REL_RECOMMENDATIONS.equals(litresRel)) { return new LitResRecommendationsItem( opdsLink, entry.Title, annotation, urlMap ); } else if (REL_BASKET.equals(litresRel)) { return null; /* return new BasketItem( opdsLink, entry.Title, annotation, urlMap ); */ } else if (REL_TOPUP.equals(litresRel)) { return new TopUpItem(opdsLink, urlMap); } else { return null; } } else { return new OPDSCatalogItem( opdsLink, entry.Title, annotation, urlMap, OPDSCatalogItem.Accessibility.ALWAYS, NetworkCatalogItem.FLAGS_DEFAULT ); } } }