version 1.1.5 updates
New web page. Attempt to work better with connecting to audible.
This commit is contained in:
parent
05ca633393
commit
a4db8f8a12
24 changed files with 608 additions and 274 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -19,6 +19,7 @@ unused/
|
||||||
caches
|
caches
|
||||||
Desktop.ini
|
Desktop.ini
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
*/Thumbs.db
|
||||||
|
|
||||||
# java
|
# java
|
||||||
*.class
|
*.class
|
||||||
|
|
|
@ -121,6 +121,18 @@ public class Audible implements IQueueListener<Book> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fix book info
|
||||||
|
private Book normalizeBook(Book b)
|
||||||
|
{
|
||||||
|
String link = b.getInfoLink();
|
||||||
|
if (link.startsWith("/"))
|
||||||
|
{
|
||||||
|
// convert to full URL.
|
||||||
|
b.setInfoLink("https://www.audible.com"+link);
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
boolean takeBook(Book b) {
|
boolean takeBook(Book b) {
|
||||||
if (!ok(b)) {
|
if (!ok(b)) {
|
||||||
LOG.warn("invalid book: " + checkBook(b));
|
LOG.warn("invalid book: " + checkBook(b));
|
||||||
|
@ -130,6 +142,9 @@ public class Audible implements IQueueListener<Book> {
|
||||||
if (!hasBook(b)) {
|
if (!hasBook(b)) {
|
||||||
if (!ignoreBook(b)) {
|
if (!ignoreBook(b)) {
|
||||||
synchronized (books) {
|
synchronized (books) {
|
||||||
|
normalizeBook(b);
|
||||||
|
|
||||||
|
|
||||||
books.put(b.getProduct_id(), b);
|
books.put(b.getProduct_id(), b);
|
||||||
}
|
}
|
||||||
BookNotifier.getInstance().bookAdded(b);
|
BookNotifier.getInstance().bookAdded(b);
|
||||||
|
@ -363,8 +378,9 @@ public class Audible implements IQueueListener<Book> {
|
||||||
booksUpdated++;
|
booksUpdated++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (booksUpdated > 0 && quick)
|
|
||||||
updateLibrary(false);
|
// if (booksUpdated > 0 && quick)
|
||||||
|
// updateLibrary(false);
|
||||||
|
|
||||||
|
|
||||||
LOG.info("Updated " + list.size() + " books");
|
LOG.info("Updated " + list.size() + " books");
|
||||||
|
@ -722,7 +738,7 @@ public class Audible implements IQueueListener<Book> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void jobCompleted(ThreadedQueue<Book> queue, IQueueJob job, Book b) {
|
public void jobCompleted(ThreadedQueue<Book> queue, IQueueJob job, Book b) {
|
||||||
LOG.info(queue.toString() + " completed:" + b + " size:" + queue.size());
|
LOG.info(queue.toString() + " completed:" + b + " remaining queue size:" + queue.size());
|
||||||
if (queue == downloadQueue) {
|
if (queue == downloadQueue) {
|
||||||
try {
|
try {
|
||||||
AAXParser.instance.update(b);
|
AAXParser.instance.update(b);
|
||||||
|
|
|
@ -108,7 +108,10 @@ public class AudibleScraper {
|
||||||
for (BasicClientCookie bc : list) {
|
for (BasicClientCookie bc : list) {
|
||||||
Cookie c = new Cookie(bc.getDomain(), bc.getName(), bc.getValue());
|
Cookie c = new Cookie(bc.getDomain(), bc.getName(), bc.getValue());
|
||||||
cm.addCookie(c);
|
cm.addCookie(c);
|
||||||
|
// LOG.info("Cookie: "+c);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
LOG.info("Loaded "+list.size()+" cookies");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,8 +167,12 @@ public class AudibleScraper {
|
||||||
FileUtils.writeByteArrayToFile(cookiesFile, o.getBytes());
|
FileUtils.writeByteArrayToFile(cookiesFile, o.getBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int maxLoginAttempts = 2;
|
||||||
protected boolean login() throws IOException {
|
protected boolean login() throws IOException {
|
||||||
|
return login(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean login(int attempt) throws IOException {
|
||||||
|
|
||||||
AudibleAccountPrefs copy = account;
|
AudibleAccountPrefs copy = account;
|
||||||
|
|
||||||
|
@ -187,6 +194,8 @@ public class AudibleScraper {
|
||||||
HtmlForm login = page.getFormByName("signIn");
|
HtmlForm login = page.getFormByName("signIn");
|
||||||
|
|
||||||
if (login == null) {
|
if (login == null) {
|
||||||
|
// TODO: find sign-in anchor and click it..
|
||||||
|
|
||||||
LOG.info("login form not found for page:" + page.getTitleText());
|
LOG.info("login form not found for page:" + page.getTitleText());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -220,7 +229,7 @@ public class AudibleScraper {
|
||||||
HtmlElement captchaImageDiv = findById("ap_captcha_img");
|
HtmlElement captchaImageDiv = findById("ap_captcha_img");
|
||||||
|
|
||||||
if (captchaImageDiv != null || ap_captcha_table != null) {
|
if (captchaImageDiv != null || ap_captcha_table != null) {
|
||||||
|
LOG.info("Appears to be a captcha... I am a bot.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,7 +249,10 @@ public class AudibleScraper {
|
||||||
LOG.info(page.getUrl());
|
LOG.info(page.getUrl());
|
||||||
|
|
||||||
LOG.info("Login failed, see html files at:" + HTMLUtil.debugFile("submitting-credentials").getAbsolutePath() + " and " + HTMLUtil.debugFile("login failed").getAbsolutePath());
|
LOG.info("Login failed, see html files at:" + HTMLUtil.debugFile("submitting-credentials").getAbsolutePath() + " and " + HTMLUtil.debugFile("login failed").getAbsolutePath());
|
||||||
|
if (attempt<maxLoginAttempts)
|
||||||
|
{
|
||||||
|
login(attempt+1);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
HTMLUtil.debugFile("submitting-credentials").delete();
|
HTMLUtil.debugFile("submitting-credentials").delete();
|
||||||
|
@ -278,13 +290,12 @@ public class AudibleScraper {
|
||||||
|
|
||||||
if (signOut == null && accountDetails == null && signIn == null) {
|
if (signOut == null && accountDetails == null && signIn == null) {
|
||||||
HTMLUtil.debugNode(page, "checkLoggedIn");
|
HTMLUtil.debugNode(page, "checkLoggedIn");
|
||||||
|
|
||||||
}
|
}
|
||||||
return isLoggedIn();
|
return isLoggedIn();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String homeURL() {
|
public String homeURL() {
|
||||||
return "/access";
|
return "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPageURL() {
|
public String getPageURL() {
|
||||||
|
@ -308,8 +319,7 @@ public class AudibleScraper {
|
||||||
if (true)
|
if (true)
|
||||||
getWebClient().setJavascriptEnabled(true);
|
getWebClient().setJavascriptEnabled(true);
|
||||||
try {
|
try {
|
||||||
setURL(homeURL());
|
setURL(homeURL(), "Loading web page...");
|
||||||
// HTMLUtil.debugNode(page, "homeURL");
|
|
||||||
|
|
||||||
if (checkLoggedIn())
|
if (checkLoggedIn())
|
||||||
return;
|
return;
|
||||||
|
@ -355,32 +365,66 @@ public class AudibleScraper {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
public boolean setURLAndLogIn(String u) throws IOException {
|
||||||
|
|
||||||
public boolean clickLib() throws Exception {
|
setURL(u);
|
||||||
HtmlAnchor lib=null;
|
if (!checkLoggedIn()) {
|
||||||
for (HtmlAnchor n : page.getAnchors()) {
|
LOG.info("not logged in after going to:"+u);
|
||||||
if (n.getHrefAttribute().contains("/lib"))
|
// trouble.. try again
|
||||||
lib = n;
|
login();
|
||||||
|
return checkLoggedIn();
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
if (lib!=null) {
|
|
||||||
setPage(lib.click());
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
public void lib() throws Exception {
|
public void lib() throws Exception {
|
||||||
|
String browserURL = ConnectionNotifier.instance.getLastURL();
|
||||||
|
|
||||||
|
if (!browserURL.isEmpty())
|
||||||
|
{
|
||||||
|
LOG.info("Using library location from browser: "+browserURL);
|
||||||
|
if (setURLAndLogIn(browserURL))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!setURLAndLogIn("/lib"))
|
||||||
|
throw new Exception("Unable to access your library. Try logging in with Browser (Cmd-B) to view your library page and try again.. \n\nThere may also be a change in audible's web site that has broken this code.");
|
||||||
|
|
||||||
|
/*
|
||||||
|
HtmlAnchor lib=null;
|
||||||
|
if (page!=null)
|
||||||
|
{
|
||||||
|
String debug = "";
|
||||||
|
|
||||||
|
for (HtmlAnchor a : page.getAnchors())
|
||||||
|
{
|
||||||
|
String ref = a.getHrefAttribute();
|
||||||
|
debug += ref+"\n";
|
||||||
|
if (a.getHrefAttribute().startsWith("/lib"))
|
||||||
|
{
|
||||||
|
lib = a;
|
||||||
|
browserURL = ref;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOG.info(debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lib!=null)
|
||||||
|
{
|
||||||
|
page = lib.click();
|
||||||
|
if (!checkLoggedIn()) {
|
||||||
|
LOG.info("Clicked lib, but not logged in anymore.");
|
||||||
|
// trouble.. try again
|
||||||
|
login();
|
||||||
|
if (!checkLoggedIn())
|
||||||
|
throw new Exception("Got logged out. Try logging in with Browser to your library page and try again..");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean ok = setURLAndLogIn("/lib");
|
||||||
|
|
||||||
setURL("/lib");
|
setURL("/lib");
|
||||||
if (!checkLoggedIn()) {
|
if (!checkLoggedIn()) {
|
||||||
|
@ -391,6 +435,7 @@ public class AudibleScraper {
|
||||||
setURL("/lib");
|
setURL("/lib");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -493,7 +538,8 @@ public class AudibleScraper {
|
||||||
|
|
||||||
if (next == null) {
|
if (next == null) {
|
||||||
assert (pageNum == 1);
|
assert (pageNum == 1);
|
||||||
setURL("/lib", "Reading Library...");
|
lib();
|
||||||
|
// setURL("/lib", "Reading Library...");
|
||||||
setPageFilter();
|
setPageFilter();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -565,8 +611,8 @@ public class AudibleScraper {
|
||||||
private void setPageFilter() {
|
private void setPageFilter() {
|
||||||
try {
|
try {
|
||||||
DomElement purchaseDateFilter = page.getElementByName("purchaseDateFilter");
|
DomElement purchaseDateFilter = page.getElementByName("purchaseDateFilter");
|
||||||
if (purchaseDateFilter != null && purchaseDateFilter instanceof HtmlSelect) {
|
|
||||||
HtmlSelect h = (HtmlSelect) purchaseDateFilter;
|
HtmlSelect h = (HtmlSelect) purchaseDateFilter;
|
||||||
int i = h.getSelectedIndex();
|
int i = h.getSelectedIndex();
|
||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
HtmlOption all = h.getOption(0);
|
HtmlOption all = h.getOption(0);
|
||||||
|
@ -590,11 +636,11 @@ public class AudibleScraper {
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
LOG.info("warning: did not find library filter htmlSelect.");
|
|
||||||
}
|
|
||||||
} catch (Throwable th) {
|
} catch (Throwable th) {
|
||||||
LOG.error("Unable to set purchaseDateFilter.", th);
|
LOG.info("Unable to set purchaseDateFilter. Writing debug log to no_date.html. This may mean we are unable to get all of your books. You may need to log in with the Browser and set the filters to show all books.");
|
||||||
|
HTMLUtil.debugNode(page, "no_date.html");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package org.openaudible.audible;
|
package org.openaudible.audible;
|
||||||
|
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.openaudible.AudibleAccountPrefs;
|
import org.openaudible.AudibleAccountPrefs;
|
||||||
import org.openaudible.util.EventNotifier;
|
import org.openaudible.util.EventNotifier;
|
||||||
|
|
||||||
|
@ -8,6 +10,11 @@ import org.openaudible.util.EventNotifier;
|
||||||
public class ConnectionNotifier extends EventNotifier<ConnectionListener> implements ConnectionListener {
|
public class ConnectionNotifier extends EventNotifier<ConnectionListener> implements ConnectionListener {
|
||||||
public static final ConnectionNotifier instance = new ConnectionNotifier();
|
public static final ConnectionNotifier instance = new ConnectionNotifier();
|
||||||
State state = State.Not_Connected;
|
State state = State.Not_Connected;
|
||||||
|
private static final Log LOG = LogFactory.getLog(ConnectionNotifier.class);
|
||||||
|
|
||||||
|
private String lastURL="";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private ConnectionNotifier() {
|
private ConnectionNotifier() {
|
||||||
}
|
}
|
||||||
|
@ -18,11 +25,15 @@ public class ConnectionNotifier extends EventNotifier<ConnectionListener> implem
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void connectionChanged(boolean connected) {
|
public void connectionChanged(boolean connected) {
|
||||||
state = connected ? State.Connected : State.Disconnected;
|
|
||||||
|
|
||||||
for (ConnectionListener l : getListeners()) {
|
State newState = connected ? State.Connected : State.Disconnected;
|
||||||
l.connectionChanged(connected);
|
if (state!=newState) {
|
||||||
|
state = newState;
|
||||||
|
for (ConnectionListener l : getListeners()) {
|
||||||
|
l.connectionChanged(connected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isConnected() {
|
public boolean isConnected() {
|
||||||
|
@ -57,6 +68,15 @@ public class ConnectionNotifier extends EventNotifier<ConnectionListener> implem
|
||||||
return getState() == State.Disconnected;
|
return getState() == State.Disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getLastURL() {
|
||||||
|
return lastURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastURL(String lastURL) {
|
||||||
|
this.lastURL = lastURL;
|
||||||
|
LOG.info("Setting lastURL to:"+lastURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// not connected is unknown.
|
// not connected is unknown.
|
||||||
// connected means in account
|
// connected means in account
|
||||||
|
|
|
@ -8,6 +8,7 @@ import org.openaudible.books.BookElement;
|
||||||
import org.openaudible.util.HTMLUtil;
|
import org.openaudible.util.HTMLUtil;
|
||||||
import org.openaudible.util.Util;
|
import org.openaudible.util.Util;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -64,11 +65,11 @@ public enum LibraryParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ArrayList<Book> parseLibraryFragment(DomNode fragment) {
|
public ArrayList<Book> parseLibraryFragment(HtmlPage p) {
|
||||||
ArrayList<Book> list = new ArrayList<>();
|
ArrayList<Book> list = new ArrayList<>();
|
||||||
ArrayList<String> colNames = new ArrayList<>();
|
ArrayList<String> colNames = new ArrayList<>();
|
||||||
|
|
||||||
HtmlTable table = fragment.getFirstByXPath("//table");
|
HtmlTable table = p.getFirstByXPath("//table");
|
||||||
if (table == null)
|
if (table == null)
|
||||||
return list;
|
return list;
|
||||||
|
|
||||||
|
@ -103,7 +104,7 @@ public enum LibraryParser {
|
||||||
rindex++;
|
rindex++;
|
||||||
if (rindex == 1)
|
if (rindex == 1)
|
||||||
continue; // skip header row.
|
continue; // skip header row.
|
||||||
Book b = parseLibraryRow(r);
|
Book b = parseLibraryRow(p, r);
|
||||||
|
|
||||||
if (b != null) {
|
if (b != null) {
|
||||||
String chk = b.checkBook();
|
String chk = b.checkBook();
|
||||||
|
@ -125,7 +126,7 @@ public enum LibraryParser {
|
||||||
String debugString = "OR_ORIG";
|
String debugString = "OR_ORIG";
|
||||||
|
|
||||||
|
|
||||||
private Book parseLibraryRow(HtmlTableRow r) {
|
private Book parseLibraryRow(HtmlPage p, HtmlTableRow r) {
|
||||||
|
|
||||||
String xml = Util.cleanString(r.asXml());
|
String xml = Util.cleanString(r.asXml());
|
||||||
if (r.getCells().size() == 0)
|
if (r.getCells().size() == 0)
|
||||||
|
@ -160,14 +161,14 @@ public enum LibraryParser {
|
||||||
|
|
||||||
for (BookColumns col : BookColumns.parseOrder) {
|
for (BookColumns col : BookColumns.parseOrder) {
|
||||||
HtmlElement cell = cells.get(col.ordinal());
|
HtmlElement cell = cells.get(col.ordinal());
|
||||||
parseBookColumn(col, cell, b);
|
parseBookColumn(p, col, cell, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void parseBookColumn(BookColumns col, HtmlElement cell, Book b) {
|
private void parseBookColumn(HtmlPage p, BookColumns col, HtmlElement cell, Book b) {
|
||||||
|
|
||||||
// HTMLUtil.debugNode(cell, col.name()+".xml");
|
// HTMLUtil.debugNode(cell, col.name()+".xml");
|
||||||
String text = Util.cleanString(cell.asText());
|
String text = Util.cleanString(cell.asText());
|
||||||
|
@ -192,24 +193,32 @@ public enum LibraryParser {
|
||||||
anchors = cell.getElementsByTagName("a");
|
anchors = cell.getElementsByTagName("a");
|
||||||
for (int x = 0; x < anchors.size(); x++) {
|
for (int x = 0; x < anchors.size(); x++) {
|
||||||
HtmlAnchor a = (HtmlAnchor) anchors.get(x);
|
HtmlAnchor a = (HtmlAnchor) anchors.get(x);
|
||||||
String url = a.getHrefAttribute();
|
String href = a.getHrefAttribute();
|
||||||
|
|
||||||
// /pd/Fiction/Exodus-Audiobook/B008I3VMMQ?
|
// /pd/Fiction/Exodus-Audiobook/B008I3VMMQ?
|
||||||
if (url.startsWith("/pd/")) {
|
if (href.startsWith("/pd/")) {
|
||||||
int q = url.indexOf("?");
|
URL url = p.getUrl();
|
||||||
|
String protocol = url.getProtocol();
|
||||||
|
String host = url.getHost();
|
||||||
|
|
||||||
|
int q = href.indexOf("?");
|
||||||
if (q != -1)
|
if (q != -1)
|
||||||
url = url.substring(0, q);
|
href = href.substring(0, q);
|
||||||
|
|
||||||
boolean ok = false;
|
boolean ok = false;
|
||||||
|
|
||||||
|
|
||||||
if (b.has(BookElement.asin) && url.contains(b.getAsin()))
|
if (b.has(BookElement.asin) && href.contains(b.getAsin()))
|
||||||
ok = true;
|
ok = true;
|
||||||
|
|
||||||
if (b.has(BookElement.product_id) && url.contains(b.getProduct_id()))
|
if (b.has(BookElement.product_id) && href.contains(b.getProduct_id()))
|
||||||
ok = true;
|
ok = true;
|
||||||
if (ok)
|
if (ok) {
|
||||||
b.setInfoLink(url);
|
|
||||||
|
String full_url = String.format("%s://%s%s", protocol, host, href);
|
||||||
|
|
||||||
|
b.setInfoLink(full_url);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
LOG.info("Unknown product link for " + b + " at " + url);
|
LOG.info("Unknown product link for " + b + " at " + url);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
package org.openaudible.books;
|
package org.openaudible.books;
|
||||||
|
|
||||||
|
|
||||||
|
import org.openaudible.util.TimeToSeconds;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
public class Book implements Comparable<Book>, Serializable {
|
public class Book implements Comparable<Book>, Serializable {
|
||||||
|
@ -203,26 +208,26 @@ public class Book implements Comparable<Book>, Serializable {
|
||||||
return get(BookElement.rating_average);
|
return get(BookElement.rating_average);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRating_average(String rating_average) {
|
|
||||||
set(BookElement.rating_average, rating_average);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRating_average(double rating_average) {
|
public void setRating_average(double rating_average) {
|
||||||
set(BookElement.rating_average, "" + rating_average);
|
set(BookElement.rating_average, "" + rating_average);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRating_average(String rating_average) {
|
||||||
|
set(BookElement.rating_average, rating_average);
|
||||||
|
}
|
||||||
|
|
||||||
public String getRating_count() {
|
public String getRating_count() {
|
||||||
return get(BookElement.rating_count);
|
return get(BookElement.rating_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRating_count(String rating_count) {
|
|
||||||
set(BookElement.rating_count, rating_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRating_count(int rating_count) {
|
public void setRating_count(int rating_count) {
|
||||||
set(BookElement.rating_count, "" + rating_count);
|
set(BookElement.rating_count, "" + rating_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRating_count(String rating_count) {
|
||||||
|
set(BookElement.rating_count, rating_count);
|
||||||
|
}
|
||||||
|
|
||||||
public String getRelease_date() {
|
public String getRelease_date() {
|
||||||
return get(BookElement.release_date);
|
return get(BookElement.release_date);
|
||||||
}
|
}
|
||||||
|
@ -287,6 +292,28 @@ public class Book implements Comparable<Book>, Serializable {
|
||||||
set(BookElement.purchase_date, purchaseDateText);
|
set(BookElement.purchase_date, purchaseDateText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDurationHHMM() {
|
||||||
|
long seconds = TimeToSeconds.parseTimeStringToSeconds(getDuration());
|
||||||
|
return TimeToSeconds.secondsToHHMM(seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReleaseDateSortable() {
|
||||||
|
String date = getRelease_date();
|
||||||
|
if (!date.isEmpty()) {
|
||||||
|
// 11-MAR-2015
|
||||||
|
SimpleDateFormat parseFormat = new SimpleDateFormat("dd-MMM-yyyy");
|
||||||
|
try {
|
||||||
|
Date d = parseFormat.parse(date);
|
||||||
|
SimpleDateFormat dispalyFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
String out = dispalyFormat.format(d);
|
||||||
|
return out;
|
||||||
|
} catch (ParseException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getPurchaseDateSortable() {
|
public String getPurchaseDateSortable() {
|
||||||
|
@ -294,10 +321,9 @@ public class Book implements Comparable<Book>, Serializable {
|
||||||
if (!date.isEmpty()) {
|
if (!date.isEmpty()) {
|
||||||
String dt[] = date.split("-");
|
String dt[] = date.split("-");
|
||||||
if (dt.length == 3) {
|
if (dt.length == 3) {
|
||||||
return "20"+ dt[2] + "-" + dt[0] + "-" + dt[1]; // yyyy-mm-dd for sorting and viewing
|
return "20" + dt[2] + "-" + dt[0] + "-" + dt[1]; // yyyy-mm-dd for sorting and viewing
|
||||||
// warning, y3k bug
|
// warning, y3k bug
|
||||||
} else
|
} else {
|
||||||
{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -274,8 +274,13 @@ public class ConvertJob implements IQueueJob, LineListener {
|
||||||
ok = true;
|
ok = true;
|
||||||
if (progress != null)
|
if (progress != null)
|
||||||
progress.setTask(null, "Complete");
|
progress.setTask(null, "Complete");
|
||||||
|
|
||||||
|
long time = System.currentTimeMillis() - start;
|
||||||
|
|
||||||
|
LOG.info("Converted your "+book+" to mp3: "+mp3.getAbsolutePath()+" size="+mp3.length()+" in " + (time / 1000L) + " seconds.");
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Error converting book:" + book, e);
|
LOG.error("Error converting book to MP3:" + book, e);
|
||||||
if (progress != null) {
|
if (progress != null) {
|
||||||
progress.setSubTask(e.getMessage());
|
progress.setSubTask(e.getMessage());
|
||||||
}
|
}
|
||||||
|
@ -288,9 +293,7 @@ public class ConvertJob implements IQueueJob, LineListener {
|
||||||
mp3.delete();
|
mp3.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
long time = System.currentTimeMillis() - start;
|
|
||||||
|
|
||||||
LOG.info("converted " + mp3.getAbsolutePath() + " " + (int) time / 1000 + " seconds.");
|
|
||||||
return mp3;
|
return mp3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -612,7 +612,7 @@ public class AudibleGUI implements BookListener, ConnectionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void exportWebPage() {
|
public void exportWebPage(boolean showUserInterface) {
|
||||||
try {
|
try {
|
||||||
File destDir = Directories.getDir(Directories.WEB);
|
File destDir = Directories.getDir(Directories.WEB);
|
||||||
|
|
||||||
|
@ -622,18 +622,18 @@ public class AudibleGUI implements BookListener, ConnectionListener {
|
||||||
// sort by purchase date.
|
// sort by purchase date.
|
||||||
list.sort((b1, b2) -> -1 * b1.getPurchaseDate().compareTo(b2.getPurchaseDate()));
|
list.sort((b1, b2) -> -1 * b1.getPurchaseDate().compareTo(b2.getPurchaseDate()));
|
||||||
|
|
||||||
PageBuilderTask task = new PageBuilderTask(destDir, list);
|
PageBuilderTask task = new PageBuilderTask(destDir, list, prefs.webPageIncludeMP3);
|
||||||
ProgressDialog.doProgressTask(task);
|
ProgressDialog.doProgressTask(task);
|
||||||
File index = new File(destDir, "books.html");
|
File index = new File(destDir, "index.html");
|
||||||
if (index.exists()) {
|
if (index.exists()) {
|
||||||
|
|
||||||
LOG.info("Book html file is: "+index.getAbsolutePath());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
URI i = index.toURI();
|
URI i = index.toURI();
|
||||||
String u = i.toString();
|
String u = i.toString();
|
||||||
AudibleGUI.instance.browse(u);
|
LOG.info("Book html file is: "+index.getAbsolutePath()+" url="+u);
|
||||||
|
if (showUserInterface )
|
||||||
|
AudibleGUI.instance.browse(u);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
showError(e, "displaying web page");
|
showError(e, "displaying web page");
|
||||||
}
|
}
|
||||||
|
@ -978,9 +978,9 @@ public class AudibleGUI implements BookListener, ConnectionListener {
|
||||||
final WebPage pageBuilder;
|
final WebPage pageBuilder;
|
||||||
final List<Book> books;
|
final List<Book> books;
|
||||||
|
|
||||||
PageBuilderTask(File destDir, final List<Book> list) {
|
PageBuilderTask(File destDir, final List<Book> list, boolean includeMP3) {
|
||||||
super("Creating Your Audiobook Web Page");
|
super("Creating Your Audiobook Web Page");
|
||||||
pageBuilder = new WebPage(destDir, this);
|
pageBuilder = new WebPage(destDir, this, includeMP3);
|
||||||
books = list;
|
books = list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1011,7 +1011,7 @@ public class AudibleGUI implements BookListener, ConnectionListener {
|
||||||
|
|
||||||
if (dl.size()==0 && conv.size()==0 && prefs.autoWebPage)
|
if (dl.size()==0 && conv.size()==0 && prefs.autoWebPage)
|
||||||
{
|
{
|
||||||
exportWebPage();
|
exportWebPage(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ public class Prefs {
|
||||||
public boolean autoConvert = true;
|
public boolean autoConvert = true;
|
||||||
public boolean autoDownload = false;
|
public boolean autoDownload = false;
|
||||||
public boolean autoWebPage = false;
|
public boolean autoWebPage = false;
|
||||||
|
public boolean webPageIncludeMP3=true;
|
||||||
int concurrentConversions = 3;
|
int concurrentConversions = 3;
|
||||||
int concurrentDownloads = 3;
|
int concurrentDownloads = 3;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ package org.openaudible.desktop.swt.manager;
|
||||||
public interface Version {
|
public interface Version {
|
||||||
|
|
||||||
String appName = "OpenAudible";
|
String appName = "OpenAudible";
|
||||||
String appVersion = "1.1.4";
|
String appVersion = "1.1.5";
|
||||||
boolean appDebug = false;
|
boolean appDebug = false;
|
||||||
String appLink = "http://openaudible.org";
|
String appLink = "http://openaudible.org";
|
||||||
String versionLink = "http://openaudible.org/swt_version.json";
|
String versionLink = "http://openaudible.org/swt_version.json";
|
||||||
|
|
|
@ -23,7 +23,9 @@ import org.openaudible.desktop.swt.manager.views.Preferences;
|
||||||
import org.openaudible.desktop.swt.util.shop.WidgetShop;
|
import org.openaudible.desktop.swt.util.shop.WidgetShop;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The CommandCenter is responsible to react on user-action. User action may for example occur when any item from the main menu is selected. The execute command is the main switch for running commands
|
* The CommandCenter is responsible to react on user-action.
|
||||||
|
* User action may for example occur when any item from the main menu is selected.
|
||||||
|
* The execute command is the main switch for running commands
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class CommandCenter {
|
public class CommandCenter {
|
||||||
|
@ -43,10 +45,6 @@ public class CommandCenter {
|
||||||
cb = new Clipboard(display);
|
cb = new Clipboard(display);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void search() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void userError(String s) {
|
public void userError(String s) {
|
||||||
MessageBoxFactory.showMessage(shell, SWT.ICON_INFORMATION, GUI.i18n.getTranslation("Unexpected event"), s);
|
MessageBoxFactory.showMessage(shell, SWT.ICON_INFORMATION, GUI.i18n.getTranslation("Unexpected event"), s);
|
||||||
}
|
}
|
||||||
|
@ -179,7 +177,7 @@ public class CommandCenter {
|
||||||
|
|
||||||
public void execute(Command c) {
|
public void execute(Command c) {
|
||||||
CommandCenter e = this;
|
CommandCenter e = this;
|
||||||
logger.info("Execute: " + c);
|
logger.info("Command: " + c);
|
||||||
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case About:
|
case About:
|
||||||
|
@ -232,7 +230,7 @@ public class CommandCenter {
|
||||||
VersionCheck.instance.checkForUpdate(shell, true);
|
VersionCheck.instance.checkForUpdate(shell, true);
|
||||||
break;
|
break;
|
||||||
case Export_Web_Page:
|
case Export_Web_Page:
|
||||||
AudibleGUI.instance.exportWebPage();
|
AudibleGUI.instance.exportWebPage(true);
|
||||||
break;
|
break;
|
||||||
case Refresh_Book_Info:
|
case Refresh_Book_Info:
|
||||||
AudibleGUI.instance.refreshBookInfo();
|
AudibleGUI.instance.refreshBookInfo();
|
||||||
|
|
|
@ -15,6 +15,7 @@ import org.eclipse.swt.layout.FormLayout;
|
||||||
import org.eclipse.swt.widgets.*;
|
import org.eclipse.swt.widgets.*;
|
||||||
import org.openaudible.Directories;
|
import org.openaudible.Directories;
|
||||||
import org.openaudible.audible.AudibleClient;
|
import org.openaudible.audible.AudibleClient;
|
||||||
|
import org.openaudible.audible.ConnectionNotifier;
|
||||||
import org.openaudible.desktop.swt.gui.MessageBoxFactory;
|
import org.openaudible.desktop.swt.gui.MessageBoxFactory;
|
||||||
import org.openaudible.desktop.swt.gui.SWTAsync;
|
import org.openaudible.desktop.swt.gui.SWTAsync;
|
||||||
import org.openaudible.util.Platform;
|
import org.openaudible.util.Platform;
|
||||||
|
@ -250,6 +251,20 @@ public class AudibleBrowser {
|
||||||
data.bottom = new FormAttachment(100, -5);
|
data.bottom = new FormAttachment(100, -5);
|
||||||
progressBar.setLayoutData(data);
|
progressBar.setLayoutData(data);
|
||||||
|
|
||||||
|
LocationListener ll = new LocationListener() {
|
||||||
|
@Override
|
||||||
|
public void changing(LocationEvent locationEvent) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changed(LocationEvent locationEvent) {
|
||||||
|
ConnectionNotifier.instance.setLastURL(locationEvent.location);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
browser.addLocationListener(ll);
|
||||||
|
|
||||||
browser.addStatusTextListener(event -> status.setText(event.text));
|
browser.addStatusTextListener(event -> status.setText(event.text));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -125,10 +125,9 @@ public class BookTable extends EnumTable<Book, BookTableColumn> implements BookL
|
||||||
public String getColumnDisplayable(BookTableColumn column, Book b) {
|
public String getColumnDisplayable(BookTableColumn column, Book b) {
|
||||||
String s;
|
String s;
|
||||||
if (column.equals(BookTableColumn.Time)) {
|
if (column.equals(BookTableColumn.Time)) {
|
||||||
|
|
||||||
//long seconds = TimeToSeconds.parseTimeStringToSeconds(b.getDuration());
|
//long seconds = TimeToSeconds.parseTimeStringToSeconds(b.getDuration());
|
||||||
// TimeToSeconds.secondsToTime()
|
// TimeToSeconds.secondsToTime()
|
||||||
return b.getDuration();
|
return b.getDurationHHMM();
|
||||||
|
|
||||||
}
|
}
|
||||||
s = super.getColumnDisplayable(column, b);
|
s = super.getColumnDisplayable(column, b);
|
||||||
|
@ -153,11 +152,15 @@ public class BookTable extends EnumTable<Book, BookTableColumn> implements BookL
|
||||||
*/
|
*/
|
||||||
case Narrated_By:
|
case Narrated_By:
|
||||||
return b.getNarratedBy();
|
return b.getNarratedBy();
|
||||||
|
|
||||||
case Time:
|
case Time:
|
||||||
// compare duration as seconds, not as a string..
|
// compare duration as seconds, not as a string..
|
||||||
return TimeToSeconds.parseTimeStringToSeconds(b.getDuration());
|
return TimeToSeconds.parseTimeStringToSeconds(b.getDuration());
|
||||||
case Title:
|
case Title:
|
||||||
return b.getFullTitle();
|
return b.getFullTitle();
|
||||||
|
|
||||||
|
case Released:
|
||||||
|
return b.getReleaseDateSortable();
|
||||||
case Purchased:
|
case Purchased:
|
||||||
return b.getPurchaseDateSortable();
|
return b.getPurchaseDateSortable();
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package org.openaudible.desktop.swt.manager.views;
|
package org.openaudible.desktop.swt.manager.views;
|
||||||
|
|
||||||
public enum BookTableColumn {
|
public enum BookTableColumn {
|
||||||
File, Title, Author, Narrated_By, Time, Purchased;
|
File, Title, Author, Narrated_By, Time, Purchased, Released;
|
||||||
static int widths[] = {22, 250, 150, 150, 60, 90};
|
static int widths[] = {22, 250, 150, 150, 50, 90, 90};
|
||||||
|
|
||||||
// HasAAX, HasMP3,
|
// HasAAX, HasMP3,
|
||||||
public static int[] getWidths() {
|
public static int[] getWidths() {
|
||||||
|
|
|
@ -78,7 +78,6 @@ public class Preferences extends Dialog {
|
||||||
autoDownload.setSelection(AudibleGUI.instance.prefs.autoDownload);
|
autoDownload.setSelection(AudibleGUI.instance.prefs.autoDownload);
|
||||||
autoWebPage.setSelection(AudibleGUI.instance.prefs.autoWebPage);
|
autoWebPage.setSelection(AudibleGUI.instance.prefs.autoWebPage);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fetch() {
|
private void fetch() {
|
||||||
|
|
|
@ -3,6 +3,6 @@ package org.openaudible.feeds.pagebuilder;
|
||||||
// The book info we want to export via json to be accessible via javascript
|
// The book info we want to export via json to be accessible via javascript
|
||||||
|
|
||||||
public class BookInfo {
|
public class BookInfo {
|
||||||
String title, author, narratedBy, mp3, image, description, summary, run_time, rating_average, rating_count, audible, purchased;
|
String title, author, narrated_by, mp3, image, description, summary, duration, rating_average, rating_count, link_url, purchase_date, release_date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@ package org.openaudible.feeds.pagebuilder;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.eclipse.jetty.util.IO;
|
import org.eclipse.jetty.util.IO;
|
||||||
import org.openaudible.Audible;
|
import org.openaudible.Audible;
|
||||||
import org.openaudible.BookToFilenameStrategy;
|
import org.openaudible.BookToFilenameStrategy;
|
||||||
|
@ -18,17 +20,20 @@ import java.io.File;
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class WebPage {
|
public class WebPage {
|
||||||
|
private static final Log LOG = LogFactory.getLog(WebPage.class);
|
||||||
final File webDir;
|
final File webDir;
|
||||||
final IProgressTask progress; // required
|
final IProgressTask progress; // required
|
||||||
int thumbSize = 200; // If changed, need to change html
|
int thumbSize = 200; // If changed, need to change html
|
||||||
// final static String indexName = "books.html";
|
final boolean includeMP3;
|
||||||
|
|
||||||
public WebPage(File dir, IProgressTask t) {
|
public WebPage(File dir, IProgressTask t, boolean includeMP3) {
|
||||||
webDir = dir;
|
webDir = dir;
|
||||||
progress = t;
|
progress = t;
|
||||||
|
this.includeMP3 = includeMP3;
|
||||||
assert (t != null);
|
assert (t != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,26 +41,28 @@ public class WebPage {
|
||||||
BookInfo i = new BookInfo();
|
BookInfo i = new BookInfo();
|
||||||
i.title = b.get(BookElement.fullTitle);
|
i.title = b.get(BookElement.fullTitle);
|
||||||
i.author = b.get(BookElement.author);
|
i.author = b.get(BookElement.author);
|
||||||
i.narratedBy = b.get(BookElement.narratedBy);
|
i.narrated_by = b.get(BookElement.narratedBy);
|
||||||
i.summary = b.get(BookElement.summary);
|
i.summary = b.get(BookElement.summary);
|
||||||
i.run_time = b.get(BookElement.duration);
|
i.duration = b.getDurationHHMM();
|
||||||
i.rating_average = b.get(BookElement.rating_average);
|
i.rating_average = b.get(BookElement.rating_average);
|
||||||
i.rating_count = b.get(BookElement.rating_count);
|
i.rating_count = b.get(BookElement.rating_count);
|
||||||
i.audible = b.get(BookElement.infoLink);
|
|
||||||
|
i.link_url = b.getInfoLink();
|
||||||
|
|
||||||
i.description = b.get(BookElement.description);
|
i.description = b.get(BookElement.description);
|
||||||
i.purchased = b.getPurchaseDateSortable();
|
i.purchase_date = b.getPurchaseDateSortable();
|
||||||
|
i.release_date = b.getReleaseDateSortable();
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void subtask(Book b, String s) throws Exception {
|
|
||||||
|
|
||||||
|
public void subtask(Book b, String s) throws Exception {
|
||||||
String n = b.getShortTitle();
|
String n = b.getShortTitle();
|
||||||
if (n.length() > 32)
|
if (n.length() > 32)
|
||||||
n = n.substring(0, 28) + "...";
|
n = n.substring(0, 28) + "...";
|
||||||
progress.setSubTask(s + " " + n);
|
progress.setSubTask(s + " " + n);
|
||||||
if (progress.wasCanceled())
|
if (progress.wasCanceled())
|
||||||
throw new Exception("User canceled");
|
throw new Exception("User canceled");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void buildPage(List<Book> books) throws Exception {
|
public void buildPage(List<Book> books) throws Exception {
|
||||||
|
@ -64,61 +71,73 @@ public class WebPage {
|
||||||
File coverImages = new File(webDir, "cover");
|
File coverImages = new File(webDir, "cover");
|
||||||
File thumbImages = new File(webDir, "thumb");
|
File thumbImages = new File(webDir, "thumb");
|
||||||
|
|
||||||
if (!mp3Dir.exists())
|
|
||||||
mp3Dir.mkdirs();
|
|
||||||
if (!coverImages.exists())
|
if (!coverImages.exists())
|
||||||
coverImages.mkdirs();
|
coverImages.mkdirs();
|
||||||
if (!thumbImages.exists())
|
if (!thumbImages.exists())
|
||||||
thumbImages.mkdirs();
|
thumbImages.mkdirs();
|
||||||
|
|
||||||
|
|
||||||
Gson gson = new Gson();
|
|
||||||
ArrayList<BookInfo> list = new ArrayList<>();
|
ArrayList<BookInfo> list = new ArrayList<>();
|
||||||
|
|
||||||
ArrayList<Book> toCopy = new ArrayList<>();
|
if (includeMP3) {
|
||||||
for (Book b : books) {
|
if (!mp3Dir.exists())
|
||||||
File mp3 = Audible.instance.getMP3FileDest(b);
|
mp3Dir.mkdirs();
|
||||||
if (!mp3.exists())
|
|
||||||
continue;
|
|
||||||
String fileName = getFileName(b); // human readable, without extension.
|
|
||||||
String mp3Name = fileName + ".mp3";
|
|
||||||
File mp3File = new File(mp3Dir, mp3Name);
|
|
||||||
|
|
||||||
if (!mp3File.exists() || mp3File.length() != mp3.length()) {
|
|
||||||
toCopy.add(b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (toCopy.size() > 0) {
|
|
||||||
progress.setTask("Copying MP3s to Web Page Directory", "");
|
progress.setTask("Copying MP3s to Web Page Directory", "");
|
||||||
int count = 1;
|
|
||||||
for (Book b : toCopy) {
|
|
||||||
if (progress.wasCanceled())
|
|
||||||
throw new Exception("Canceled");
|
|
||||||
|
|
||||||
|
ArrayList<Book> toCopy = new ArrayList<>();
|
||||||
|
for (Book b : books) {
|
||||||
File mp3 = Audible.instance.getMP3FileDest(b);
|
File mp3 = Audible.instance.getMP3FileDest(b);
|
||||||
|
if (!mp3.exists())
|
||||||
|
continue;
|
||||||
String fileName = getFileName(b); // human readable, without extension.
|
String fileName = getFileName(b); // human readable, without extension.
|
||||||
String mp3Name = fileName + ".mp3";
|
String mp3Name = fileName + ".mp3";
|
||||||
File mp3File = new File(mp3Dir, mp3Name);
|
File mp3File = new File(mp3Dir, mp3Name);
|
||||||
progress.setTask("Copying book " + count + " of " + toCopy.size() + " to " + mp3File.getAbsolutePath());
|
|
||||||
|
|
||||||
CopyWithProgress.copyWithProgress(progress, mp3, mp3File);
|
if (!mp3File.exists()) {
|
||||||
|
toCopy.add(b);
|
||||||
|
} else {
|
||||||
|
long s1 = mp3File.length();
|
||||||
|
long s2 = mp3.length();
|
||||||
|
long m1 = mp3File.lastModified();
|
||||||
|
long m2 = mp3.lastModified();
|
||||||
|
|
||||||
count++;
|
if (s1 != s2) {
|
||||||
|
String d1 = m1 != 0 ? new Date(m1).toString() : "0";
|
||||||
|
String d2 = m2 != 0 ? new Date(m2).toString() : "0";
|
||||||
|
LOG.info("Replacing book " + mp3.getPath() + " with " + mp3File.getPath() + " s1=" + s1 + " s2=" + s2 + " d1=" + d1 + " d2=" + d2);
|
||||||
|
boolean ok = mp3.delete();
|
||||||
|
if (ok)
|
||||||
|
toCopy.add(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (toCopy.size() > 0) {
|
||||||
|
int count = 1;
|
||||||
|
for (Book b : toCopy) {
|
||||||
|
if (progress.wasCanceled())
|
||||||
|
throw new Exception("Canceled");
|
||||||
|
|
||||||
|
File mp3 = Audible.instance.getMP3FileDest(b);
|
||||||
|
String fileName = getFileName(b); // human readable, without extension.
|
||||||
|
String mp3Name = fileName + ".mp3";
|
||||||
|
File mp3File = new File(mp3Dir, mp3Name);
|
||||||
|
progress.setTask("Copying book " + count + " of " + toCopy.size() + " to " + mp3File.getAbsolutePath());
|
||||||
|
CopyWithProgress.copyWithProgress(progress, mp3, mp3File);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
progress.setTask("Creating Book Web Page", "");
|
progress.setTask("Creating Book Web Page", "");
|
||||||
|
|
||||||
|
|
||||||
for (Book b : books) {
|
for (Book b : books) {
|
||||||
File mp3 = Audible.instance.getMP3FileDest(b);
|
File mp3 = Audible.instance.getMP3FileDest(b);
|
||||||
|
|
||||||
subtask(b, "Reading");
|
subtask(b, "Compiling book list");
|
||||||
|
|
||||||
// only export mp3
|
|
||||||
if (!mp3.exists())
|
// only export mp3
|
||||||
|
if (includeMP3 && !mp3.exists())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
File img = Audible.instance.getImageFileDest(b);
|
File img = Audible.instance.getImageFileDest(b);
|
||||||
|
@ -130,10 +149,11 @@ public class WebPage {
|
||||||
|
|
||||||
File coverFile = new File(coverImages, coverName);
|
File coverFile = new File(coverImages, coverName);
|
||||||
File thumbFile = new File(thumbImages, thumbName);
|
File thumbFile = new File(thumbImages, thumbName);
|
||||||
File mp3File = new File(mp3Dir, mp3Name);
|
|
||||||
|
|
||||||
BookInfo i = toBookInfo(b);
|
BookInfo i = toBookInfo(b);
|
||||||
i.mp3 = mp3Name;
|
if (includeMP3)
|
||||||
|
i.mp3 = mp3Name;
|
||||||
|
|
||||||
|
|
||||||
if (img.exists()) {
|
if (img.exists()) {
|
||||||
if (!coverFile.exists() || coverFile.length() != img.length()) {
|
if (!coverFile.exists() || coverFile.length() != img.length()) {
|
||||||
|
@ -151,13 +171,14 @@ public class WebPage {
|
||||||
i.image = "";
|
i.image = "";
|
||||||
|
|
||||||
list.add(i);
|
list.add(i);
|
||||||
|
if (progress.wasCanceled())
|
||||||
|
throw new Exception("User canceled");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progress.wasCanceled())
|
|
||||||
throw new Exception("User canceled");
|
|
||||||
progress.setTask(null, "Exporting web data");
|
progress.setTask(null, "Exporting web data");
|
||||||
|
|
||||||
|
Gson gson = new Gson();
|
||||||
String json = gson.toJson(list);
|
String json = gson.toJson(list);
|
||||||
|
|
||||||
try (FileWriter writer = new FileWriter(new File(webDir, "books.json"))) {
|
try (FileWriter writer = new FileWriter(new File(webDir, "books.json"))) {
|
||||||
|
|
|
@ -70,4 +70,18 @@ public class TimeToSeconds {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String secondsToHHMM(long sec) {
|
||||||
|
|
||||||
|
int m = (int) Math.round(sec/60.0);
|
||||||
|
int h = m/60;
|
||||||
|
m = m%60;
|
||||||
|
if (m>0 || h>0) {
|
||||||
|
String hh = h < 10 ? "0" + h : "" + h;
|
||||||
|
String mm = m < 10 ? "0" + m : "" + m;
|
||||||
|
return hh + ":" + mm;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 526 B |
Binary file not shown.
Before Width: | Height: | Size: 574 B |
BIN
src/main/webapp/assets/loading.gif
Normal file
BIN
src/main/webapp/assets/loading.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.3 KiB |
125
src/main/webapp/assets/openaudible.js
Normal file
125
src/main/webapp/assets/openaudible.js
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
// OpenAudible.js web page to display audio book library.
|
||||||
|
|
||||||
|
// filter out (hide) books not in filter text. If no filter text, all books are shown.
|
||||||
|
function filter() {
|
||||||
|
const text = $("#filter").val();
|
||||||
|
const rex = new RegExp(text, 'i');
|
||||||
|
$('.searchable tr').hide();
|
||||||
|
$('.searchable tr').filter(function () {
|
||||||
|
return rex.test($(this).text());
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function asString(str, len) {
|
||||||
|
if (!str) return "";
|
||||||
|
if (len)
|
||||||
|
str = str.substring(0, len);
|
||||||
|
return str.replace(/(?:\r\n|\r|\n)/g, ' ').replace(' ', ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function mp3Link(book, content) {
|
||||||
|
if (!book || !book.mp3 || !content)
|
||||||
|
return "";
|
||||||
|
return "<a href='mp3/" + encodeURIComponent(book.mp3) + "'>" + content + "</a> ";
|
||||||
|
}
|
||||||
|
|
||||||
|
function bookImage(book, addLink) {
|
||||||
|
var image = (book && book.image !== undefined) ? ("<img src='thumb/" + encodeURIComponent(book.image) + "' width='200' height='200'>")
|
||||||
|
: "<img src='assets/no_cover.png' width='200' height='200'>";
|
||||||
|
if (addLink) return mp3Link(book, image);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
// convert the json book data to a format for the book.
|
||||||
|
function populateBooks(arr, table) {
|
||||||
|
|
||||||
|
let i;
|
||||||
|
const data = [];
|
||||||
|
|
||||||
|
for (i = 0; i < arr.length; i++) {
|
||||||
|
const book = arr[i];
|
||||||
|
|
||||||
|
// Thumbnail size 200x200
|
||||||
|
const row = {};
|
||||||
|
const narrated_by = asString(book.narrated_by);
|
||||||
|
const author = asString(book.author);
|
||||||
|
const title = asString(book.title);
|
||||||
|
const duration = asString(book.duration);
|
||||||
|
|
||||||
|
row['book'] = book;
|
||||||
|
row['title'] = title;
|
||||||
|
row['narrated_by'] = narrated_by;
|
||||||
|
row['author'] = author;
|
||||||
|
row['duration'] = duration;
|
||||||
|
|
||||||
|
row['purchase_date'] = asString(book.purchase_date);
|
||||||
|
row['release_date'] = asString(book.release_date);
|
||||||
|
row['rating'] = asString(book.rating_average);
|
||||||
|
row['summary'] = asString(book.summary, 500);
|
||||||
|
row['description'] = asString(book.description, 800);
|
||||||
|
row['mp3'] = mp3Link(book, book.mp3);
|
||||||
|
row['image'] = bookImage(book, true);
|
||||||
|
|
||||||
|
let info = "<strong>" + title + "</strong><br>";
|
||||||
|
if (author.length > 0)
|
||||||
|
info += "by <i>" + author + "</i><br>";
|
||||||
|
if (narrated_by.length > 0)
|
||||||
|
info += "Narrated by " + narrated_by + "<br>";
|
||||||
|
info += duration;
|
||||||
|
info += " ";
|
||||||
|
info += asString(book.rating_average, 99);
|
||||||
|
row['info'] = info;
|
||||||
|
|
||||||
|
data.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create bootstrapTable and populate table with data.
|
||||||
|
if (table && arr.length)
|
||||||
|
{
|
||||||
|
$(table).bootstrapTable( {data: data})
|
||||||
|
.on('click-row.bs.table', function (e, row, elem) {
|
||||||
|
showBook(row.book); // row click handler.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// display a single book in a modal dialog.
|
||||||
|
function showBook(book) {
|
||||||
|
$("#detail_title").text(asString(book.title));
|
||||||
|
|
||||||
|
const image = bookImage(book, true);
|
||||||
|
$("#detail_image").html(image);
|
||||||
|
|
||||||
|
$('#title').text(asString(book.title));
|
||||||
|
$('#narrated_by').text(asString(book.narrated_by));
|
||||||
|
$('#author').text(asString(book.author));
|
||||||
|
$('#purchased').text(asString(book.purchased));
|
||||||
|
let rating = asString(book.rating_average);
|
||||||
|
|
||||||
|
if (rating) {
|
||||||
|
if (book.rating_count)
|
||||||
|
rating += " (" + book.rating_count + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#rating').text(rating);
|
||||||
|
$('#duration').text(asString(book.duration));
|
||||||
|
const summary = asString(book.summary, 9999).replace(/(?:\r\n|\r|\n)/g, '<p />');
|
||||||
|
$('#summary').text(summary);
|
||||||
|
|
||||||
|
let audible = "";
|
||||||
|
if (book.link_url) {
|
||||||
|
audible = "<a href='"+ book.link_url + "'>" + book.audible + "</a>";
|
||||||
|
}
|
||||||
|
$('#audible').html(audible);
|
||||||
|
var mp3 = mp3Link(book, book.mp3);
|
||||||
|
if (mp3) {
|
||||||
|
$("#mp3").html(mp3Link(book, book.mp3));
|
||||||
|
$("mp3_details").show();
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
$("mp3_details").hide();
|
||||||
|
}
|
||||||
|
$("#detail_modal").modal('show');
|
||||||
|
}
|
||||||
|
|
|
@ -1,139 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.10.1/bootstrap-table.min.css">
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/js/bootstrap.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.10.1/bootstrap-table.min.js"></script>
|
|
||||||
<link rel="stylesheet" type="text/css" href="assets/books.css">
|
|
||||||
<!-- Created by OpenAudible export web page. -->
|
|
||||||
<script src="books.js" type="text/javascript"></script>
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
function filter() {
|
|
||||||
var text = $("#filter").val();
|
|
||||||
var rex = new RegExp(text, 'i');
|
|
||||||
$('.searchable tr').hide();
|
|
||||||
$('.searchable tr').filter(function () {
|
|
||||||
return rex.test($(this).text());
|
|
||||||
}).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
|
||||||
populateBooks(window.myBooks);
|
|
||||||
$('#filter').keyup(filter);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
function trunc(str, len) {
|
|
||||||
if (str === undefined) return "";
|
|
||||||
return str.substring(0, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function resizeSummary(table) {
|
|
||||||
table.find('th:nth-child(2), td:nth-child(2)').css('width', '200');
|
|
||||||
}
|
|
||||||
|
|
||||||
function populateBooks(arr) {
|
|
||||||
|
|
||||||
var i;
|
|
||||||
var data = [];
|
|
||||||
|
|
||||||
for (i = 0; i < arr.length; i++) {
|
|
||||||
var book = arr[i];
|
|
||||||
|
|
||||||
var rating = book.rating_average = (book.rating_average !== undefined) ? book.rating_average : "";
|
|
||||||
var author = book.author = (book.author !== undefined) ? trunc(book.author, 20) : "";
|
|
||||||
var narratedBy = book.narratedBy = (book.narratedBy !== undefined) ? trunc(book.narratedBy, 20) : "";
|
|
||||||
var run_time = book.run_time = (book.run_time !== undefined) ? book.run_time : "";
|
|
||||||
var summary = trunc(book.description, 2048);
|
|
||||||
|
|
||||||
|
|
||||||
summary = summary.replace(/(?:\r\n|\r|\n)/g, '<p />');
|
|
||||||
|
|
||||||
var thumb = "<img src='assets/download.jpg'>";
|
|
||||||
|
|
||||||
if (book.image !== undefined)
|
|
||||||
thumb = "<img src='thumb/" + encodeURIComponent(book.image) + "' width='200' height='200'>"; // Thumbnail size
|
|
||||||
|
|
||||||
|
|
||||||
var linkName = trunc(book.title, 90);
|
|
||||||
var mp3 = encodeURIComponent(book.mp3);
|
|
||||||
var thumbLink = "<a href='mp3/" + mp3 + "'>" + thumb + "</a> ";
|
|
||||||
|
|
||||||
var info = "<strong>" + trunc(book.title, 50) + "</strong><br>";
|
|
||||||
|
|
||||||
if (author.length > 0)
|
|
||||||
info += "by <i>" + author + "</i><br>";
|
|
||||||
if (narratedBy.length > 0)
|
|
||||||
info += "Narrated by " + narratedBy + "<br>";
|
|
||||||
info += run_time;
|
|
||||||
info += " ";
|
|
||||||
info += trunc(book.rating_average, 99);
|
|
||||||
|
|
||||||
var row = {};
|
|
||||||
row['title'] = thumbLink;
|
|
||||||
row['info'] = info;
|
|
||||||
row['purchased'] = book.purchased;
|
|
||||||
row['summary'] = summary;
|
|
||||||
|
|
||||||
data.push(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#table').bootstrapTable({data: data});
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<title>OpenAudible collection of audiobooks</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
|
|
||||||
<div id="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4 col-md-offset-2">
|
|
||||||
<div class="input-group">
|
|
||||||
|
|
||||||
<div class="input-group-btn">
|
|
||||||
<button class="btn btn-default" type="submit"><i class="glyphicon glyphicon-search"></i></button>
|
|
||||||
</div>
|
|
||||||
<input type="text" class="form-control" placeholder="Search" name="srch-term" id="filter">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
|
|
||||||
<table id="table" class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th data-field="title" data-sortable="true">Title</th>
|
|
||||||
<th data-field="info" data-sortable="true">Info</th>
|
|
||||||
<th data-field="purchased" data-sortable="true">Purchased</th>
|
|
||||||
<th data-field="summary" data-sortable="false">Summary</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="searchable">
|
|
||||||
</tbody>
|
|
||||||
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
177
src/main/webapp/index.html
Normal file
177
src/main/webapp/index.html
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<!-- Generated by openaudible.org -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.10.1/bootstrap-table.min.css">
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/js/bootstrap.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.10.1/bootstrap-table.min.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="assets/books.css">
|
||||||
|
<script src="assets/openaudible.js" type="text/javascript"></script>
|
||||||
|
<script src="books.js" type="text/javascript"></script> <!-- Created by OpenAudible export web page. -->
|
||||||
|
<title>OpenAudible</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="container">
|
||||||
|
<div class="row"> <!-- search filter row -->
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-btn">
|
||||||
|
<button class="btn btn-default" type="submit"><i class="glyphicon glyphicon-search"></i></button>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="form-control" placeholder="Search" name="srch-term" id="filter">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label><input type="checkbox" name="checkbox" id="show_images">Show Images</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-2 align-text-bottom text-center">
|
||||||
|
<strong><a href="http://openaudible.org">OpenAudible</a></strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row" class="book_table" id="compact_view"> <!-- Table row -->
|
||||||
|
<div class="col-md-12">
|
||||||
|
<table id="compact_table" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th data-field="title" data-sortable="true">Title</th>
|
||||||
|
<th data-field="author" data-sortable="true">Author</th>
|
||||||
|
<th data-field="narrated_by" data-sortable="true">Narrator</th>
|
||||||
|
<th data-field="duration" data-sortable="true">Duration</th>
|
||||||
|
<th data-field="release_date" data-sortable="true">Released</th>
|
||||||
|
<th data-field="purchase_date" data-sortable="true">Purchased</th>
|
||||||
|
<th data-field="description" data-sortable="true">Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="searchable"> <!-- Filled in using json. -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row" class="book_table" id="image_view"> <!-- Table row -->
|
||||||
|
<div class="col-md-12">
|
||||||
|
<table id="image_table" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th data-field="image" data-sortable="true">Title</th>
|
||||||
|
<th data-field="info" data-sortable="true">Info</th>
|
||||||
|
<th data-field="release_date" data-sortable="true">Released</th>
|
||||||
|
<th data-field="purchase_date" data-sortable="true">Purchased</th>
|
||||||
|
<th data-field="summary" data-sortable="false">Summary</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="searchable"> <!-- Body of table filled in using populateBooks . -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="detail_modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"
|
||||||
|
aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h4 class="modal-title" id="detail_title"></h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-5" id="detail_image">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-7" id="detail_details">
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th class="detail_key">Author:</th>
|
||||||
|
<td class="detail_value" id="author">Value</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="detail_key">Narrated By:</th>
|
||||||
|
<td class="detail_value" id="narrated_by">Value</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="detail_key">Duration:</th>
|
||||||
|
<td class="detail_value" id="duration">Value</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="detail_key">Purchased</th>
|
||||||
|
<td class="detail_value" id="purchase_date">Value</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="detail_key">Rating:</th>
|
||||||
|
<td class="detail_value" id="rating">Value</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="mp3_details">
|
||||||
|
<th class="detail_key">MP3:</th>
|
||||||
|
<td class="detail_value" id="mp3">Value</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div id="summary" class='summary'></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
function showImages(showImages) {
|
||||||
|
|
||||||
|
console.log("showImages:" + showImages);
|
||||||
|
|
||||||
|
if (showImages) {
|
||||||
|
$("#image_table").show();
|
||||||
|
$("#compact_table").hide();
|
||||||
|
populateBooks(window.myBooks, "#image_table");
|
||||||
|
} else {
|
||||||
|
$("#image_table").hide();
|
||||||
|
$("#compact_table").show();
|
||||||
|
populateBooks(window.myBooks, "#compact_table");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
|
||||||
|
// handle show image checkbox change
|
||||||
|
$('#show_images').change(function () {
|
||||||
|
showImages($(this).prop('checked'));
|
||||||
|
});
|
||||||
|
|
||||||
|
showImages(false); // display
|
||||||
|
$('#filter').keyup(filter);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue