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