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
|
||||
Desktop.ini
|
||||
Thumbs.db
|
||||
*/Thumbs.db
|
||||
|
||||
# java
|
||||
*.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) {
|
||||
if (!ok(b)) {
|
||||
LOG.warn("invalid book: " + checkBook(b));
|
||||
|
@ -130,6 +142,9 @@ public class Audible implements IQueueListener<Book> {
|
|||
if (!hasBook(b)) {
|
||||
if (!ignoreBook(b)) {
|
||||
synchronized (books) {
|
||||
normalizeBook(b);
|
||||
|
||||
|
||||
books.put(b.getProduct_id(), b);
|
||||
}
|
||||
BookNotifier.getInstance().bookAdded(b);
|
||||
|
@ -363,8 +378,9 @@ public class Audible implements IQueueListener<Book> {
|
|||
booksUpdated++;
|
||||
}
|
||||
}
|
||||
if (booksUpdated > 0 && quick)
|
||||
updateLibrary(false);
|
||||
|
||||
// if (booksUpdated > 0 && quick)
|
||||
// updateLibrary(false);
|
||||
|
||||
|
||||
LOG.info("Updated " + list.size() + " books");
|
||||
|
@ -722,7 +738,7 @@ public class Audible implements IQueueListener<Book> {
|
|||
|
||||
@Override
|
||||
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) {
|
||||
try {
|
||||
AAXParser.instance.update(b);
|
||||
|
|
|
@ -108,7 +108,10 @@ public class AudibleScraper {
|
|||
for (BasicClientCookie bc : list) {
|
||||
Cookie c = new Cookie(bc.getDomain(), bc.getName(), bc.getValue());
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
static int maxLoginAttempts = 2;
|
||||
protected boolean login() throws IOException {
|
||||
return login(0);
|
||||
}
|
||||
|
||||
protected boolean login(int attempt) throws IOException {
|
||||
|
||||
AudibleAccountPrefs copy = account;
|
||||
|
||||
|
@ -187,6 +194,8 @@ public class AudibleScraper {
|
|||
HtmlForm login = page.getFormByName("signIn");
|
||||
|
||||
if (login == null) {
|
||||
// TODO: find sign-in anchor and click it..
|
||||
|
||||
LOG.info("login form not found for page:" + page.getTitleText());
|
||||
return false;
|
||||
}
|
||||
|
@ -220,7 +229,7 @@ public class AudibleScraper {
|
|||
HtmlElement captchaImageDiv = findById("ap_captcha_img");
|
||||
|
||||
if (captchaImageDiv != null || ap_captcha_table != null) {
|
||||
|
||||
LOG.info("Appears to be a captcha... I am a bot.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -240,7 +249,10 @@ public class AudibleScraper {
|
|||
LOG.info(page.getUrl());
|
||||
|
||||
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 {
|
||||
HTMLUtil.debugFile("submitting-credentials").delete();
|
||||
|
@ -278,13 +290,12 @@ public class AudibleScraper {
|
|||
|
||||
if (signOut == null && accountDetails == null && signIn == null) {
|
||||
HTMLUtil.debugNode(page, "checkLoggedIn");
|
||||
|
||||
}
|
||||
return isLoggedIn();
|
||||
}
|
||||
|
||||
public String homeURL() {
|
||||
return "/access";
|
||||
return "/";
|
||||
}
|
||||
|
||||
public String getPageURL() {
|
||||
|
@ -308,8 +319,7 @@ public class AudibleScraper {
|
|||
if (true)
|
||||
getWebClient().setJavascriptEnabled(true);
|
||||
try {
|
||||
setURL(homeURL());
|
||||
// HTMLUtil.debugNode(page, "homeURL");
|
||||
setURL(homeURL(), "Loading web page...");
|
||||
|
||||
if (checkLoggedIn())
|
||||
return;
|
||||
|
@ -355,32 +365,66 @@ public class AudibleScraper {
|
|||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
public boolean setURLAndLogIn(String u) throws IOException {
|
||||
|
||||
public boolean clickLib() throws Exception {
|
||||
HtmlAnchor lib=null;
|
||||
for (HtmlAnchor n : page.getAnchors()) {
|
||||
if (n.getHrefAttribute().contains("/lib"))
|
||||
lib = n;
|
||||
setURL(u);
|
||||
if (!checkLoggedIn()) {
|
||||
LOG.info("not logged in after going to:"+u);
|
||||
// trouble.. try again
|
||||
login();
|
||||
return checkLoggedIn();
|
||||
}
|
||||
|
||||
|
||||
if (lib!=null) {
|
||||
setPage(lib.click());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
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");
|
||||
if (!checkLoggedIn()) {
|
||||
|
@ -391,6 +435,7 @@ public class AudibleScraper {
|
|||
setURL("/lib");
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
|
@ -493,7 +538,8 @@ public class AudibleScraper {
|
|||
|
||||
if (next == null) {
|
||||
assert (pageNum == 1);
|
||||
setURL("/lib", "Reading Library...");
|
||||
lib();
|
||||
// setURL("/lib", "Reading Library...");
|
||||
setPageFilter();
|
||||
|
||||
} else {
|
||||
|
@ -565,8 +611,8 @@ public class AudibleScraper {
|
|||
private void setPageFilter() {
|
||||
try {
|
||||
DomElement purchaseDateFilter = page.getElementByName("purchaseDateFilter");
|
||||
if (purchaseDateFilter != null && purchaseDateFilter instanceof HtmlSelect) {
|
||||
HtmlSelect h = (HtmlSelect) purchaseDateFilter;
|
||||
|
||||
HtmlSelect h = (HtmlSelect) purchaseDateFilter;
|
||||
int i = h.getSelectedIndex();
|
||||
if (i != 0) {
|
||||
HtmlOption all = h.getOption(0);
|
||||
|
@ -590,11 +636,11 @@ public class AudibleScraper {
|
|||
}
|
||||
|
||||
return;
|
||||
} else {
|
||||
LOG.info("warning: did not find library filter htmlSelect.");
|
||||
}
|
||||
|
||||
} 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;
|
||||
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.openaudible.AudibleAccountPrefs;
|
||||
import org.openaudible.util.EventNotifier;
|
||||
|
||||
|
@ -8,6 +10,11 @@ import org.openaudible.util.EventNotifier;
|
|||
public class ConnectionNotifier extends EventNotifier<ConnectionListener> implements ConnectionListener {
|
||||
public static final ConnectionNotifier instance = new ConnectionNotifier();
|
||||
State state = State.Not_Connected;
|
||||
private static final Log LOG = LogFactory.getLog(ConnectionNotifier.class);
|
||||
|
||||
private String lastURL="";
|
||||
|
||||
|
||||
|
||||
private ConnectionNotifier() {
|
||||
}
|
||||
|
@ -18,11 +25,15 @@ public class ConnectionNotifier extends EventNotifier<ConnectionListener> implem
|
|||
|
||||
@Override
|
||||
public void connectionChanged(boolean connected) {
|
||||
state = connected ? State.Connected : State.Disconnected;
|
||||
|
||||
for (ConnectionListener l : getListeners()) {
|
||||
l.connectionChanged(connected);
|
||||
State newState = connected ? State.Connected : State.Disconnected;
|
||||
if (state!=newState) {
|
||||
state = newState;
|
||||
for (ConnectionListener l : getListeners()) {
|
||||
l.connectionChanged(connected);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
|
@ -57,6 +68,15 @@ public class ConnectionNotifier extends EventNotifier<ConnectionListener> implem
|
|||
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.
|
||||
// connected means in account
|
||||
|
|
|
@ -8,6 +8,7 @@ import org.openaudible.books.BookElement;
|
|||
import org.openaudible.util.HTMLUtil;
|
||||
import org.openaudible.util.Util;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
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<String> colNames = new ArrayList<>();
|
||||
|
||||
HtmlTable table = fragment.getFirstByXPath("//table");
|
||||
HtmlTable table = p.getFirstByXPath("//table");
|
||||
if (table == null)
|
||||
return list;
|
||||
|
||||
|
@ -103,7 +104,7 @@ public enum LibraryParser {
|
|||
rindex++;
|
||||
if (rindex == 1)
|
||||
continue; // skip header row.
|
||||
Book b = parseLibraryRow(r);
|
||||
Book b = parseLibraryRow(p, r);
|
||||
|
||||
if (b != null) {
|
||||
String chk = b.checkBook();
|
||||
|
@ -125,7 +126,7 @@ public enum LibraryParser {
|
|||
String debugString = "OR_ORIG";
|
||||
|
||||
|
||||
private Book parseLibraryRow(HtmlTableRow r) {
|
||||
private Book parseLibraryRow(HtmlPage p, HtmlTableRow r) {
|
||||
|
||||
String xml = Util.cleanString(r.asXml());
|
||||
if (r.getCells().size() == 0)
|
||||
|
@ -160,14 +161,14 @@ public enum LibraryParser {
|
|||
|
||||
for (BookColumns col : BookColumns.parseOrder) {
|
||||
HtmlElement cell = cells.get(col.ordinal());
|
||||
parseBookColumn(col, cell, b);
|
||||
parseBookColumn(p, col, cell, 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");
|
||||
String text = Util.cleanString(cell.asText());
|
||||
|
@ -192,24 +193,32 @@ public enum LibraryParser {
|
|||
anchors = cell.getElementsByTagName("a");
|
||||
for (int x = 0; x < anchors.size(); x++) {
|
||||
HtmlAnchor a = (HtmlAnchor) anchors.get(x);
|
||||
String url = a.getHrefAttribute();
|
||||
String href = a.getHrefAttribute();
|
||||
|
||||
// /pd/Fiction/Exodus-Audiobook/B008I3VMMQ?
|
||||
if (url.startsWith("/pd/")) {
|
||||
int q = url.indexOf("?");
|
||||
if (href.startsWith("/pd/")) {
|
||||
URL url = p.getUrl();
|
||||
String protocol = url.getProtocol();
|
||||
String host = url.getHost();
|
||||
|
||||
int q = href.indexOf("?");
|
||||
if (q != -1)
|
||||
url = url.substring(0, q);
|
||||
href = href.substring(0, q);
|
||||
|
||||
boolean ok = false;
|
||||
|
||||
|
||||
if (b.has(BookElement.asin) && url.contains(b.getAsin()))
|
||||
if (b.has(BookElement.asin) && href.contains(b.getAsin()))
|
||||
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;
|
||||
if (ok)
|
||||
b.setInfoLink(url);
|
||||
if (ok) {
|
||||
|
||||
String full_url = String.format("%s://%s%s", protocol, host, href);
|
||||
|
||||
b.setInfoLink(full_url);
|
||||
}
|
||||
else
|
||||
LOG.info("Unknown product link for " + b + " at " + url);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
package org.openaudible.books;
|
||||
|
||||
|
||||
import org.openaudible.util.TimeToSeconds;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class Book implements Comparable<Book>, Serializable {
|
||||
|
@ -203,26 +208,26 @@ public class Book implements Comparable<Book>, Serializable {
|
|||
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) {
|
||||
set(BookElement.rating_average, "" + rating_average);
|
||||
}
|
||||
|
||||
public void setRating_average(String rating_average) {
|
||||
set(BookElement.rating_average, rating_average);
|
||||
}
|
||||
|
||||
public String getRating_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) {
|
||||
set(BookElement.rating_count, "" + rating_count);
|
||||
}
|
||||
|
||||
public void setRating_count(String rating_count) {
|
||||
set(BookElement.rating_count, rating_count);
|
||||
}
|
||||
|
||||
public String getRelease_date() {
|
||||
return get(BookElement.release_date);
|
||||
}
|
||||
|
@ -287,6 +292,28 @@ public class Book implements Comparable<Book>, Serializable {
|
|||
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() {
|
||||
|
@ -294,10 +321,9 @@ public class Book implements Comparable<Book>, Serializable {
|
|||
if (!date.isEmpty()) {
|
||||
String dt[] = date.split("-");
|
||||
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
|
||||
} else
|
||||
{
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -274,8 +274,13 @@ public class ConvertJob implements IQueueJob, LineListener {
|
|||
ok = true;
|
||||
if (progress != null)
|
||||
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) {
|
||||
LOG.error("Error converting book:" + book, e);
|
||||
LOG.error("Error converting book to MP3:" + book, e);
|
||||
if (progress != null) {
|
||||
progress.setSubTask(e.getMessage());
|
||||
}
|
||||
|
@ -288,9 +293,7 @@ public class ConvertJob implements IQueueJob, LineListener {
|
|||
mp3.delete();
|
||||
}
|
||||
}
|
||||
long time = System.currentTimeMillis() - start;
|
||||
|
||||
LOG.info("converted " + mp3.getAbsolutePath() + " " + (int) time / 1000 + " seconds.");
|
||||
return mp3;
|
||||
}
|
||||
|
||||
|
|
|
@ -612,7 +612,7 @@ public class AudibleGUI implements BookListener, ConnectionListener {
|
|||
}
|
||||
|
||||
|
||||
public void exportWebPage() {
|
||||
public void exportWebPage(boolean showUserInterface) {
|
||||
try {
|
||||
File destDir = Directories.getDir(Directories.WEB);
|
||||
|
||||
|
@ -622,18 +622,18 @@ public class AudibleGUI implements BookListener, ConnectionListener {
|
|||
// sort by purchase date.
|
||||
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);
|
||||
File index = new File(destDir, "books.html");
|
||||
File index = new File(destDir, "index.html");
|
||||
if (index.exists()) {
|
||||
|
||||
LOG.info("Book html file is: "+index.getAbsolutePath());
|
||||
|
||||
try {
|
||||
URI i = index.toURI();
|
||||
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) {
|
||||
showError(e, "displaying web page");
|
||||
}
|
||||
|
@ -978,9 +978,9 @@ public class AudibleGUI implements BookListener, ConnectionListener {
|
|||
final WebPage pageBuilder;
|
||||
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");
|
||||
pageBuilder = new WebPage(destDir, this);
|
||||
pageBuilder = new WebPage(destDir, this, includeMP3);
|
||||
books = list;
|
||||
}
|
||||
|
||||
|
@ -1011,7 +1011,7 @@ public class AudibleGUI implements BookListener, ConnectionListener {
|
|||
|
||||
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 autoDownload = false;
|
||||
public boolean autoWebPage = false;
|
||||
|
||||
public boolean webPageIncludeMP3=true;
|
||||
int concurrentConversions = 3;
|
||||
int concurrentDownloads = 3;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package org.openaudible.desktop.swt.manager;
|
|||
public interface Version {
|
||||
|
||||
String appName = "OpenAudible";
|
||||
String appVersion = "1.1.4";
|
||||
String appVersion = "1.1.5";
|
||||
boolean appDebug = false;
|
||||
String appLink = "http://openaudible.org";
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
@ -43,10 +45,6 @@ public class CommandCenter {
|
|||
cb = new Clipboard(display);
|
||||
}
|
||||
|
||||
public void search() {
|
||||
|
||||
}
|
||||
|
||||
public void userError(String 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) {
|
||||
CommandCenter e = this;
|
||||
logger.info("Execute: " + c);
|
||||
logger.info("Command: " + c);
|
||||
|
||||
switch (c) {
|
||||
case About:
|
||||
|
@ -232,7 +230,7 @@ public class CommandCenter {
|
|||
VersionCheck.instance.checkForUpdate(shell, true);
|
||||
break;
|
||||
case Export_Web_Page:
|
||||
AudibleGUI.instance.exportWebPage();
|
||||
AudibleGUI.instance.exportWebPage(true);
|
||||
break;
|
||||
case Refresh_Book_Info:
|
||||
AudibleGUI.instance.refreshBookInfo();
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.eclipse.swt.layout.FormLayout;
|
|||
import org.eclipse.swt.widgets.*;
|
||||
import org.openaudible.Directories;
|
||||
import org.openaudible.audible.AudibleClient;
|
||||
import org.openaudible.audible.ConnectionNotifier;
|
||||
import org.openaudible.desktop.swt.gui.MessageBoxFactory;
|
||||
import org.openaudible.desktop.swt.gui.SWTAsync;
|
||||
import org.openaudible.util.Platform;
|
||||
|
@ -250,6 +251,20 @@ public class AudibleBrowser {
|
|||
data.bottom = new FormAttachment(100, -5);
|
||||
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));
|
||||
}
|
||||
|
||||
|
|
|
@ -125,10 +125,9 @@ public class BookTable extends EnumTable<Book, BookTableColumn> implements BookL
|
|||
public String getColumnDisplayable(BookTableColumn column, Book b) {
|
||||
String s;
|
||||
if (column.equals(BookTableColumn.Time)) {
|
||||
|
||||
//long seconds = TimeToSeconds.parseTimeStringToSeconds(b.getDuration());
|
||||
// TimeToSeconds.secondsToTime()
|
||||
return b.getDuration();
|
||||
return b.getDurationHHMM();
|
||||
|
||||
}
|
||||
s = super.getColumnDisplayable(column, b);
|
||||
|
@ -153,11 +152,15 @@ public class BookTable extends EnumTable<Book, BookTableColumn> implements BookL
|
|||
*/
|
||||
case Narrated_By:
|
||||
return b.getNarratedBy();
|
||||
|
||||
case Time:
|
||||
// compare duration as seconds, not as a string..
|
||||
return TimeToSeconds.parseTimeStringToSeconds(b.getDuration());
|
||||
case Title:
|
||||
return b.getFullTitle();
|
||||
|
||||
case Released:
|
||||
return b.getReleaseDateSortable();
|
||||
case Purchased:
|
||||
return b.getPurchaseDateSortable();
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package org.openaudible.desktop.swt.manager.views;
|
||||
|
||||
public enum BookTableColumn {
|
||||
File, Title, Author, Narrated_By, Time, Purchased;
|
||||
static int widths[] = {22, 250, 150, 150, 60, 90};
|
||||
File, Title, Author, Narrated_By, Time, Purchased, Released;
|
||||
static int widths[] = {22, 250, 150, 150, 50, 90, 90};
|
||||
|
||||
// HasAAX, HasMP3,
|
||||
public static int[] getWidths() {
|
||||
|
|
|
@ -78,7 +78,6 @@ public class Preferences extends Dialog {
|
|||
autoDownload.setSelection(AudibleGUI.instance.prefs.autoDownload);
|
||||
autoWebPage.setSelection(AudibleGUI.instance.prefs.autoWebPage);
|
||||
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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 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.openaudible.Audible;
|
||||
import org.openaudible.BookToFilenameStrategy;
|
||||
|
@ -18,17 +20,20 @@ import java.io.File;
|
|||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class WebPage {
|
||||
private static final Log LOG = LogFactory.getLog(WebPage.class);
|
||||
final File webDir;
|
||||
final IProgressTask progress; // required
|
||||
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;
|
||||
progress = t;
|
||||
this.includeMP3 = includeMP3;
|
||||
assert (t != null);
|
||||
}
|
||||
|
||||
|
@ -36,26 +41,28 @@ public class WebPage {
|
|||
BookInfo i = new BookInfo();
|
||||
i.title = b.get(BookElement.fullTitle);
|
||||
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.run_time = b.get(BookElement.duration);
|
||||
i.duration = b.getDurationHHMM();
|
||||
i.rating_average = b.get(BookElement.rating_average);
|
||||
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.purchased = b.getPurchaseDateSortable();
|
||||
i.purchase_date = b.getPurchaseDateSortable();
|
||||
i.release_date = b.getReleaseDateSortable();
|
||||
return i;
|
||||
}
|
||||
|
||||
public void subtask(Book b, String s) throws Exception {
|
||||
|
||||
public void subtask(Book b, String s) throws Exception {
|
||||
String n = b.getShortTitle();
|
||||
if (n.length() > 32)
|
||||
n = n.substring(0, 28) + "...";
|
||||
progress.setSubTask(s + " " + n);
|
||||
if (progress.wasCanceled())
|
||||
throw new Exception("User canceled");
|
||||
|
||||
}
|
||||
|
||||
public void buildPage(List<Book> books) throws Exception {
|
||||
|
@ -64,61 +71,73 @@ public class WebPage {
|
|||
File coverImages = new File(webDir, "cover");
|
||||
File thumbImages = new File(webDir, "thumb");
|
||||
|
||||
if (!mp3Dir.exists())
|
||||
mp3Dir.mkdirs();
|
||||
if (!coverImages.exists())
|
||||
coverImages.mkdirs();
|
||||
if (!thumbImages.exists())
|
||||
thumbImages.mkdirs();
|
||||
|
||||
|
||||
Gson gson = new Gson();
|
||||
ArrayList<BookInfo> list = new ArrayList<>();
|
||||
|
||||
ArrayList<Book> toCopy = new ArrayList<>();
|
||||
for (Book b : books) {
|
||||
File mp3 = Audible.instance.getMP3FileDest(b);
|
||||
if (!mp3.exists())
|
||||
continue;
|
||||
String fileName = getFileName(b); // human readable, without extension.
|
||||
String mp3Name = fileName + ".mp3";
|
||||
File mp3File = new File(mp3Dir, mp3Name);
|
||||
if (includeMP3) {
|
||||
if (!mp3Dir.exists())
|
||||
mp3Dir.mkdirs();
|
||||
|
||||
if (!mp3File.exists() || mp3File.length() != mp3.length()) {
|
||||
toCopy.add(b);
|
||||
}
|
||||
}
|
||||
if (toCopy.size() > 0) {
|
||||
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);
|
||||
if (!mp3.exists())
|
||||
continue;
|
||||
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);
|
||||
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", "");
|
||||
|
||||
|
||||
for (Book b : books) {
|
||||
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;
|
||||
|
||||
File img = Audible.instance.getImageFileDest(b);
|
||||
|
@ -130,10 +149,11 @@ public class WebPage {
|
|||
|
||||
File coverFile = new File(coverImages, coverName);
|
||||
File thumbFile = new File(thumbImages, thumbName);
|
||||
File mp3File = new File(mp3Dir, mp3Name);
|
||||
|
||||
BookInfo i = toBookInfo(b);
|
||||
i.mp3 = mp3Name;
|
||||
if (includeMP3)
|
||||
i.mp3 = mp3Name;
|
||||
|
||||
|
||||
if (img.exists()) {
|
||||
if (!coverFile.exists() || coverFile.length() != img.length()) {
|
||||
|
@ -151,13 +171,14 @@ public class WebPage {
|
|||
i.image = "";
|
||||
|
||||
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");
|
||||
|
||||
Gson gson = new Gson();
|
||||
String json = gson.toJson(list);
|
||||
|
||||
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