package be.nikiroo.fanfix.library; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.json.JSONArray; import org.json.JSONObject; import be.nikiroo.fanfix.Instance; import be.nikiroo.fanfix.data.Chapter; import be.nikiroo.fanfix.data.JsonIO; import be.nikiroo.fanfix.data.MetaData; import be.nikiroo.fanfix.data.Paragraph; import be.nikiroo.fanfix.data.Paragraph.ParagraphType; import be.nikiroo.fanfix.data.Story; import be.nikiroo.utils.IOUtils; import be.nikiroo.utils.Image; import be.nikiroo.utils.Progress; import be.nikiroo.utils.Version; /** * This {@link BasicLibrary} will access a remote server to list the available * stories, and download the ones you try to load to the local directory * specified in the configuration. *

* This remote library uses http:// or https://. * * @author niki */ public class WebLibrary extends BasicLibrary { private String host; private int port; private final String key; private final String subkey; // informative only (server will make the actual checks) private boolean rw; /** * Create a {@link RemoteLibrary} linked to the given server. *

* Note that the key is structured: * xxx(|yyy|wl)(|rw) *

* Note that anything before the first pipe (|) character is * considered to be the encryption key, anything after that character is * called the subkey (including the other pipe characters and flags!). *

* This is important because the subkey (including the pipe characters and * flags) must be present as-is in the server configuration file to be * allowed. *

*

* Some examples: *

* * @param key * the key that will allow us to exchange information with the * server * @param host * the host to contact or NULL for localhost * @param port * the port to contact it on */ public WebLibrary(String key, String host, int port) { int index = -1; if (key != null) { index = key.indexOf('|'); } if (index >= 0) { this.key = key.substring(0, index); this.subkey = key.substring(index + 1); } else { this.key = key; this.subkey = ""; } this.rw = subkey.contains("|rw"); this.host = host; this.port = port; // TODO: not supported yet this.rw = false; } public Version getVersion() { try { InputStream in = download(WebLibraryUrls.VERSION_URL); try { return new Version(IOUtils.readSmallStream(in)); } finally { in.close(); } } catch (IOException e) { } return new Version(); } @Override public Status getStatus() { try { download(WebLibraryUrls.INDEX_URL).close(); } catch (IOException e) { try { download(WebLibraryUrls.VERSION_URL).close(); return Status.UNAUTHORIZED; } catch (IOException ioe) { return Status.UNAVAILABLE; } } return rw ? Status.READ_WRITE : Status.READ_ONLY; } @Override public String getLibraryName() { return (rw ? "[READ-ONLY] " : "") + host + ":" + port + " (" + getVersion() + ")"; } @Override public Image getCover(String luid) throws IOException { InputStream in = download(WebLibraryUrls.getStoryUrlCover(luid)); try { return new Image(in); } finally { in.close(); } } @Override public Image getCustomSourceCover(final String source) throws IOException { // TODO maybe global system in BasicLib ? return null; } @Override public Image getCustomAuthorCover(final String author) throws IOException { // TODO maybe global system in BasicLib ? return null; } @Override public void setSourceCover(String source, String luid) throws IOException { // TODO Auto-generated method stub throw new IOException("Not implemented yet"); } @Override public void setAuthorCover(String author, String luid) throws IOException { // TODO Auto-generated method stub throw new IOException("Not implemented yet"); } @Override public synchronized Story getStory(final String luid, Progress pg) throws IOException { // TODO: pg Story story; InputStream in = download(WebLibraryUrls.getStoryUrlJson(luid)); try { JSONObject json = new JSONObject(IOUtils.readSmallStream(in)); story = JsonIO.toStory(json); } finally { in.close(); } story.getMeta().setCover(getCover(luid)); int chapNum = 1; for (Chapter chap : story) { int number = 1; for (Paragraph para : chap) { if (para.getType() == ParagraphType.IMAGE) { InputStream subin = download( WebLibraryUrls.getStoryUrl(luid, chapNum, number)); try { para.setContentImage(new Image(subin)); } finally { subin.close(); } } number++; } chapNum++; } return story; } @Override protected List getMetas(Progress pg) throws IOException { List metas = new ArrayList(); InputStream in = download(WebLibraryUrls.LIST_URL_METADATA); JSONArray jsonArr = new JSONArray(IOUtils.readSmallStream(in)); for (int i = 0; i < jsonArr.length(); i++) { JSONObject json = jsonArr.getJSONObject(i); metas.add(JsonIO.toMetaData(json)); } return metas; } @Override // Could work (more slowly) without it public MetaData imprt(final URL url, Progress pg) throws IOException { if (true) throw new IOException("Not implemented yet"); // Import the file locally if it is actually a file if (url == null || url.getProtocol().equalsIgnoreCase("file")) { return super.imprt(url, pg); } // Import it remotely if it is an URL // TODO return super.imprt(url, pg); } @Override // Could work (more slowly) without it protected synchronized void changeSTA(final String luid, final String newSource, final String newTitle, final String newAuthor, Progress pg) throws IOException { // TODO super.changeSTA(luid, newSource, newTitle, newAuthor, pg); } @Override protected void updateInfo(MetaData meta) { // Will be taken care of directly server side } @Override protected void invalidateInfo(String luid) { // Will be taken care of directly server side } // The following methods are only used by Save and Delete in BasicLibrary: @Override protected int getNextId() { throw new java.lang.InternalError("Should not have been called"); } @Override protected void doDelete(String luid) throws IOException { throw new java.lang.InternalError("Should not have been called"); } @Override protected Story doSave(Story story, Progress pg) throws IOException { throw new java.lang.InternalError("Should not have been called"); } // @Override public File getFile(final String luid, Progress pg) { throw new java.lang.InternalError( "Operation not supportorted on remote Libraries"); } // starts with "/", never NULL private InputStream download(String path) throws IOException { URL url = new URL(host + ":" + port + path); Map post = new HashMap(); post.put("login", subkey); post.put("password", key); return Instance.getInstance().getCache().openNoCache(url, null, post, null, null); } }