New option: default library (for remote by default)
[nikiroo-utils.git] / src / be / nikiroo / fanfix / library / RemoteLibraryServer.java
CommitLineData
e42573a0 1package be.nikiroo.fanfix.library;
b0e88ebd 2
b0e88ebd 3import java.io.IOException;
085a2f9a 4import java.security.InvalidParameterException;
74a40dfb 5import java.util.ArrayList;
68e2c6d2 6import java.util.List;
b0e88ebd 7
e42573a0 8import be.nikiroo.fanfix.Instance;
74a40dfb 9import be.nikiroo.fanfix.data.Chapter;
b0e88ebd 10import be.nikiroo.fanfix.data.MetaData;
74a40dfb 11import be.nikiroo.fanfix.data.Paragraph;
085a2f9a 12import be.nikiroo.fanfix.data.Story;
b9ce9cad
NR
13import be.nikiroo.utils.Progress;
14import be.nikiroo.utils.Progress.ProgressListener;
b0e88ebd 15import be.nikiroo.utils.Version;
62c63b07
NR
16import be.nikiroo.utils.serial.server.ConnectActionServerObject;
17import be.nikiroo.utils.serial.server.ServerObject;
b0e88ebd 18
a85e8077
NR
19/**
20 * Create a new remote server that will listen for order on the given port.
21 * <p>
2a25f781
NR
22 * The available commands are given as arrays of objects (first item is the key,
23 * second is the command, the rest are the arguments).
24 * <p>
25 * The key is always a String, the commands are also Strings; the parameters
26 * vary depending upon the command.
a85e8077 27 * <ul>
3bbc86a5 28 * <li>[key] PING: will return PONG if the key is accepted</li>
2a25f781 29 * <li>[key] GET_METADATA *: will return the metadata of all the stories in the
085a2f9a 30 * library</li>
2a25f781 31 * <li>[key] GET_STORY [luid]: will return the given story if it exists (or NULL
2070ced5 32 * if not)</li>
74a40dfb
NR
33 * <li>[key] SAVE_STORY [luid]: save the story (that must be sent just after the
34 * command) with the given LUID</li>
2a25f781
NR
35 * <li>[key] DELETE_STORY [luid]: delete the story of LUID luid</li>
36 * <li>[key] GET_COVER [luid]: return the cover of the story</li>
37 * <li>[key] GET_SOURCE_COVER [source]: return the cover for this source</li>
38 * <li>[key] SET_SOURCE_COVER [source], [luid]: set the default cover for the
2070ced5 39 * given source to the cover of the story denoted by luid</li>
2a25f781 40 * <li>[key] EXIT: stop the server</li>
a85e8077
NR
41 * </ul>
42 *
43 * @author niki
44 */
62c63b07 45public class RemoteLibraryServer extends ServerObject {
2070ced5 46 private final String key;
b0e88ebd 47
a85e8077
NR
48 /**
49 * Create a new remote server (will not be active until
50 * {@link RemoteLibraryServer#start()} is called).
51 *
2070ced5
NR
52 * @param key
53 * the key that will restrict access to this server
a85e8077
NR
54 * @param port
55 * the port to listen on
56 *
57 * @throws IOException
58 * in case of I/O error
59 */
2070ced5 60 public RemoteLibraryServer(String key, int port) throws IOException {
22b2b942 61 super("Fanfix remote library", port, true);
2070ced5 62 this.key = key;
b9ce9cad
NR
63
64 setTraceHandler(Instance.getTraceHandler());
b0e88ebd
NR
65 }
66
67 @Override
62c63b07 68 protected Object onRequest(ConnectActionServerObject action,
b0e88ebd 69 Version clientVersion, Object data) throws Exception {
2070ced5 70 String key = "";
085a2f9a
NR
71 String command = "";
72 Object[] args = new Object[0];
73 if (data instanceof Object[]) {
2070ced5
NR
74 Object[] dataArray = (Object[]) data;
75 if (dataArray.length >= 2) {
76 args = new Object[dataArray.length - 2];
77 for (int i = 2; i < dataArray.length; i++) {
f569d249 78 args[i - 2] = dataArray[i];
2070ced5
NR
79 }
80
81 key = "" + dataArray[0];
82 command = "" + dataArray[1];
b0e88ebd
NR
83 }
84 }
85
b9ce9cad 86 String trace = "[" + command + "] ";
085a2f9a 87 for (Object arg : args) {
b9ce9cad 88 trace += arg + " ";
085a2f9a 89 }
b9ce9cad 90 getTraceHandler().trace(trace);
b0e88ebd 91
2070ced5 92 if (!key.equals(this.key)) {
b9ce9cad
NR
93 getTraceHandler().trace("Key rejected.");
94 return null;
2070ced5
NR
95 }
96
3bbc86a5
NR
97 if ("PING".equals(command)) {
98 return "PONG";
99 } else if ("GET_METADATA".equals(command)) {
2070ced5 100 if (args[0].equals("*")) {
b9ce9cad
NR
101 List<MetaData> metas = Instance.getLibrary().getMetas(
102 createPgForwarder(action));
a85e8077
NR
103 return metas.toArray(new MetaData[] {});
104 }
085a2f9a 105 throw new InvalidParameterException(
2070ced5 106 "only * is valid here, but you passed: " + args[0]);
a85e8077 107 } else if ("GET_STORY".equals(command)) {
b9ce9cad
NR
108 MetaData meta = Instance.getLibrary().getInfo("" + args[0]);
109 meta = meta.clone();
110 meta.setCover(null);
111
112 action.send(meta);
113 action.rec();
114
74a40dfb 115 Story story = Instance.getLibrary().getStory("" + args[0], null);
b9ce9cad
NR
116 for (Object obj : breakStory(story)) {
117 action.send(obj);
118 action.rec();
119 }
085a2f9a 120 } else if ("SAVE_STORY".equals(command)) {
b9ce9cad
NR
121 List<Object> list = new ArrayList<Object>();
122
123 action.send(null);
124 Object obj = action.rec();
125 while (obj != null) {
126 list.add(obj);
127 action.send(null);
128 obj = action.rec();
129 }
130
131 Story story = rebuildStory(list);
74a40dfb 132 Instance.getLibrary().save(story, "" + args[1], null);
085a2f9a 133 } else if ("DELETE_STORY".equals(command)) {
2070ced5 134 Instance.getLibrary().delete("" + args[0]);
e604986c 135 } else if ("GET_COVER".equals(command)) {
2070ced5 136 return Instance.getLibrary().getCover("" + args[0]);
085a2f9a 137 } else if ("GET_SOURCE_COVER".equals(command)) {
2070ced5 138 return Instance.getLibrary().getSourceCover("" + args[0]);
085a2f9a 139 } else if ("SET_SOURCE_COVER".equals(command)) {
2070ced5 140 Instance.getLibrary().setSourceCover("" + args[0], "" + args[1]);
5e848e6a
NR
141 } else if ("EXIT".equals(command)) {
142 stop(0, false);
b0e88ebd
NR
143 }
144
145 return null;
146 }
74a40dfb 147
b9ce9cad
NR
148 @Override
149 protected void onError(Exception e) {
150 getTraceHandler().error(e);
151 }
74a40dfb 152
b9ce9cad
NR
153 /**
154 * Break a story in multiple {@link Object}s for easier serialisation.
155 *
156 * @param story
157 * the {@link Story} to break
158 *
159 * @return the list of {@link Object}s
160 */
161 static List<Object> breakStory(Story story) {
162 List<Object> list = new ArrayList<Object>();
74a40dfb
NR
163
164 story = story.clone();
b9ce9cad 165 list.add(story);
74a40dfb 166
b9ce9cad
NR
167 if (story.getMeta().isImageDocument()) {
168 for (Chapter chap : story) {
169 list.add(chap);
170 list.addAll(chap.getParagraphs());
171 chap.setParagraphs(new ArrayList<Paragraph>());
74a40dfb 172 }
b9ce9cad 173 story.setChapters(new ArrayList<Chapter>());
74a40dfb 174 }
74a40dfb 175
b9ce9cad
NR
176 return list;
177 }
74a40dfb 178
b9ce9cad
NR
179 /**
180 * Rebuild a story from a list of broke up {@link Story} parts.
181 *
182 * @param list
183 * the list of {@link Story} parts
184 *
185 * @return the reconstructed {@link Story}
186 */
187 static Story rebuildStory(List<Object> list) {
74a40dfb 188 Story story = null;
b9ce9cad 189 Chapter chap = null;
74a40dfb 190
b9ce9cad
NR
191 for (Object obj : list) {
192 if (obj instanceof Story) {
193 story = (Story) obj;
194 } else if (obj instanceof Chapter) {
195 chap = (Chapter) obj;
196 story.getChapters().add(chap);
197 } else if (obj instanceof Paragraph) {
198 chap.getParagraphs().add((Paragraph) obj);
74a40dfb
NR
199 }
200 }
201
202 return story;
203 }
204
b9ce9cad
NR
205 /**
206 * Update the {@link Progress} with the adequate {@link Object} received
207 * from the network via {@link RemoteLibraryServer}.
208 *
209 * @param pg
210 * the {@link Progress} to update
211 * @param rep
212 * the object received from the network
213 *
214 * @return TRUE if it was a progress event, FALSE if not
215 */
216 static boolean updateProgress(Progress pg, Object rep) {
217 if (rep instanceof Integer[]) {
218 Integer[] a = (Integer[]) rep;
219 if (a.length == 3) {
220 int min = a[0];
221 int max = a[1];
222 int progress = a[2];
223
224 if (min >= 0 && min <= max) {
225 pg.setMinMax(min, max);
226 pg.setProgress(progress);
227
228 return true;
229 }
230 }
74a40dfb 231 }
b9ce9cad
NR
232
233 return false;
74a40dfb
NR
234 }
235
b9ce9cad
NR
236 /**
237 * Create a {@link Progress} that will forward its progress over the
238 * network.
239 *
240 * @param action
241 * the {@link ConnectActionServerObject} to use to forward it
242 *
243 * @return the {@link Progress}
244 */
245 private static Progress createPgForwarder(
246 final ConnectActionServerObject action) {
247 final Progress pg = new Progress();
248 final Integer[] p = new Integer[] { -1, -1, -1 };
249 pg.addProgressListener(new ProgressListener() {
250 @Override
251 public void progress(Progress progress, String name) {
252 int min = pg.getMin();
253 int max = pg.getMax();
254 int relativeProgress = min
255 + (int) Math.round(pg.getRelativeProgress()
256 * (max - min));
257
258 // Do not re-send the same value twice over the wire
259 if (p[0] != min || p[1] != max || p[2] != relativeProgress) {
260 p[0] = min;
261 p[1] = max;
262 p[2] = relativeProgress;
263
264 try {
265 action.send(new Integer[] { min, max, relativeProgress });
266 action.rec();
267 } catch (Exception e) {
268 Instance.getTraceHandler().error(e);
269 }
270 }
271 }
272 });
273
274 return pg;
74a40dfb 275 }
b0e88ebd 276}