+
+ @Override
+ protected void onError(Exception e) {
+ if (e instanceof SSLException) {
+ long now = System.currentTimeMillis();
+ System.out.println(StringUtils.fromTime(now) + ": "
+ + "[Client connection refused (bad key)]");
+ } else {
+ getTraceHandler().error(e);
+ }
+ }
+
+ /**
+ * 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);
+
+ 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>());
+ }
+
+ 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;
+
+ 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;
+ }
+
+ /**
+ * 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) {
+ boolean updateProgress = false;
+ if (rep instanceof Integer[] && ((Integer[]) rep).length == 3)
+ updateProgress = true;
+ if (rep instanceof Object[] && ((Object[]) rep).length >= 5
+ && "UPDATE".equals(((Object[]) rep)[0]))
+ updateProgress = true;
+
+ if (updateProgress) {
+ Object[] a = (Object[]) rep;
+
+ int offset = 0;
+ if (a[0] instanceof String) {
+ offset = 1;
+ }
+
+ int min = (Integer) a[0 + offset];
+ int max = (Integer) a[1 + offset];
+ int progress = (Integer) a[2 + offset];
+
+ Object meta = null;
+ if (a.length > (3 + offset)) {
+ meta = a[3 + offset];
+ }
+
+ String name = null;
+ if (a.length > (4 + offset)) {
+ name = a[4 + offset] == null ? "" : a[4 + offset].toString();
+ }
+
+ if (min >= 0 && min <= max) {
+ pg.setName(name);
+ pg.setMinMax(min, max);
+ pg.setProgress(progress);
+ if (meta != null) {
+ pg.put("meta", meta);
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * 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 Progress createPgForwarder(final ConnectActionServerObject action) {
+ final Boolean[] isDoneForwarded = new Boolean[] { false };
+ final Progress pg = new Progress() {
+ @Override
+ public boolean isDone() {
+ return isDoneForwarded[0];
+ }
+ };
+
+ final Integer[] p = new Integer[] { -1, -1, -1 };
+ final Object[] pMeta = new MetaData[1];
+ final String[] pName = new String[1];
+ final Long[] lastTime = new Long[] { new Date().getTime() };
+ pg.addProgressListener(new ProgressListener() {
+ @Override
+ public void progress(Progress progress, String name) {
+ Object meta = pg.get("meta");
+ if (meta instanceof MetaData) {
+ meta = removeCover((MetaData) meta);
+ }
+
+ int min = pg.getMin();
+ int max = pg.getMax();
+ int rel = min + (int) Math
+ .round(pg.getRelativeProgress() * (max - min));
+
+ boolean samePg = p[0] == min && p[1] == max && p[2] == rel;
+
+ // Do not re-send the same value twice over the wire,
+ // unless more than 2 seconds have elapsed (to maintain the
+ // connection)
+ if (!samePg || !same(pMeta[0], meta) || !same(pName[0], name) //
+ || (new Date().getTime() - lastTime[0] > 2000)) {
+ p[0] = min;
+ p[1] = max;
+ p[2] = rel;
+ pMeta[0] = meta;
+ pName[0] = name;
+
+ try {
+ action.send(new Object[] { "UPDATE", min, max, rel,
+ meta, name });
+ action.rec();
+ } catch (Exception e) {
+ getTraceHandler().error(e);
+ }
+
+ lastTime[0] = new Date().getTime();
+ }
+
+ isDoneForwarded[0] = (pg.getProgress() >= pg.getMax());
+ }
+ });
+
+ return pg;
+ }
+
+ private boolean same(Object obj1, Object obj2) {
+ if (obj1 == null || obj2 == null)
+ return obj1 == null && obj2 == null;
+
+ return obj1.equals(obj2);
+ }
+
+ // with 30 seconds timeout
+ private void forcePgDoneSent(Progress pg) {
+ long start = new Date().getTime();
+ pg.done();
+ while (!pg.isDone() && new Date().getTime() - start < 30000) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ getTraceHandler().error(e);
+ }
+ }
+ }
+
+ private MetaData removeCover(MetaData meta) {
+ MetaData light = null;
+ if (meta != null) {
+ if (meta.getCover() == null) {
+ light = meta;
+ } else {
+ light = meta.clone();
+ light.setCover(null);
+ }
+ }
+
+ return light;
+ }
+
+ private boolean isAllowed(MetaData meta, List<String> whitelist,
+ List<String> blacklist) {
+ MetaResultList one = new MetaResultList(Arrays.asList(meta));
+ if (!whitelist.isEmpty()) {
+ if (one.filter(whitelist, null, null).isEmpty()) {
+ return false;
+ }
+ }
+ if (!blacklist.isEmpty()) {
+ if (!one.filter(blacklist, null, null).isEmpty()) {
+ return false;
+ }
+ }
+
+ return true;
+ }