X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Ffanfix%2Flibrary%2FBasicLibrary.java;h=350a8cafcc6ecc4ff2eafba768e052fe323db122;hb=8d2b0320bee001e24611484cb1d952682ab2d1b6;hp=d324008f0b4c2294e503d907a25b0edc226ed944;hpb=085a2f9a3a811a910de7c3011eb6f5ef2ab18aa0;p=fanfix.git diff --git a/src/be/nikiroo/fanfix/library/BasicLibrary.java b/src/be/nikiroo/fanfix/library/BasicLibrary.java index d324008..f77d0ed 100644 --- a/src/be/nikiroo/fanfix/library/BasicLibrary.java +++ b/src/be/nikiroo/fanfix/library/BasicLibrary.java @@ -1,12 +1,12 @@ package be.nikiroo.fanfix.library; -import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; +import java.net.UnknownHostException; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import be.nikiroo.fanfix.Instance; import be.nikiroo.fanfix.data.MetaData; @@ -14,8 +14,10 @@ import be.nikiroo.fanfix.data.Story; import be.nikiroo.fanfix.output.BasicOutput; import be.nikiroo.fanfix.output.BasicOutput.OutputType; import be.nikiroo.fanfix.supported.BasicSupport; -import be.nikiroo.fanfix.supported.BasicSupport.SupportType; +import be.nikiroo.fanfix.supported.SupportType; +import be.nikiroo.utils.Image; import be.nikiroo.utils.Progress; +import be.nikiroo.utils.StringUtils; /** * Manage a library of Stories: import, export, list, modify. @@ -29,6 +31,44 @@ import be.nikiroo.utils.Progress; * @author niki */ abstract public class BasicLibrary { + /** + * A {@link BasicLibrary} status. + * + * @author niki + */ + public enum Status { + /** The library is ready and r/w. */ + READ_WRITE, + /** The library is ready, but read-only. */ + READ_ONLY, + /** You are not allowed to access this library. */ + UNAUTHORIZED, + /** The library is invalid, and will never work as is. */ + INVALID, + /** The library is currently out of commission, but may work later. */ + UNAVAILABLE; + + /** + * The library is available (you can query it). + *
+ * It does not 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 a name for this library (the UI may display this). *
@@ -40,6 +80,15 @@ abstract public class BasicLibrary { return ""; } + /** + * The library status. + * + * @return the current status + */ + public Status getStatus() { + return Status.READ_WRITE; + } + /** * Retrieve the main {@link File} corresponding to the given {@link Story}, * which can be passed to an external reader or instance. @@ -47,13 +96,16 @@ abstract public class BasicLibrary { * Do NOT alter this file. * * @param luid - * the Library UID of the story + * the Library UID of the story, can be NULL * @param pg * 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. @@ -62,21 +114,52 @@ abstract public class BasicLibrary { * the Library UID of the story * * @return the cover image + * + * @throws IOException + * in case of IOException + */ + public abstract Image getCover(String luid) throws IOException; + + /** + * Retrieve the list of {@link MetaData} known by this {@link BasicLibrary} + * in a easy-to-filter version. + * + * @param pg + * the optional {@link Progress} + * @return the list of {@link MetaData} as a {@link MetaResultList} you can + * query + * @throws IOException + * in case of I/O eror */ - public abstract BufferedImage getCover(String luid); + public MetaResultList getList(Progress pg) throws IOException { + // TODO: ensure it is the main used interface + + return new MetaResultList(getMetas(pg)); + } + + // TODO: make something for (normal and custom) non-story covers /** * Return the cover image associated to this source. *
- * By default, return the cover of the first story with this source.
+ * By default, return the custom cover if any, and if not, return the cover
+ * of the first story with this source.
*
* @param source
* the source
*
* @return the cover image or NULL
+ *
+ * @throws IOException
+ * in case of IOException
*/
- public BufferedImage getSourceCover(String source) {
- List
+ * 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
+ * By default, return NULL.
+ *
+ * @param source
+ * 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.
+ *
+ * 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
+ */
+ @SuppressWarnings("unused")
+ public Image getCustomAuthorCover(String author) throws IOException {
+ return null;
+ }
+
+ /**
+ * 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);
+ 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 setAuthorCover(String author, String luid)
+ throws IOException;
/**
* Return the list of stories (represented by their {@link MetaData}, which
* MAY not have the cover included).
+ *
+ * The returned list MUST be a copy, not the original one.
*
* @param pg
* the optional {@link Progress}
*
* @return the list (can be empty but not NULL)
+ *
+ * @throws IOException
+ * in case of IOException
*/
- protected abstract List
+ * All the cache can be deleted if NULL is passed as meta.
+ *
+ * @param luid
+ * the LUID of the {@link Story} to clear from the cache, or NULL
+ * for all stories
+ */
+ protected abstract void invalidateInfo(String luid);
+
+ /**
+ * Invalidate the {@link Story} cache (when the content has changed, but we
+ * already have it) with the new given meta.
+ *
+ * @param meta
+ * the {@link Story} to clear from the cache
+ *
+ * @throws IOException
+ * in case of IOException
+ */
+ protected abstract void updateInfo(MetaData meta) throws IOException;
/**
* Return the next LUID that can be used.
*
* @return the next luid
*/
- protected abstract int getNextId();
+ protected abstract String getNextId();
/**
* Delete the target {@link Story}.
@@ -147,121 +342,111 @@ abstract public class BasicLibrary {
throws IOException;
/**
- * Refresh the {@link BasicLibrary}, that is, make sure all stories are
+ * Refresh the {@link BasicLibrary}, that is, make sure all metas are
* loaded.
*
- * @param full
- * force the full content of the stories to be loaded, not just
- * the {@link MetaData}
- *
* @param pg
* the optional progress reporter
*/
- public void refresh(boolean full, Progress pg) {
- if (full) {
- // TODO: progress
- List
+ * The next time we try to retrieve the {@link Story}, it may be required to
+ * cache it again.
*
- * @return the authors
+ * @param luid
+ * the story to clear
+ *
+ * @throws IOException
+ * in case of I/O error
*/
- public synchronized List
- * Cover images not included.
- *
- * @return the stories
+ * @return the same as getList()
+ * @throws IOException
+ * in case of I/O error
+ * @deprecated please use {@link BasicLibrary#getList()} and
+ * {@link MetaResultList#getSourcesGrouped()} instead.
*/
- public synchronized List
- * Cover images not included.
- *
- * @param type
- * the type of story to retrieve, or NULL for all
- *
- * @return the stories
+ * @return the same as getList()
+ * @throws IOException
+ * in case of I/O error
+ * @deprecated please use {@link BasicLibrary#getList()} and
+ * {@link MetaResultList#getAuthors()} instead.
*/
- public synchronized List
- * Cover images not included.
- *
- * @param author
- * the author of the stories to retrieve, or NULL for all
+ * Cover images MAYBE not included.
*
* @return the stories
+ *
+ * @throws IOException
+ * in case of IOException
*/
- public synchronized List
+ * Note that it will update both the cover and the resume in meta.
*
* @param luid
* the Library UID of the story
@@ -294,8 +484,55 @@ abstract public class BasicLibrary {
* 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 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}.
+ *
+ * Note that it will update both the cover and the resume in meta.
+ *
+ * @param luid
+ * the LUID of the story
+ * @param meta
+ * 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, MetaData meta, Progress pg)
+ throws IOException {
+
if (pg == null) {
pg = new Progress();
}
@@ -308,33 +545,38 @@ abstract public class BasicLibrary {
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).process(url,
- pgProcess);
- 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.syserr(new IOException(
- "Cannot load file from library: " + file, e));
- } finally {
- pgProcess.done();
- pg.done();
+ File file = null;
+
+ if (luid != null && meta != null) {
+ file = getFile(luid, pgGet);
+ }
+
+ pgGet.done();
+ try {
+ if (file != null) {
+ SupportType type = SupportType.valueOfAllOkUC(meta.getType());
+ if (type == null) {
+ throw new IOException("Unknown type: " + meta.getType());
}
- break;
+ URL url = file.toURI().toURL();
+ 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);
}
+ } catch (IOException e) {
+ // We should not have not-supported files in the library
+ Instance.getInstance().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;
@@ -349,18 +591,54 @@ abstract public class BasicLibrary {
* @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 {
+ return imprt(url, null, pg);
+ }
+
+ /**
+ * Import the {@link Story} at the given {@link URL} into the
+ * {@link BasicLibrary}.
+ *
+ * @param url
+ * the {@link URL} to import
+ * @param luid
+ * the LUID to use
+ * @param pg
+ * the optional progress reporter
+ *
+ * @return the imported Story {@link MetaData}
+ *
+ * @throws UnknownHostException
+ * if the host is not supported
+ * @throws IOException
+ * in case of I/O error
+ */
+ MetaData imprt(URL url, String luid, Progress pg) throws IOException {
+ if (pg == null)
+ pg = new Progress();
+
+ pg.setMinMax(0, 1000);
+ Progress pgProcess = new Progress();
+ Progress pgSave = new Progress();
+ pg.addProgress(pgProcess, 800);
+ pg.addProgress(pgSave, 200);
+
BasicSupport support = BasicSupport.getSupport(url);
if (support == null) {
- throw new IOException("URL not supported: " + url.toString());
+ throw new UnknownHostException("" + url);
}
- return save(support.process(url, pg), null);
+ Story story = save(support.process(pgProcess), luid, pgSave);
+ pg.done();
+
+ return story.getMeta();
}
/**
@@ -425,7 +703,7 @@ abstract public class BasicLibrary {
pg.addProgress(pgOut, 1);
}
- BasicOutput out = BasicOutput.getOutput(type, false);
+ BasicOutput out = BasicOutput.getOutput(type, false, false);
if (out == null) {
throw new IOException("Output type not supported: " + type);
}
@@ -475,24 +753,39 @@ abstract public class BasicLibrary {
*/
public synchronized Story save(Story story, String luid, Progress pg)
throws IOException {
+ if (pg == null) {
+ pg = new Progress();
+ }
+
+ Instance.getInstance().getTraceHandler().trace(
+ this.getClass().getSimpleName() + ": saving story " + luid);
+
// Do not change the original metadata, but change the original story
MetaData meta = story.getMeta().clone();
story.setMeta(meta);
+ pg.setName("Saving story");
+
if (luid == null || luid.isEmpty()) {
- meta.setLuid(String.format("%03d", getNextId()));
+ meta.setLuid(getNextId());
} else {
meta.setLuid(luid);
}
- if (getInfo(luid) != null) {
+ if (luid != null && getInfo(luid) != null) {
delete(luid);
}
- doSave(story, pg);
+ story = doSave(story, pg);
- clearCache();
+ updateInfo(story.getMeta());
+ Instance.getInstance().getTraceHandler()
+ .trace(this.getClass().getSimpleName() + ": story saved ("
+ + luid + ")");
+
+ pg.setName(meta.getTitle());
+ pg.done();
return story;
}
@@ -506,8 +799,15 @@ abstract public class BasicLibrary {
* in case of I/O error
*/
public synchronized void delete(String luid) throws IOException {
+ Instance.getInstance().getTraceHandler().trace(
+ this.getClass().getSimpleName() + ": deleting story " + luid);
+
doDelete(luid);
- clearCache();
+ invalidateInfo(luid);
+
+ Instance.getInstance().getTraceHandler()
+ .trace(this.getClass().getSimpleName() + ": story deleted ("
+ + luid + ")");
}
/**
@@ -530,7 +830,83 @@ abstract public class BasicLibrary {
throw new IOException("Story not found: " + luid);
}
+ changeSTA(luid, newSource, meta.getTitle(), meta.getAuthor(), pg);
+ }
+
+ /**
+ * Change the title (name) of the given {@link Story}.
+ *
+ * @param luid
+ * the {@link Story} LUID
+ * @param newTitle
+ * the new title
+ * @param pg
+ * the optional progress reporter
+ *
+ * @throws IOException
+ * in case of I/O error or if the {@link Story} was not found
+ */
+ public synchronized void changeTitle(String luid, String newTitle,
+ Progress pg) throws IOException {
+ MetaData meta = getInfo(luid);
+ if (meta == null) {
+ throw new IOException("Story not found: " + luid);
+ }
+
+ changeSTA(luid, meta.getSource(), newTitle, meta.getAuthor(), pg);
+ }
+
+ /**
+ * Change the author of the given {@link Story}.
+ *
+ * @param luid
+ * the {@link Story} LUID
+ * @param newAuthor
+ * the new author
+ * @param pg
+ * the optional progress reporter
+ *
+ * @throws IOException
+ * in case of I/O error or if the {@link Story} was not found
+ */
+ public synchronized void changeAuthor(String luid, String newAuthor,
+ Progress pg) throws IOException {
+ MetaData meta = getInfo(luid);
+ if (meta == null) {
+ throw new IOException("Story not found: " + luid);
+ }
+
+ changeSTA(luid, meta.getSource(), meta.getTitle(), newAuthor, pg);
+ }
+
+ /**
+ * Change the Source, Title and Author of the {@link Story} in one single
+ * go.
+ *
+ * @param luid
+ * the {@link Story} LUID
+ * @param newSource
+ * the new source
+ * @param newTitle
+ * the new title
+ * @param newAuthor
+ * the new author
+ * @param pg
+ * the optional progress reporter
+ *
+ * @throws IOException
+ * in case of I/O error or if the {@link Story} was not found
+ */
+ protected synchronized void changeSTA(String luid, String newSource,
+ String newTitle, String newAuthor, Progress pg) throws IOException {
+ MetaData meta = getInfo(luid);
+ if (meta == null) {
+ throw new IOException("Story not found: " + luid);
+ }
+
meta.setSource(newSource);
+ meta.setTitle(newTitle);
+ meta.setAuthor(newAuthor);
saveMeta(meta, pg);
}
@@ -541,7 +917,7 @@ abstract public class BasicLibrary {
* By default, delete the old {@link Story} then recreate a new
* {@link Story}.
*
- * Note that this behaviour can lead to data loss.
+ * Note that this behaviour can lead to data loss in case of problems!
*
* @param meta
* the new {@link MetaData} (LUID MUST NOT change)
@@ -567,11 +943,56 @@ abstract public class BasicLibrary {
throw new IOException("Story not found: " + meta.getLuid());
}
+ // TODO: this is not safe!
delete(meta.getLuid());
-
story.setMeta(meta);
save(story, meta.getLuid(), pgSet);
pg.done();
}
+
+ /**
+ * Describe a {@link Story} from its {@link MetaData} and return a list of
+ * title/value that represent this {@link Story}.
+ *
+ * @param meta
+ * the {@link MetaData} to represent
+ *
+ * @return the information, translated and sorted
+ */
+ static public Map