# Version WIP
- Bug fixes
-- Remote server/client improvements
+- Remote server/client improvements (progress report, can send large files)
- Better support for some CBZ files (if SUMMARY or URL files are present in it)
- Fix cover images not deleted on story delete
- Fix some images not supported because not jpeg-able (now try again in png)
-- Fix some covers not found (normal and remote hopefully)
+- Fix some covers not found over the wire (nikiroo-utils)
## Version 1.6.2
try {
String format = Instance.getConfig()
.getString(Config.IMAGE_FORMAT_CONTENT).toLowerCase();
- boolean ok = ImageIO.write(image, format, target);
- if (!ok) {
- // Some formats are not reliable
- // Second change: PNG
- if (!format.equals("png")) {
- ok = ImageIO.write(image, "png", target);
- }
- if (!ok) {
- throw new IOException(
- "Cannot find a writer for this image and format: "
- + format);
- }
+ boolean ok = false;
+ try {
+ ok = ImageIO.write(image, format, target);
+ } catch (IOException e) {
+ ok = false;
+ }
+
+ // Some formats are not reliable
+ // Second change: PNG
+ if (!ok && !format.equals("png")) {
+ ok = ImageIO.write(image, "png", target);
+ }
+
+ if (!ok) {
+ throw new IOException(
+ "Cannot find a writer for this image and format: "
+ + format);
}
} catch (IOException e) {
throw new IOException("Cannot write image to " + target, e);
}
@Override
- protected List<MetaData> getMetas(Progress pg) {
- // TODO: progress
- final List<MetaData> metas = new ArrayList<MetaData>();
- MetaData[] fromNetwork = this.getRemoteObject( //
- new Object[] { key, "GET_METADATA", "*" });
+ public BufferedImage getCover(final String luid) {
+ final BufferedImage[] result = new BufferedImage[1];
- if (fromNetwork != null) {
- for (MetaData meta : fromNetwork) {
- metas.add(meta);
- }
- }
+ try {
+ new ConnectActionClientObject(host, port, true) {
+ @Override
+ public void action(Version serverVersion) throws Exception {
+ Object rep = send(new Object[] { key, "GET_COVER", luid });
+ result[0] = (BufferedImage) rep;
+ }
- return metas;
- }
+ @Override
+ protected void onError(Exception e) {
+ Instance.getTraceHandler().error(e);
+ }
+ }.connect();
+ } catch (Exception e) {
+ Instance.getTraceHandler().error(e);
+ }
- @Override
- public BufferedImage getCover(final String luid) {
- return this.getRemoteObject( //
- new Object[] { key, "GET_COVER", luid });
+ return result[0];
}
@Override
public BufferedImage getSourceCover(final String source) {
- return this.getRemoteObject( //
- new Object[] { key, "GET_SOURCE_COVER", source });
+ final BufferedImage[] result = new BufferedImage[1];
+
+ try {
+ new ConnectActionClientObject(host, port, true) {
+ @Override
+ public void action(Version serverVersion) throws Exception {
+ Object rep = send(new Object[] { key, "GET_SOURCE_COVER",
+ source });
+ result[0] = (BufferedImage) rep;
+ }
+
+ @Override
+ protected void onError(Exception e) {
+ Instance.getTraceHandler().error(e);
+ }
+ }.connect();
+ } catch (Exception e) {
+ Instance.getTraceHandler().error(e);
+ }
+
+ return result[0];
}
@Override
public synchronized Story getStory(final String luid, Progress pg) {
- return this.getRemoteStory( //
- new Object[] { key, "GET_STORY", luid });
- }
+ final Progress pgF = pg;
+ final Story[] result = new Story[1];
- @Override
- protected void clearCache() {
+ try {
+ new ConnectActionClientObject(host, port, true) {
+ @Override
+ public void action(Version serverVersion) throws Exception {
+ Progress pg = pgF;
+ if (pg == null) {
+ pg = new Progress();
+ }
+
+ Object rep = send(new Object[] { key, "GET_STORY", luid });
+
+ MetaData meta = null;
+ if (rep instanceof MetaData) {
+ meta = (MetaData) rep;
+ if (meta.getWords() <= Integer.MAX_VALUE) {
+ pg.setMinMax(0, (int) meta.getWords());
+ }
+ }
+
+ List<Object> list = new ArrayList<Object>();
+ for (Object obj = send(null); obj != null; obj = send(null)) {
+ list.add(obj);
+ pg.add(1);
+ }
+
+ result[0] = RemoteLibraryServer.rebuildStory(list);
+ pg.done();
+ }
+
+ @Override
+ protected void onError(Exception e) {
+ Instance.getTraceHandler().error(e);
+ }
+ }.connect();
+ } catch (Exception e) {
+ Instance.getTraceHandler().error(e);
+ }
+
+ return result[0];
}
@Override
- public synchronized Story save(Story story, String luid, Progress pg)
- throws IOException {
- getRemoteObject(new Object[] { key, "SAVE_STORY", story, luid });
+ public synchronized Story save(final Story story, final String luid,
+ Progress pg) throws IOException {
+ final Progress pgF = pg;
+
+ new ConnectActionClientObject(host, port, true) {
+ @Override
+ public void action(Version serverVersion) throws Exception {
+ Progress pg = pgF;
+ if (pg == null) {
+ pg = new Progress();
+ }
+
+ if (story.getMeta().getWords() <= Integer.MAX_VALUE) {
+ pg.setMinMax(0, (int) story.getMeta().getWords());
+ }
+
+ send(new Object[] { key, "SAVE_STORY", luid });
+
+ List<Object> list = RemoteLibraryServer.breakStory(story);
+ for (Object obj : list) {
+ send(obj);
+ pg.add(1);
+ }
+
+ send(null);
+ pg.done();
+ }
+
+ @Override
+ protected void onError(Exception e) {
+ Instance.getTraceHandler().error(e);
+ }
+ }.connect();
// because the meta changed:
clearCache();
}
@Override
- public synchronized void delete(String luid) throws IOException {
- getRemoteObject(new Object[] { key, "DELETE_STORY", luid });
+ public synchronized void delete(final String luid) throws IOException {
+ new ConnectActionClientObject(host, port, true) {
+ @Override
+ public void action(Version serverVersion) throws Exception {
+ send(new Object[] { key, "DELETE_STORY", luid });
+ }
+
+ @Override
+ protected void onError(Exception e) {
+ Instance.getTraceHandler().error(e);
+ }
+ }.connect();
}
@Override
- public void setSourceCover(String source, String luid) {
- this.<BufferedImage> getRemoteObject( //
- new Object[] { key, "SET_SOURCE_COVER", source, luid });
+ public void setSourceCover(final String source, final String luid) {
+ try {
+ new ConnectActionClientObject(host, port, true) {
+ @Override
+ public void action(Version serverVersion) throws Exception {
+ send(new Object[] { key, "SET_SOURCE_COVER", source, luid });
+ }
+
+ @Override
+ protected void onError(Exception e) {
+ Instance.getTraceHandler().error(e);
+ }
+ }.connect();
+ } catch (IOException e) {
+ Instance.getTraceHandler().error(e);
+ }
}
@Override
"Operation not supportorted on remote Libraries");
}
- // 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");
- }
-
- /**
- * Return an object from the server.
- *
- * @param <T>
- * the expected type of object
- * @param command
- * the command to send (can contain at most ONE {@link Story})
- *
- * @return the object or NULL
- */
- private <T> T getRemoteObject(final Object[] command) {
- return getRemoteObjectOrStory(command, false);
- }
-
- /**
- * Return an object from the server.
- *
- * @param command
- * the command to send (can contain at most ONE {@link Story})
- *
- * @return the object or NULL
- */
- private Story getRemoteStory(final Object[] command) {
- return getRemoteObjectOrStory(command, true);
- }
+ protected List<MetaData> getMetas(Progress pg) {
+ final Progress pgF = pg;
+ final List<MetaData> metas = new ArrayList<MetaData>();
- /**
- * Return an object from the server.
- *
- * @param <T>
- * the expected type of object
- * @param command
- * the command to send (can contain at most ONE {@link Story})
- *
- * @return the object or NULL
- */
- @SuppressWarnings("unchecked")
- private <T> T getRemoteObjectOrStory(final Object[] command,
- final boolean getStory) {
- final Object[] result = new Object[1];
try {
new ConnectActionClientObject(host, port, true) {
@Override
public void action(Version serverVersion) throws Exception {
- Story story = null;
- for (int i = 0; i < command.length; i++) {
- if (command[i] instanceof Story) {
- story = (Story) command[i];
- command[i] = null;
- }
+ Progress pg = pgF;
+ if (pg == null) {
+ pg = new Progress();
}
- Object rep = send(command);
+ Object rep = send(new Object[] { key, "GET_METADATA", "*" });
- if (story != null) {
- RemoteLibraryServer.sendStory(story, this);
- }
+ while (true) {
+ if (!RemoteLibraryServer.updateProgress(pg, rep)) {
+ break;
+ }
- if (getStory) {
- rep = RemoteLibraryServer.recStory(this);
+ rep = send(null);
}
- result[0] = rep;
+ for (MetaData meta : (MetaData[]) rep) {
+ metas.add(meta);
+ }
}
@Override
Instance.getTraceHandler().error(e);
}
}.connect();
- } catch (IOException e) {
- Instance.getTraceHandler().error(e);
- }
-
- try {
- return (T) result[0];
} catch (Exception e) {
Instance.getTraceHandler().error(e);
- return null;
}
+
+ return metas;
+ }
+
+ @Override
+ protected void clearCache() {
+ }
+
+ // 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");
}
}
import be.nikiroo.fanfix.data.MetaData;
import be.nikiroo.fanfix.data.Paragraph;
import be.nikiroo.fanfix.data.Story;
+import be.nikiroo.utils.Progress;
+import be.nikiroo.utils.Progress.ProgressListener;
import be.nikiroo.utils.Version;
-import be.nikiroo.utils.serial.server.ConnectActionClientObject;
import be.nikiroo.utils.serial.server.ConnectActionServerObject;
import be.nikiroo.utils.serial.server.ServerObject;
public RemoteLibraryServer(String key, int port) throws IOException {
super("Fanfix remote library", port, true);
this.key = key;
+
+ setTraceHandler(Instance.getTraceHandler());
}
@Override
}
}
- System.out.print("[" + command + "] ");
+ String trace = "[" + command + "] ";
for (Object arg : args) {
- System.out.print(arg + " ");
+ trace += arg + " ";
}
- System.out.println("");
+ getTraceHandler().trace(trace);
if (!key.equals(this.key)) {
- System.out.println("Key rejected.");
- throw new SecurityException("Invalid key");
+ getTraceHandler().trace("Key rejected.");
+ return null;
}
- // TODO: progress (+send name + %age info back to client)
-
if ("GET_METADATA".equals(command)) {
if (args[0].equals("*")) {
- List<MetaData> metas = Instance.getLibrary().getMetas(null);
+ List<MetaData> metas = Instance.getLibrary().getMetas(
+ createPgForwarder(action));
return metas.toArray(new MetaData[] {});
}
throw new InvalidParameterException(
"only * is valid here, but you passed: " + args[0]);
} else if ("GET_STORY".equals(command)) {
+ MetaData meta = Instance.getLibrary().getInfo("" + args[0]);
+ meta = meta.clone();
+ meta.setCover(null);
+
+ action.send(meta);
+ action.rec();
+
Story story = Instance.getLibrary().getStory("" + args[0], null);
- sendStory(story, action);
+ for (Object obj : breakStory(story)) {
+ action.send(obj);
+ action.rec();
+ }
} else if ("SAVE_STORY".equals(command)) {
- Story story = recStory(action);
+ List<Object> list = new ArrayList<Object>();
+
+ action.send(null);
+ Object obj = action.rec();
+ while (obj != null) {
+ list.add(obj);
+ action.send(null);
+ obj = action.rec();
+ }
+
+ Story story = rebuildStory(list);
Instance.getLibrary().save(story, "" + args[1], null);
} else if ("DELETE_STORY".equals(command)) {
Instance.getLibrary().delete("" + args[0]);
return null;
}
- public static void sendStory(Story story, Object sender)
- throws NoSuchFieldException, NoSuchMethodException,
- ClassNotFoundException, IOException {
+ @Override
+ protected void onError(Exception e) {
+ getTraceHandler().error(e);
+ }
- if (!story.getMeta().isImageDocument()) {
- sendNextObject(sender, story);
- return;
- }
+ /**
+ * Break a story in multiple {@link Object}s for easier serialisation.
+ *
+ * @param story
+ * the {@link Story} to break
+ *
+ * @return the list of {@link Object}s
+ */
+ static List<Object> breakStory(Story story) {
+ List<Object> list = new ArrayList<Object>();
story = story.clone();
+ list.add(story);
- List<Chapter> chaps = story.getChapters();
- story.setChapters(new ArrayList<Chapter>());
- sendNextObject(sender, story);
-
- for (Chapter chap : chaps) {
- List<Paragraph> paras = chap.getParagraphs();
- chap.setParagraphs(new ArrayList<Paragraph>());
- sendNextObject(sender, chap);
-
- for (Paragraph para : paras) {
- sendNextObject(sender, para);
+ if (story.getMeta().isImageDocument()) {
+ for (Chapter chap : story) {
+ list.add(chap);
+ list.addAll(chap.getParagraphs());
+ chap.setParagraphs(new ArrayList<Paragraph>());
}
+ story.setChapters(new ArrayList<Chapter>());
}
- }
- public static Story recStory(Object source) throws NoSuchFieldException,
- NoSuchMethodException, ClassNotFoundException, IOException {
+ return list;
+ }
+ /**
+ * Rebuild a story from a list of broke up {@link Story} parts.
+ *
+ * @param list
+ * the list of {@link Story} parts
+ *
+ * @return the reconstructed {@link Story}
+ */
+ static Story rebuildStory(List<Object> list) {
Story story = null;
+ Chapter chap = null;
- Object obj = getNextObject(source);
- if (obj instanceof Story) {
- story = (Story) obj;
-
- Chapter current = null;
- for (obj = getNextObject(source); obj != null; obj = getNextObject(source)) {
- if (obj instanceof Chapter) {
- current = (Chapter) obj;
- story.getChapters().add(current);
- } else if (obj instanceof Paragraph) {
- current.getParagraphs().add((Paragraph) obj);
- }
+ for (Object obj : list) {
+ if (obj instanceof Story) {
+ story = (Story) obj;
+ } else if (obj instanceof Chapter) {
+ chap = (Chapter) obj;
+ story.getChapters().add(chap);
+ } else if (obj instanceof Paragraph) {
+ chap.getParagraphs().add((Paragraph) obj);
}
}
return story;
}
- private static Object getNextObject(Object clientOrServer)
- throws NoSuchFieldException, NoSuchMethodException,
- ClassNotFoundException, IOException {
- if (clientOrServer instanceof ConnectActionClientObject) {
- ConnectActionClientObject client = (ConnectActionClientObject) clientOrServer;
- return client.send(null);
- } else if (clientOrServer instanceof ConnectActionServerObject) {
- ConnectActionServerObject server = (ConnectActionServerObject) clientOrServer;
- Object obj = server.rec();
- server.send(null);
- return obj;
- } else {
- throw new ClassNotFoundException();
+ /**
+ * Update the {@link Progress} with the adequate {@link Object} received
+ * from the network via {@link RemoteLibraryServer}.
+ *
+ * @param pg
+ * the {@link Progress} to update
+ * @param rep
+ * the object received from the network
+ *
+ * @return TRUE if it was a progress event, FALSE if not
+ */
+ static boolean updateProgress(Progress pg, Object rep) {
+ if (rep instanceof Integer[]) {
+ Integer[] a = (Integer[]) rep;
+ if (a.length == 3) {
+ int min = a[0];
+ int max = a[1];
+ int progress = a[2];
+
+ if (min >= 0 && min <= max) {
+ pg.setMinMax(min, max);
+ pg.setProgress(progress);
+
+ return true;
+ }
+ }
}
+
+ return false;
}
- private static void sendNextObject(Object clientOrServer, Object obj)
- throws NoSuchFieldException, NoSuchMethodException,
- ClassNotFoundException, IOException {
- if (clientOrServer instanceof ConnectActionClientObject) {
- ConnectActionClientObject client = (ConnectActionClientObject) clientOrServer;
- client.send(obj);
- } else if (clientOrServer instanceof ConnectActionServerObject) {
- ConnectActionServerObject server = (ConnectActionServerObject) clientOrServer;
- server.send(obj);
- server.rec();
- } else {
- throw new ClassNotFoundException();
- }
+ /**
+ * Create a {@link Progress} that will forward its progress over the
+ * network.
+ *
+ * @param action
+ * the {@link ConnectActionServerObject} to use to forward it
+ *
+ * @return the {@link Progress}
+ */
+ private static Progress createPgForwarder(
+ final ConnectActionServerObject action) {
+ final Progress pg = new Progress();
+ final Integer[] p = new Integer[] { -1, -1, -1 };
+ pg.addProgressListener(new ProgressListener() {
+ @Override
+ public void progress(Progress progress, String name) {
+ int min = pg.getMin();
+ int max = pg.getMax();
+ int relativeProgress = min
+ + (int) Math.round(pg.getRelativeProgress()
+ * (max - min));
+
+ // Do not re-send the same value twice over the wire
+ if (p[0] != min || p[1] != max || p[2] != relativeProgress) {
+ p[0] = min;
+ p[1] = max;
+ p[2] = relativeProgress;
+
+ try {
+ action.send(new Integer[] { min, max, relativeProgress });
+ action.rec();
+ } catch (Exception e) {
+ Instance.getTraceHandler().error(e);
+ }
+ }
+ }
+ });
+
+ return pg;
}
}