import java.io.IOException;
import java.net.URL;
import java.net.UnknownHostException;
-import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.TreeMap;
import be.nikiroo.fanfix.Instance;
* @author niki
*/
public enum Status {
- /** The library is ready. */
- READY,
+ /** The library is ready and r/w. */
+ READ_WRITE,
+ /** The library is ready, but read-only. */
+ READ_ONLY,
/** The library is invalid (not correctly set up). */
INVALID,
/** You are not allowed to access this library. */
- UNAUTORIZED,
+ UNAUTHORIZED,
/** The library is currently out of commission. */
- UNAVAILABLE,
+ UNAVAILABLE;
+
+ /**
+ * The library is available (you can query it).
+ * <p>
+ * It does <b>not</b> specify if it is read-only or not.
+ *
+ * @return TRUE if it is
+ */
+ public boolean isReady() {
+ return (this == READ_WRITE || this == READ_ONLY);
+ }
+
+ /**
+ * This library can be modified (= you are allowed to modify it).
+ *
+ * @return TRUE if it is
+ */
+ public boolean isWritable() {
+ return (this == READ_WRITE);
+ }
}
/**
* @return the current status
*/
public Status getStatus() {
- return Status.READY;
+ return Status.READ_WRITE;
}
/**
* the optional {@link Progress}
*
* @return the corresponding {@link Story}
+ *
+ * @throws IOException
+ * in case of IOException
*/
- public abstract File getFile(String luid, Progress pg);
+ public abstract File getFile(String luid, Progress pg) throws IOException;
/**
* Return the cover image associated to this story.
* the Library UID of the story
*
* @return the cover image
+ *
+ * @throws IOException
+ * in case of IOException
*/
- public abstract Image getCover(String luid);
+ public abstract Image getCover(String luid) throws IOException;
/**
* Return the cover image associated to this source.
* the source
*
* @return the cover image or NULL
+ *
+ * @throws IOException
+ * in case of IOException
*/
- public Image getSourceCover(String source) {
+ public Image getSourceCover(String source) throws IOException {
Image custom = getCustomSourceCover(source);
if (custom != null) {
return custom;
return null;
}
+ /**
+ * Return the cover image associated to this author.
+ * <p>
+ * By default, return the custom cover if any, and if not, return the cover
+ * of the first story with this author.
+ *
+ * @param author
+ * the author
+ *
+ * @return the cover image or NULL
+ *
+ * @throws IOException
+ * in case of IOException
+ */
+ public Image getAuthorCover(String author) throws IOException {
+ Image custom = getCustomAuthorCover(author);
+ if (custom != null) {
+ return custom;
+ }
+
+ List<MetaData> metas = getListByAuthor(author);
+ if (metas.size() > 0) {
+ return getCover(metas.get(0).getLuid());
+ }
+
+ return null;
+ }
+
/**
* Return the custom cover image associated to this source.
* <p>
* the source to look for
*
* @return the custom cover or NULL if none
+ *
+ * @throws IOException
+ * in case of IOException
+ */
+ @SuppressWarnings("unused")
+ public Image getCustomSourceCover(String source) throws IOException {
+ return null;
+ }
+
+ /**
+ * Return the custom cover image associated to this author.
+ * <p>
+ * By default, return NULL.
+ *
+ * @param author
+ * the author to look for
+ *
+ * @return the custom cover or NULL if none
+ *
+ * @throws IOException
+ * in case of IOException
*/
- public Image getCustomSourceCover(@SuppressWarnings("unused") String source) {
+ @SuppressWarnings("unused")
+ public Image getCustomAuthorCover(String author) throws IOException {
return null;
}
/**
- * Fix the source cover to the given story cover.
+ * Set the source cover to the given story cover.
*
* @param source
* the source to change
* @param luid
* the story LUID
+ *
+ * @throws IOException
+ * in case of IOException
+ */
+ public abstract void setSourceCover(String source, String luid)
+ throws IOException;
+
+ /**
+ * Set the author cover to the given story cover.
+ *
+ * @param author
+ * the author to change
+ * @param luid
+ * the story LUID
+ *
+ * @throws IOException
+ * in case of IOException
*/
- public abstract void setSourceCover(String source, String luid);
+ public abstract void setAuthorCover(String author, String luid)
+ throws IOException;
/**
* Return the list of stories (represented by their {@link MetaData}, which
* the optional {@link Progress}
*
* @return the list (can be empty but not NULL)
+ *
+ * @throws IOException
+ * in case of IOException
*/
- protected abstract List<MetaData> getMetas(Progress pg);
+ protected abstract List<MetaData> getMetas(Progress pg) throws IOException;
/**
* Invalidate the {@link Story} cache (when the content should be re-read
*
* @param meta
* the {@link Story} to clear from the cache
+ *
+ * @throws IOException
+ * in case of IOException
*/
- protected abstract void updateInfo(MetaData meta);
+ protected abstract void updateInfo(MetaData meta) throws IOException;
/**
* Return the next LUID that can be used.
* the optional progress reporter
*/
public void refresh(Progress pg) {
- getMetas(pg);
+ try {
+ getMetas(pg);
+ } catch (IOException e) {
+ // We will let it fail later
+ }
}
/**
* List all the known types (sources) of stories.
*
* @return the sources
+ *
+ * @throws IOException
+ * in case of IOException
*/
- public synchronized List<String> getSources() {
+ public synchronized List<String> getSources() throws IOException {
List<String> list = new ArrayList<String>();
for (MetaData meta : getMetas(null)) {
String storySource = meta.getSource();
* </ul>
*
* @return the grouped list
+ *
+ * @throws IOException
+ * in case of IOException
*/
- public synchronized Map<String, List<String>> getSourcesGrouped() {
+ public synchronized Map<String, List<String>> getSourcesGrouped()
+ throws IOException {
Map<String, List<String>> map = new TreeMap<String, List<String>>();
for (String source : getSources()) {
String name;
* List all the known authors of stories.
*
* @return the authors
+ *
+ * @throws IOException
+ * in case of IOException
*/
- public synchronized List<String> getAuthors() {
+ public synchronized List<String> getAuthors() throws IOException {
List<String> list = new ArrayList<String>();
for (MetaData meta : getMetas(null)) {
String storyAuthor = meta.getAuthor();
* <tt>0-9</tt>, which may only be present or not).
*
* @return the authors' names, grouped by letter(s)
+ *
+ * @throws IOException
+ * in case of IOException
*/
- public List<Entry<String, List<String>>> getAuthorsGrouped() {
+ public Map<String, List<String>> getAuthorsGrouped() throws IOException {
int MAX = 20;
- List<Entry<String, List<String>>> groups = new ArrayList<Entry<String, List<String>>>();
+ Map<String, List<String>> groups = new TreeMap<String, List<String>>();
List<String> authors = getAuthors();
+ // If all authors fit the max, just report them as is
if (authors.size() <= MAX) {
- groups.add(new SimpleEntry<String, List<String>>("", authors));
+ groups.put("", authors);
return groups;
}
- groups.add(new SimpleEntry<String, List<String>>("*", getAuthorsGroup(
- authors, '*')));
- groups.add(new SimpleEntry<String, List<String>>("0-9",
- getAuthorsGroup(authors, '0')));
-
+ // Create groups A to Z, which can be empty here
for (char car = 'A'; car <= 'Z'; car++) {
- groups.add(new SimpleEntry<String, List<String>>(Character
- .toString(car), getAuthorsGroup(authors, car)));
+ groups.put(Character.toString(car), getAuthorsGroup(authors, car));
}
- // do NOT collapse * and [0-9] with the rest
- for (int i = 2; i + 1 < groups.size(); i++) {
- Entry<String, List<String>> now = groups.get(i);
- Entry<String, List<String>> next = groups.get(i + 1);
- int currentTotal = now.getValue().size() + next.getValue().size();
+ // Collapse them
+ List<String> keys = new ArrayList<String>(groups.keySet());
+ for (int i = 0; i + 1 < keys.size(); i++) {
+ String keyNow = keys.get(i);
+ String keyNext = keys.get(i + 1);
+
+ List<String> now = groups.get(keyNow);
+ List<String> next = groups.get(keyNext);
+
+ int currentTotal = now.size() + next.size();
if (currentTotal <= MAX) {
- String key = now.getKey().charAt(0) + "-"
- + next.getKey().charAt(next.getKey().length() - 1);
+ String key = keyNow.charAt(0) + "-"
+ + keyNext.charAt(keyNext.length() - 1);
+
List<String> all = new ArrayList<String>();
- all.addAll(now.getValue());
- all.addAll(next.getValue());
- groups.set(i, new SimpleEntry<String, List<String>>(key, all));
- groups.remove(i + 1);
- i--;
+ all.addAll(now);
+ all.addAll(next);
+
+ groups.remove(keyNow);
+ groups.remove(keyNext);
+ groups.put(key, all);
+
+ keys.set(i, key); // set the new key instead of key(i)
+ keys.remove(i + 1); // remove the next, consumed key
+ i--; // restart at key(i)
}
}
- for (int i = 0; i < groups.size(); i++) {
- if (groups.get(i).getValue().size() == 0) {
- groups.remove(i);
- i--;
+ // Add "special" groups
+ groups.put("*", getAuthorsGroup(authors, '*'));
+ groups.put("0-9", getAuthorsGroup(authors, '0'));
+
+ // Prune empty groups
+ keys = new ArrayList<String>(groups.keySet());
+ for (String key : keys) {
+ if (groups.get(key).isEmpty()) {
+ groups.remove(key);
}
}
* @param car
* the starting character, <tt>*</tt>, <tt>0</tt> or a capital
* letter
- * @return the authors that fulfill the starting letter
+ *
+ * @return the authors that fulfil the starting letter
*/
private List<String> getAuthorsGroup(List<String> authors, char car) {
List<String> accepted = new ArrayList<String>();
* Cover images <b>MAYBE</b> not included.
*
* @return the stories
+ *
+ * @throws IOException
+ * in case of IOException
*/
- public synchronized List<MetaData> getList() {
+ public synchronized List<MetaData> getList() throws IOException {
return getMetas(null);
}
* the type of story to retrieve, or NULL for all
*
* @return the stories
+ *
+ * @throws IOException
+ * in case of IOException
*/
- public synchronized List<MetaData> getListBySource(String type) {
+ public synchronized List<MetaData> getListBySource(String type)
+ throws IOException {
List<MetaData> list = new ArrayList<MetaData>();
for (MetaData meta : getMetas(null)) {
String storyType = meta.getSource();
* the author of the stories to retrieve, or NULL for all
*
* @return the stories
+ *
+ * @throws IOException
+ * in case of IOException
*/
- public synchronized List<MetaData> getListByAuthor(String author) {
+ public synchronized List<MetaData> getListByAuthor(String author)
+ throws IOException {
List<MetaData> list = new ArrayList<MetaData>();
for (MetaData meta : getMetas(null)) {
String storyAuthor = meta.getAuthor();
* the Library UID of the story
*
* @return the corresponding {@link Story}
+ *
+ * @throws IOException
+ * in case of IOException
*/
- public synchronized MetaData getInfo(String luid) {
+ public synchronized MetaData getInfo(String luid) throws IOException {
if (luid != null) {
for (MetaData meta : getMetas(null)) {
if (luid.equals(meta.getLuid())) {
* the optional progress reporter
*
* @return the corresponding {@link Story} or NULL if not found
+ *
+ * @throws IOException
+ * in case of IOException
*/
- public synchronized Story getStory(String luid, Progress pg) {
+ public synchronized Story getStory(String luid, Progress pg)
+ throws IOException {
+ Progress pgMetas = new Progress();
+ Progress pgStory = new Progress();
+ if (pg != null) {
+ pg.setMinMax(0, 100);
+ pg.addProgress(pgMetas, 10);
+ pg.addProgress(pgStory, 90);
+ }
+
+ MetaData meta = null;
+ for (MetaData oneMeta : getMetas(pgMetas)) {
+ if (oneMeta.getLuid().equals(luid)) {
+ meta = oneMeta;
+ break;
+ }
+ }
+
+ pgMetas.done();
+
+ Story story = getStory(luid, meta, pgStory);
+ pgStory.done();
+
+ return story;
+ }
+
+ /**
+ * Retrieve a specific {@link Story}.
+ *
+ * @param luid
+ * the meta of the story
+ * @param pg
+ * the optional progress reporter
+ *
+ * @return the corresponding {@link Story} or NULL if not found
+ *
+ * @throws IOException
+ * in case of IOException
+ */
+ public synchronized Story getStory(String luid,
+ @SuppressWarnings("javadoc") MetaData meta, Progress pg)
+ throws IOException {
+
if (pg == null) {
pg = new Progress();
}
pg.addProgress(pgProcess, 1);
Story story = null;
- for (MetaData meta : getMetas(null)) {
- if (meta.getLuid().equals(luid)) {
- File file = getFile(luid, pgGet);
- pgGet.done();
- try {
- SupportType type = SupportType.valueOfAllOkUC(meta
- .getType());
- URL url = file.toURI().toURL();
- if (type != null) {
- story = BasicSupport.getSupport(type, url) //
- .process(pgProcess);
-
- // Because we do not want to clear the meta cache:
- meta.setCover(story.getMeta().getCover());
- meta.setResume(story.getMeta().getResume());
- story.setMeta(meta);
- //
- } else {
- throw new IOException("Unknown type: " + meta.getType());
- }
- } catch (IOException e) {
- // We should not have not-supported files in the
- // library
- Instance.getTraceHandler().error(
- new IOException("Cannot load file from library: "
- + file, e));
- } finally {
- pgProcess.done();
- pg.done();
- }
-
- break;
+ File file = getFile(luid, pgGet);
+ pgGet.done();
+ try {
+ SupportType type = SupportType.valueOfAllOkUC(meta.getType());
+ URL url = file.toURI().toURL();
+ if (type != null) {
+ story = BasicSupport.getSupport(type, url) //
+ .process(pgProcess);
+
+ // Because we do not want to clear the meta cache:
+ meta.setCover(story.getMeta().getCover());
+ meta.setResume(story.getMeta().getResume());
+ story.setMeta(meta);
+ //
+ } else {
+ throw new IOException("Unknown type: " + meta.getType());
}
+ } catch (IOException e) {
+ // We should not have not-supported files in the
+ // library
+ Instance.getTraceHandler().error(
+ new IOException(String.format(
+ "Cannot load file of type '%s' from library: %s",
+ meta.getType(), file), e));
+ } finally {
+ pgProcess.done();
+ pg.done();
}
return story;
* @param pg
* the optional progress reporter
*
- * @return the imported {@link Story}
+ * @return the imported Story {@link MetaData}
*
* @throws UnknownHostException
* if the host is not supported
* @throws IOException
* in case of I/O error
*/
- public Story imprt(URL url, Progress pg) throws IOException {
+ public MetaData imprt(URL url, Progress pg) throws IOException {
if (pg == null)
pg = new Progress();
Story story = save(support.process(pgProcess), pgSave);
pg.done();
- return story;
+ return story.getMeta();
}
/**
meta.setTitle(newTitle);
meta.setAuthor(newAuthor);
saveMeta(meta, pg);
+
+ invalidateInfo(luid);
}
/**