merge from master
[nikiroo-utils.git] / library / WebLibrary.java
1 package be.nikiroo.fanfix.library;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.net.URL;
7 import java.util.ArrayList;
8 import java.util.HashMap;
9 import java.util.List;
10 import java.util.Map;
11
12 import org.json.JSONArray;
13 import org.json.JSONObject;
14
15 import be.nikiroo.fanfix.Instance;
16 import be.nikiroo.fanfix.data.Chapter;
17 import be.nikiroo.fanfix.data.JsonIO;
18 import be.nikiroo.fanfix.data.MetaData;
19 import be.nikiroo.fanfix.data.Paragraph;
20 import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
21 import be.nikiroo.fanfix.data.Story;
22 import be.nikiroo.utils.IOUtils;
23 import be.nikiroo.utils.Image;
24 import be.nikiroo.utils.Progress;
25
26 /**
27 * This {@link BasicLibrary} will access a remote server to list the available
28 * stories, and download the ones you try to load to the local directory
29 * specified in the configuration.
30 * <p>
31 * This remote library uses http:// or https://.
32 *
33 * @author niki
34 */
35 public class WebLibrary extends BasicLibrary {
36 private String host;
37 private int port;
38 private final String key;
39 private final String subkey;
40
41 // informative only (server will make the actual checks)
42 private boolean rw;
43
44 /**
45 * Create a {@link RemoteLibrary} linked to the given server.
46 * <p>
47 * Note that the key is structured:
48 * <tt><b><i>xxx</i></b>(|<b><i>yyy</i></b>|<b>wl</b>)(|<b>rw</b>)</tt>
49 * <p>
50 * Note that anything before the first pipe (<tt>|</tt>) character is
51 * considered to be the encryption key, anything after that character is
52 * called the subkey (including the other pipe characters and flags!).
53 * <p>
54 * This is important because the subkey (including the pipe characters and
55 * flags) must be present as-is in the server configuration file to be
56 * allowed.
57 * <ul>
58 * <li><b><i>xxx</i></b>: the encryption key used to communicate with the
59 * server</li>
60 * <li><b><i>yyy</i></b>: the secondary key</li>
61 * <li><b>rw</b>: flag to allow read and write access if it is not the
62 * default on this server</li>
63 * <li><b>wl</b>: flag to allow access to all the stories (bypassing the
64 * whitelist if it exists)</li>
65 * </ul>
66 * <p>
67 * Some examples:
68 * <ul>
69 * <li><b>my_key</b>: normal connection, will take the default server
70 * options</li>
71 * <li><b>my_key|agzyzz|wl</b>: will ask to bypass the white list (if it
72 * exists)</li>
73 * <li><b>my_key|agzyzz|rw</b>: will ask read-write access (if the default
74 * is read-only)</li>
75 * <li><b>my_key|agzyzz|wl|rw</b>: will ask both read-write access and white
76 * list bypass</li>
77 * </ul>
78 *
79 * @param key
80 * the key that will allow us to exchange information with the
81 * server
82 * @param host
83 * the host to contact or NULL for localhost
84 * @param port
85 * the port to contact it on
86 */
87 public WebLibrary(String key, String host, int port) {
88 int index = -1;
89 if (key != null) {
90 index = key.indexOf('|');
91 }
92
93 if (index >= 0) {
94 this.key = key.substring(0, index);
95 this.subkey = key.substring(index + 1);
96 } else {
97 this.key = key;
98 this.subkey = "";
99 }
100
101 this.rw = subkey.contains("|rw");
102
103 this.host = host;
104 this.port = port;
105
106 // TODO: not supported yet
107 this.rw = false;
108 }
109
110 @Override
111 public Status getStatus() {
112 try {
113 download("/");
114 } catch (IOException e) {
115 try {
116 download("/style.css");
117 return Status.UNAUTHORIZED;
118 } catch (IOException ioe) {
119 return Status.INVALID;
120 }
121 }
122
123 return rw ? Status.READ_WRITE : Status.READ_ONLY;
124 }
125
126 @Override
127 public String getLibraryName() {
128 return (rw ? "[READ-ONLY] " : "") + host + ":" + port;
129 }
130
131 @Override
132 public Image getCover(String luid) throws IOException {
133 InputStream in = download("/story/" + luid + "/cover");
134 if (in != null) {
135 return new Image(in);
136 }
137
138 return null;
139 }
140
141 @Override
142 public Image getCustomSourceCover(final String source) throws IOException {
143 // TODO maybe global system in BasicLib ?
144 return null;
145 }
146
147 @Override
148 public Image getCustomAuthorCover(final String author) throws IOException {
149 // TODO maybe global system in BasicLib ?
150 return null;
151 }
152
153 @Override
154 public void setSourceCover(String source, String luid) throws IOException {
155 // TODO Auto-generated method stub
156 throw new IOException("Not implemented yet");
157 }
158
159 @Override
160 public void setAuthorCover(String author, String luid) throws IOException {
161 // TODO Auto-generated method stub
162 throw new IOException("Not implemented yet");
163 }
164
165 @Override
166 public synchronized Story getStory(final String luid, Progress pg)
167 throws IOException {
168
169 // TODO: pg
170
171 Story story;
172 InputStream in = download("/story/" + luid + "/json");
173 try {
174 JSONObject json = new JSONObject(IOUtils.readSmallStream(in));
175 story = JsonIO.toStory(json);
176 } finally {
177 in.close();
178 }
179
180 story.getMeta().setCover(getCover(luid));
181 int chapNum = 1;
182 for (Chapter chap : story) {
183 int number = 1;
184 for (Paragraph para : chap) {
185 if (para.getType() == ParagraphType.IMAGE) {
186 InputStream subin = download(
187 "/story/" + luid + "/" + chapNum + "/" + number);
188 try {
189 para.setContentImage(new Image(subin));
190 } finally {
191 subin.close();
192 }
193 }
194
195 number++;
196 }
197
198 chapNum++;
199 }
200
201 return story;
202 }
203
204 @Override
205 protected List<MetaData> getMetas(Progress pg) throws IOException {
206 List<MetaData> metas = new ArrayList<MetaData>();
207 InputStream in = download("/list/luids");
208 JSONArray jsonArr = new JSONArray(IOUtils.readSmallStream(in));
209 for (int i = 0; i < jsonArr.length(); i++) {
210 JSONObject json = jsonArr.getJSONObject(i);
211 metas.add(JsonIO.toMetaData(json));
212 }
213
214 return metas;
215 }
216
217 @Override
218 // Could work (more slowly) without it
219 public MetaData imprt(final URL url, Progress pg) throws IOException {
220 if (true)
221 throw new IOException("Not implemented yet");
222
223 // Import the file locally if it is actually a file
224
225 if (url == null || url.getProtocol().equalsIgnoreCase("file")) {
226 return super.imprt(url, pg);
227 }
228
229 // Import it remotely if it is an URL
230
231 // TODO
232 return super.imprt(url, pg);
233 }
234
235 @Override
236 // Could work (more slowly) without it
237 protected synchronized void changeSTA(final String luid,
238 final String newSource, final String newTitle,
239 final String newAuthor, Progress pg) throws IOException {
240 // TODO
241 super.changeSTA(luid, newSource, newTitle, newAuthor, pg);
242 }
243
244 @Override
245 protected void updateInfo(MetaData meta) {
246 // Will be taken care of directly server side
247 }
248
249 @Override
250 protected void invalidateInfo(String luid) {
251 // Will be taken care of directly server side
252 }
253
254 // The following methods are only used by Save and Delete in BasicLibrary:
255
256 @Override
257 protected int getNextId() {
258 throw new java.lang.InternalError("Should not have been called");
259 }
260
261 @Override
262 protected void doDelete(String luid) throws IOException {
263 throw new java.lang.InternalError("Should not have been called");
264 }
265
266 @Override
267 protected Story doSave(Story story, Progress pg) throws IOException {
268 throw new java.lang.InternalError("Should not have been called");
269 }
270
271 //
272
273 @Override
274 public File getFile(final String luid, Progress pg) {
275 throw new java.lang.InternalError(
276 "Operation not supportorted on remote Libraries");
277 }
278
279 // starts with "/"
280 private InputStream download(String path) throws IOException {
281 URL url = new URL(host + ":" + port + path);
282
283 Map<String, String> post = new HashMap<String, String>();
284 post.put("login", subkey);
285 post.put("password", key);
286
287 return Instance.getInstance().getCache().openNoCache(url, null, post,
288 null, null);
289 }
290 }