/* * 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 java.io.*; import org.geometerplus.zlibrary.core.network.*; import org.geometerplus.zlibrary.core.util.MimeType; import org.geometerplus.zlibrary.core.util.ZLNetworkUtil; import org.geometerplus.fbreader.network.NetworkBookItem; import org.geometerplus.fbreader.network.atom.*; import org.geometerplus.fbreader.network.urlInfo.*; public class OPDSBookItem extends NetworkBookItem implements OPDSConstants { private static CharSequence getAnnotation(OPDSEntry entry) { if (entry.Content != null) { return entry.Content; } if (entry.Summary != null) { return entry.Summary; } return null; } private static List<AuthorData> getAuthors(OPDSEntry entry) { final String AuthorPrefix = "author:"; final String AuthorsPrefix = "authors:"; final LinkedList<AuthorData> authors = new LinkedList<AuthorData>(); for (ATOMAuthor author: entry.Authors) { String name = author.Name; final String lowerCased = name.toLowerCase(); int index = lowerCased.indexOf(AuthorPrefix); if (index != -1) { name = name.substring(index + AuthorPrefix.length()); } else { index = lowerCased.indexOf(AuthorsPrefix); if (index != -1) { name = name.substring(index + AuthorsPrefix.length()); } } index = name.indexOf(','); final AuthorData authorData; if (index != -1) { final String before = name.substring(0, index).trim(); final String after = name.substring(index + 1).trim(); authorData = new AuthorData(after + ' ' + before, before); } else { name = name.trim(); index = name.lastIndexOf(' '); authorData = new AuthorData(name, name.substring(index + 1)); } authors.add(authorData); } return authors; } private static List<String> getTags(OPDSEntry entry) { final LinkedList<String> tags = new LinkedList<String>(); for (ATOMCategory category : entry.Categories) { String label = category.getLabel(); if (label == null) { label = category.getTerm(); } if (label != null) { tags.add(label); } } return tags; } private static UrlInfoCollection<UrlInfo> getUrls(OPDSNetworkLink networkLink, OPDSEntry entry, String baseUrl) { final UrlInfoCollection<UrlInfo> urls = new UrlInfoCollection<UrlInfo>(); for (ATOMLink link: entry.Links) { final String href = ZLNetworkUtil.url(baseUrl, link.getHref()); final MimeType type = MimeType.get(link.getType()); final String rel = networkLink.relation(link.getRel(), type); final UrlInfo.Type referenceType = typeByRelation(rel); if (REL_IMAGE_THUMBNAIL.equals(rel) || REL_THUMBNAIL.equals(rel)) { if (MimeType.IMAGE_PNG.equals(type) || MimeType.IMAGE_JPEG.equals(type)) { urls.addInfo(new UrlInfo(UrlInfo.Type.Thumbnail, href)); } } else if ((rel != null && rel.startsWith(REL_IMAGE_PREFIX)) || REL_COVER.equals(rel)) { if (MimeType.IMAGE_PNG.equals(type) || MimeType.IMAGE_JPEG.equals(type)) { urls.addInfo(new UrlInfo(UrlInfo.Type.Image, href)); } } else if (MimeType.APP_ATOM.Name.equals(type.Name) && "entry".equals(type.getParameter("type"))) { urls.addInfo(new UrlInfo(UrlInfo.Type.SingleEntry, href)); } else if (UrlInfo.Type.BookBuy == referenceType) { final OPDSLink opdsLink = (OPDSLink)link; String price = null; final OPDSPrice opdsPrice = opdsLink.selectBestPrice(); if (opdsPrice != null) { price = BookBuyUrlInfo.price(opdsPrice.Price, opdsPrice.Currency); } if (price == null) { // FIXME: HACK: price handling must be implemented not through attributes!!! price = BookBuyUrlInfo.price(entry.getAttribute(OPDSXMLReader.KEY_PRICE), null); } if (price == null) { price = ""; } if (MimeType.TEXT_HTML.equals(type)) { collectReferences(urls, opdsLink, href, UrlInfo.Type.BookBuyInBrowser, price, true); } else { collectReferences(urls, opdsLink, href, UrlInfo.Type.BookBuy, price, false); } } else if (referenceType == UrlInfo.Type.Related) { urls.addInfo(new RelatedUrlInfo(referenceType, link.getTitle(), type, href)); } else if (referenceType == UrlInfo.Type.Comments) { urls.addInfo(new RelatedUrlInfo(referenceType, link.getTitle(), type, href)); } else if (referenceType == UrlInfo.Type.TOC) { urls.addInfo(new UrlInfo(referenceType, href)); } else if (referenceType != null) { final int format = formatByMimeType(type); if (format != BookUrlInfo.Format.NONE) { urls.addInfo(new BookUrlInfo(referenceType, format, href)); } } } return urls; } private static UrlInfo.Type typeByRelation(String rel) { if (rel == null || REL_ACQUISITION.equals(rel) || REL_ACQUISITION_OPEN.equals(rel)) { return UrlInfo.Type.Book; } else if (REL_ACQUISITION_SAMPLE.equals(rel)) { return UrlInfo.Type.BookDemo; } else if (REL_ACQUISITION_CONDITIONAL.equals(rel)) { return UrlInfo.Type.BookConditional; } else if (REL_ACQUISITION_SAMPLE_OR_FULL.equals(rel)) { return UrlInfo.Type.BookFullOrDemo; } else if (REL_ACQUISITION_BUY.equals(rel)) { return UrlInfo.Type.BookBuy; } else if (REL_RELATED.equals(rel)) { return UrlInfo.Type.Related; } else if (REL_CONTENTS.equals(rel)) { return UrlInfo.Type.TOC; } else if (REL_REPLIES.equals(rel)) { return UrlInfo.Type.Comments; } else { return null; } } private static void collectReferences( UrlInfoCollection<UrlInfo> urls, OPDSLink opdsLink, String href, UrlInfo.Type type, String price, boolean addWithoutFormat ) { boolean added = false; for (String mime : opdsLink.Formats) { final int format = formatByMimeType(MimeType.get(mime)); if (format != BookUrlInfo.Format.NONE) { urls.addInfo(new BookBuyUrlInfo(type, format, href, price)); added = true; } } if (!added && addWithoutFormat) { urls.addInfo(new BookBuyUrlInfo(type, BookUrlInfo.Format.NONE, href, price)); } } static int formatByMimeType(MimeType type) { if (MimeType.APP_FB2ZIP.equals(type)) { return BookUrlInfo.Format.FB2_ZIP; } else if (MimeType.APP_EPUB.equals(type)) { return BookUrlInfo.Format.EPUB; } else if (MimeType.APP_MOBI.equals(type)) { return BookUrlInfo.Format.MOBIPOCKET; } return BookUrlInfo.Format.NONE; } public OPDSBookItem( OPDSNetworkLink link, String id, int index, CharSequence title, CharSequence summary, List<AuthorData> authors, List<String> tags, String seriesTitle, float indexInSeries, UrlInfoCollection<?> urls ) { super( link, id, index, title, summary, authors, tags, seriesTitle, indexInSeries, urls ); } OPDSBookItem(OPDSNetworkLink networkLink, OPDSEntry entry, String baseUrl, int index) { this( networkLink, entry.Id.Uri, index, entry.Title, getAnnotation(entry), getAuthors(entry), getTags(entry), entry.SeriesTitle, entry.SeriesIndex, getUrls(networkLink, entry, baseUrl) ); } private volatile boolean myInformationIsFull; @Override public synchronized boolean isFullyLoaded() { return myInformationIsFull || getUrl(UrlInfo.Type.SingleEntry) == null; } @Override public synchronized void loadFullInformation() throws ZLNetworkException { if (myInformationIsFull) { return; } final String url = getUrl(UrlInfo.Type.SingleEntry); if (url == null) { myInformationIsFull = true; return; } ZLNetworkManager.Instance().perform(new ZLNetworkRequest(url) { @Override public void handleStream(InputStream inputStream, int length) throws IOException, ZLNetworkException { new OPDSXMLReader( new SingleEntryFeedHandler(url), true ).read(inputStream); myInformationIsFull = true; } }); } @Override public OPDSCatalogItem createRelatedCatalogItem(RelatedUrlInfo info) { if (MimeType.APP_ATOM.equals(info.Mime)) { return new OPDSCatalogItem((OPDSNetworkLink)Link, info); } return null; } private class SingleEntryFeedHandler implements ATOMFeedHandler<OPDSFeedMetadata,OPDSEntry> { private final String myUrl; SingleEntryFeedHandler(String url) { myUrl = url; } public void processFeedStart() { } public boolean processFeedMetadata(OPDSFeedMetadata feed, boolean beforeEntries) { return false; } public boolean processFeedEntry(OPDSEntry entry) { addUrls(getUrls((OPDSNetworkLink)Link, entry, myUrl)); final CharSequence summary = getAnnotation(entry); if (summary != null) { setSummary(summary); } return false; } public void processFeedEnd() { } } }