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