clicks and fixes

This commit is contained in:
openaudible 2018-09-07 14:30:21 -07:00
parent 2f89d5d6c6
commit caeb70deea
16 changed files with 537 additions and 282 deletions

View file

@ -25,6 +25,7 @@ import org.openaudible.download.DownloadQueue;
import org.openaudible.progress.IProgressTask;
import org.openaudible.util.CopyWithProgress;
import org.openaudible.util.HTMLUtil;
import org.openaudible.util.TimeToSeconds;
import org.openaudible.util.queues.IQueueJob;
import org.openaudible.util.queues.IQueueListener;
import org.openaudible.util.queues.ThreadedQueue;
@ -36,31 +37,34 @@ import java.io.Writer;
import java.util.*;
public class Audible implements IQueueListener<Book> {
final static String ignoreSetFileName = "ignore.json";
private static final Log LOG = LogFactory.getLog(Audible.class);
public static Audible instance; // Singleton
final String keysFileName = "keys.json";
private final HashMap<String, Book> books = new HashMap<>(); // Book.id(), Book
private final HashSet<String> ignoreSet = new HashSet<>(); // book ID's to ignore.
public DownloadQueue downloadQueue;
public ConvertQueue convertQueue;
public long totalDuration = 0;
// private String activationBytes = "";
volatile boolean quit = false;
boolean convertToMP3 = false;
String accountPrefsFileName = "account.json";
String cookiesFileName = "cookies.json";
String bookFileName = "books.json";
final String keysFileName = "keys.json";
HashSet<File> mp3Files = null;
HashSet<File> aaxFiles = null;
HashSet<Book> toDownload = new HashSet<>();
HashSet<Book> toConvert = new HashSet<>();
Object lock = new Object();
long needFileCacheUpdate = 0;
int booksUpdated = 0;
Exception last = null;
private AudibleAccountPrefs account = new AudibleAccountPrefs();
private AudibleScraper audibleScraper;
// AudibleRegion region = AudibleRegion.US;
private IProgressTask progress;
private boolean autoConvertToMP3 = false;
private final HashMap<String, Book> books = new HashMap<>(); // Book.id(), Book
// AudibleRegion region = AudibleRegion.US;
private final HashSet<String> ignoreSet = new HashSet<>(); // book ID's to ignore.
public Audible() {
@ -125,13 +129,11 @@ public class Audible implements IQueueListener<Book> {
}
// fix book info
private Book normalizeBook(Book b)
{
private Book normalizeBook(Book b) {
String link = b.getInfoLink();
if (link.startsWith("/"))
{
if (link.startsWith("/")) {
// convert to full URL.
b.setInfoLink("https://www.audible.com"+link);
b.setInfoLink("https://www.audible.com" + link);
}
return b;
}
@ -166,16 +168,14 @@ public class Audible implements IQueueListener<Book> {
return out;
}
public void addToIgnoreSet(Collection<Book> books)
{
for (Book b:books) {
public void addToIgnoreSet(Collection<Book> books) {
for (Book b : books) {
removeBook(b);
ignoreSet.add(b.id());
}
saveIgnoreSet();
}
final static String ignoreSetFileName = "ignore.json";
private void loadIgnoreSet() {
try {
Gson gson = new GsonBuilder().create();
@ -184,7 +184,7 @@ public class Audible implements IQueueListener<Book> {
if (prefsFile.exists()) {
String content = HTMLUtil.readFile(prefsFile);
HashSet set = gson.fromJson(content, HashSet.class);
if (set!=null) {
if (set != null) {
ignoreSet.clear();
ignoreSet.addAll(set);
}
@ -200,9 +200,8 @@ public class Audible implements IQueueListener<Book> {
Gson gson = new GsonBuilder().create();
try {
HTMLUtil.writeFile(Directories.META.getDir(ignoreSetFileName), gson.toJson(ignoreSet));
}catch(Throwable th)
{
LOG.error("Error saving ignore list!",th);
} catch (Throwable th) {
LOG.error("Error saving ignore list!", th);
}
}
}
@ -261,7 +260,7 @@ public class Audible implements IQueueListener<Book> {
BookNotifier.getInstance().setEnabled(true);
BookNotifier.getInstance().booksUpdated();
}
updateFileCache();
LookupKey.instance.load(Directories.BASE.getDir(keysFileName));
loadIgnoreSet();
}
@ -286,7 +285,7 @@ public class Audible implements IQueueListener<Book> {
LOG.info("Books to download:" + results.size());
}
ArrayList<Book> toConvert = toConvert();
Collection<Book> toConvert = toConvert();
if (autoConvertToMP3) {
results = convertQueue.addAll(toConvert);
@ -372,17 +371,14 @@ public class Audible implements IQueueListener<Book> {
// Look for books with missing info and re-parse if needed.
// Information can be lost if
boolean needSave = false;
for (Book b:getBooks())
{
if (!b.has(BookElement.summary) && hasAAX(b))
{
task.setTask("Updating book information", "Reading "+b);
for (Book b : getBooks()) {
if (!b.has(BookElement.summary) && hasAAX(b)) {
task.setTask("Updating book information", "Reading " + b);
needSave = AAXParser.instance.parseBook(b);
}
}
if (needSave)
{
if (needSave) {
try {
save();
} catch (IOException e) {
@ -396,8 +392,38 @@ public class Audible implements IQueueListener<Book> {
mp3Files = getFileSet(Directories.MP3);
aaxFiles = getFileSet(Directories.AAX);
needFileCacheUpdate = System.currentTimeMillis();
HashSet<Book> c = new HashSet<>();
HashSet<Book> d = new HashSet<>();
long seconds = 0;
for (Book b : getBooks()) {
if (isIgnoredBook(b)) continue;
if (canDownload(b)) d.add(b);
if (canConvert(b)) c.add(b);
seconds += TimeToSeconds.parseTimeStringToSeconds(b.getDuration());
}
synchronized (lock) {
toDownload.clear();
toDownload.addAll(d);
toConvert.clear();
toConvert.addAll(c);
totalDuration = seconds;
}
}
public int getDownloadCount() {
return toDownload.size();
}
public int getConvertCount() {
return toConvert.size();
}
public int mp3Count() {
return mp3Files.size();
}
@ -584,23 +610,24 @@ public class Audible implements IQueueListener<Book> {
return Directories.AAX.getDir(b.getProduct_id() + ".AAX");
}
public ArrayList<Book> toDownload() {
ArrayList<Book> list = new ArrayList<>();
for (Book b : getBooks()) {
if (!hasAAX(b) && !hasMP3(b) && downloadQueue.canAdd(b))
list.add(b);
}
return list;
public boolean canDownload(Book b) {
return !hasAAX(b) && !hasMP3(b) && downloadQueue.canAdd(b);
}
public ArrayList<Book> toConvert() {
ArrayList<Book> list = new ArrayList<>();
for (Book b : getBooks()) {
if (hasAAX(b) && !hasMP3(b) && convertQueue.canAdd(b))
list.add(b);
public boolean canConvert(Book b) {
return hasAAX(b) && !hasMP3(b) && convertQueue.canAdd(b);
}
public Set<Book> toDownload() {
synchronized (lock) {
return new HashSet<Book>(toDownload);
}
}
public Set<Book> toConvert() {
synchronized (lock) {
return new HashSet<Book>(toConvert);
}
return list;
}
public void quit() {
@ -854,5 +881,18 @@ public class Audible implements IQueueListener<Book> {
public boolean isIgnoredBook(Book b) {
return ignoreSet.contains(b.id());
}
public boolean inDownloadSet(Book b) {
synchronized(lock)
{
return toDownload.contains(b);
}
}
public boolean inConvertSet(Book b) {
synchronized(lock)
{
return toConvert.contains(b);
}
}
}

View file

@ -10,6 +10,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
@ -143,7 +144,7 @@ public class AudibleCLI {
audible.load();
break;
case toDownload: {
ArrayList<Book> list = audible.toDownload();
Collection<Book> list = audible.toDownload();
for (Book b : list)
println(b);
println("Need to download:" + list.size());
@ -151,7 +152,7 @@ public class AudibleCLI {
}
case toConvert: {
ArrayList<Book> list = audible.toConvert();
Collection<Book> list = audible.toConvert();
for (Book b : list)
println(b);
println("Need to convert:" + list.size());

View file

@ -29,16 +29,15 @@ import java.util.*;
// audible.com web page scraper
// Not thread safe, run single instance at a time.
public class AudibleScraper {
final static String cookiesFileName = "cookies.json";
private static final Log LOG = LogFactory.getLog(AudibleScraper.class);
static int maxLoginAttempts = 2;
final AudibleAccountPrefs account;
private final AudibleClient webClient;
public HtmlPage page;
final AudibleAccountPrefs account;
final static String cookiesFileName = "cookies.json";
boolean debugCust = false;
boolean loggedIn = false;
String clickToDownload = "Click to download ";
private IProgressTask progress;
@ -52,6 +51,13 @@ public class AudibleScraper {
}
}
public static void deleteCookies() {
File cookiesFile = Directories.META.getDir(cookiesFileName);
if (cookiesFile.exists()) {
cookiesFile.delete();
}
}
public HtmlPage getPage() {
return page;
@ -111,7 +117,7 @@ public class AudibleScraper {
// LOG.info("Cookie: "+c);
}
LOG.info("Loaded "+list.size()+" cookies");
LOG.info("Loaded " + list.size() + " cookies");
}
}
@ -121,8 +127,7 @@ public class AudibleScraper {
try {
setURL("/signout", "Signing out");
} catch(Throwable th)
{
} catch (Throwable th) {
LOG.info("signout error, ignorning...");
}
@ -136,15 +141,6 @@ public class AudibleScraper {
}
public static void deleteCookies()
{
File cookiesFile = Directories.META.getDir(cookiesFileName);
if (cookiesFile.exists()) {
cookiesFile.delete();
}
}
public void saveCookies() throws IOException {
CookieManager cm = getWebClient().getCookieManager();
@ -167,7 +163,6 @@ public class AudibleScraper {
FileUtils.writeByteArrayToFile(cookiesFile, o.getBytes());
}
static int maxLoginAttempts = 2;
protected boolean login() throws IOException {
return login(0);
}
@ -249,9 +244,8 @@ 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);
if (attempt < maxLoginAttempts) {
login(attempt + 1);
}
} else {
@ -369,7 +363,7 @@ public class AudibleScraper {
setURL(u);
if (!checkLoggedIn()) {
LOG.info("not logged in after going to:"+u);
LOG.info("not logged in after going to:" + u);
// trouble.. try again
login();
return checkLoggedIn();
@ -380,15 +374,13 @@ public class AudibleScraper {
public void lib() throws Exception {
String browserURL = ConnectionNotifier.instance.getLastURL();
if (browserURL.startsWith(getAudibleBase()))
{
if (browserURL.startsWith(getAudibleBase())) {
// a bit of a hack.. try to log in using library URL in browser.
LOG.info("Using library location from browser: "+browserURL);
LOG.info("Using library location from browser: " + browserURL);
try {
if (setURLAndLogIn(browserURL))
return;
} catch (IOException e)
{
} catch (IOException e) {
LOG.error(e);
}
}
@ -465,17 +457,22 @@ public class AudibleScraper {
}
public Page setURL(String u, String task) throws FailingHttpStatusCodeException, IOException {
return setURL(u, "", true);
}
public Page setURL(String u, String task, boolean appendURL) throws FailingHttpStatusCodeException, IOException {
EventTimer evt = new EventTimer();
if (u.startsWith("/"))
u = getAudibleBase() + u;
if (!task.isEmpty())
{
task += ": ";
if (appendURL) {
if (!task.isEmpty()) {
if (u.length() > 20)
task += "\n";
else
task += ": ";
}
task += u;
}
task+=u;
getProgress().setSubTask(task);
LOG.info("setURL:" + u);
Page p = getWebClient().getPage(u);
@ -529,8 +526,7 @@ public class AudibleScraper {
// In the US, productID that ends in a lowercase letter is a partial.
// in the UK, a partial book has PUB_000123bUK
// So if a book product ID contains any lower case letter, it should be a partial..
public boolean isPartialBook(Book b)
{
public boolean isPartialBook(Book b) {
String pid = b.getProduct_id();
return !pid.equals(pid.toUpperCase());
}
@ -568,20 +564,20 @@ public class AudibleScraper {
setPageFilter();
} else {
// getProgress().setTask("Getting a list of your library. );
EventTimer evt = new EventTimer();
// this is a bit of a hack. Extract the URL from the "next" HtmlButton.
String u = next.getAttribute("data-url");
if (u != null) {
if (!u.endsWith("&"))
u += "&";
u += "page=" + pageNum;
setURL(u, "Reading Library page " + pageNum + "... Found " + results.size() + " books");
setURL(u, "Reading Library page " + pageNum + "... Found " + results.size() + " books", false);
} else {
// this is simple, but it doesn't work. Not sure why. Javascript, something else..
page = next.click(); // go to next page.
// LOG.info(next.getClass() + " " + evt.reportString("next-click") + next.asXml());
}
}
String cur = page.getUrl().toString();
@ -603,8 +599,7 @@ public class AudibleScraper {
assert (false);
}
if (isPartialBook(b))
{
if (isPartialBook(b)) {
partialBooks++;
continue;
}
@ -617,7 +612,7 @@ public class AudibleScraper {
newBooks++;
}
}
LOG.info("pageNum="+pageNum+" total="+list.size()+" new="+newBooks+" partial="+partialBooks);
LOG.info("pageNum=" + pageNum + " total=" + list.size() + " new=" + newBooks + " partial=" + partialBooks);
if (newBooks == 0) {
@ -645,29 +640,29 @@ public class AudibleScraper {
DomElement purchaseDateFilter = page.getElementByName("purchaseDateFilter");
HtmlSelect h = (HtmlSelect) purchaseDateFilter;
int i = h.getSelectedIndex();
if (i != 0) {
HtmlOption all = h.getOption(0);
String url = all.getAttribute("data-url");
try {
if (url != null && !url.isEmpty()) {
//LOG.info("url: "+ url);
String newURL = url + "&purchaseDateFilter=all&programFilter=all&sortBy=PURCHASE_DATE.dsc";
page = (HtmlPage) setURL(newURL, "Setting view filter");
LOG.info("new URL: " + page.getUrl());
}
h = (HtmlSelect) page.getElementByName("purchaseDateFilter");
i = h.getSelectedIndex();
if (i != 0) {
LOG.error("Expected filter to be set to 0, not " + i);
}
} catch (Exception e) {
LOG.error("Error setting filter.. update may be required.", e);
int i = h.getSelectedIndex();
if (i != 0) {
HtmlOption all = h.getOption(0);
String url = all.getAttribute("data-url");
try {
if (url != null && !url.isEmpty()) {
//LOG.info("url: "+ url);
String newURL = url + "&purchaseDateFilter=all&programFilter=all&sortBy=PURCHASE_DATE.dsc";
page = (HtmlPage) setURL(newURL, "Setting view filter");
LOG.info("new URL: " + page.getUrl());
}
h = (HtmlSelect) page.getElementByName("purchaseDateFilter");
i = h.getSelectedIndex();
if (i != 0) {
LOG.error("Expected filter to be set to 0, not " + i);
}
}
return;
} catch (Exception e) {
LOG.error("Error setting filter.. update may be required.", e);
}
}
return;
} catch (Throwable 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.");

View file

@ -234,6 +234,21 @@ public enum LibraryParser {
break;
case Author:
b.setAuthor(text);
// attempt to get link to author page.
try {
anchors = cell.getElementsByTagName("a");
if (anchors.size() == 1) {
HtmlAnchor first = (HtmlAnchor) anchors.get(0);
URL url = p.getFullyQualifiedUrl(first.getHrefAttribute());
String href = url.toString();
if (href.contains("?"))
href = href.substring(0, href.indexOf("?"));
b.setAuthorLink(href);
}
} catch(Throwable th)
{
LOG.error("error getting author link:"+xml);
}
break;
case Length:
b.setDuration(text);

View file

@ -73,11 +73,24 @@ public class Book implements Comparable<Book>, Serializable {
return (getShortTitle().length() > 0) ? getShortTitle() : getFullTitle();
}
public boolean equals(Book that) {
if (that == null) return false;
if (this == that) return true;
boolean e1 = this.getProduct_id().equals(that.getProduct_id());
return e1;
@Override
public int hashCode() {
return getKey().hashCode();
}
@Override
public boolean equals(Object obj) {
assert(obj instanceof Book);
assert(obj !=null);
return (obj!=null) && this.getKey().equals(((Book)obj).getKey());
}
private String getKey() {
String k = getProduct_id();
assert(k!=null);
assert(!k.isEmpty());
return k;
}
public boolean isOK() {
@ -270,11 +283,6 @@ public class Book implements Comparable<Book>, Serializable {
return out;
}
@Override
public int hashCode() {
assert (!getProduct_id().isEmpty());
return getProduct_id().hashCode();
}
public String getPurchaseDate() {
return get(BookElement.purchase_date);
@ -322,4 +330,11 @@ public class Book implements Comparable<Book>, Serializable {
return date;
}
public String getAuthorLink() {
return get(BookElement.author_link);
}
public void setAuthorLink(String s) {
set(BookElement.author_link, s);
}
}

View file

@ -3,7 +3,7 @@ package org.openaudible.books;
// Audio Book Attributes.
//
public enum BookElement {
product_id, codec, asin, infoLink, fullTitle, author, narratedBy, summary, description, duration, format, rating_average, rating_count, release_date, purchase_date, publisher, genre, shortTitle, copyright, user_id, cust_id, order_number;
product_id, codec, asin, infoLink, fullTitle, author, narratedBy, summary, description, duration, format, rating_average, rating_count, release_date, purchase_date, publisher, genre, shortTitle, copyright, user_id, cust_id, order_number, author_link;
public static BookElement findByName(String s) {

View file

@ -16,6 +16,7 @@ public class Application extends GUI {
public static Application instance;
public boolean quitting = false;
AudibleGUI audibleGUI = new AudibleGUI();
public MainWindow mainWindow;
public Application(Display d) {
super(d);
@ -36,7 +37,8 @@ public class Application extends GUI {
}
public Composite createMainBody(Composite parent) {
return new MainWindow(parent);
mainWindow = new MainWindow(parent);
return mainWindow;
}
void createLayout() {

View file

@ -40,23 +40,21 @@ import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class AudibleGUI implements BookListener, ConnectionListener {
private static final Log LOG = LogFactory.getLog(AudibleGUI.class);
public static AudibleGUI instance;
final Audible audible = new Audible();
final String appPrefsFileName = "settings.json";
public Prefs prefs = new Prefs();
boolean hasFFMPEG = false;
BookNotifier bookNotifier = BookNotifier.getInstance();
boolean loggedIn = false;
int downloadCount, convertCount;
String textFilter = "";
AudibleBrowser browser = null;
public Prefs prefs = new Prefs();
final String appPrefsFileName = "settings.json";
private long totalDuration;
AudibleAccountPrefs userPass = null;
public AudibleGUI() {
assert (instance == null);
@ -115,8 +113,6 @@ public class AudibleGUI implements BookListener, ConnectionListener {
}
AudibleAccountPrefs userPass = null;
@Override
public AudibleAccountPrefs getAccountPrefs(AudibleAccountPrefs in) {
if (in.audiblePassword.isEmpty() || in.audibleUser.isEmpty()) {
@ -144,6 +140,15 @@ public class AudibleGUI implements BookListener, ConnectionListener {
return in;
}
public int selectedAAXCount() {
int count = 0;
for (Book b : getSelected()) {
if (audible.hasAAX(b))
count++;
}
return count;
}
/*
public void fetchDecryptionKeyOld() {
@ -214,16 +219,6 @@ public class AudibleGUI implements BookListener, ConnectionListener {
}
*/
public int selectedAAXCount() {
int count = 0;
for (Book b : getSelected()) {
if (audible.hasAAX(b))
count++;
}
return count;
}
public boolean canDownloadAll() {
return audible.aaxCount() < audible.getBookCount();
}
@ -250,14 +245,6 @@ public class AudibleGUI implements BookListener, ConnectionListener {
downloadAAX(l);
}
public int getDownloadCount() {
return downloadCount;
}
public int getConvertCount() {
return convertCount;
}
public void connect() {
if (!hasLogin()) {
MessageBoxFactory.showGeneral(null, 0, "Missing credentials", "This version requires your audible email and password to be set in preferences.");
@ -309,7 +296,6 @@ public class AudibleGUI implements BookListener, ConnectionListener {
}
}
public boolean hasAAX(Book b) {
return audible.hasAAX(b);
}
@ -368,7 +354,6 @@ public class AudibleGUI implements BookListener, ConnectionListener {
downloadAndConvertWithDialog();
}
// returns null if not logged in.
private AudibleScraper connect(ProgressTask progressTask) throws Exception {
audible.setProgress(progressTask);
@ -412,8 +397,8 @@ public class AudibleGUI implements BookListener, ConnectionListener {
}
private void downloadAndConvertWithDialog() {
ArrayList<Book> dl = audible.toDownload();
ArrayList<Book> conv = audible.toConvert();
Collection<Book> dl = audible.toDownload();
Collection<Book> conv = audible.toConvert();
if (dl.size() == 0 && conv.size() == 0) {
String upToDate = "Your library is up to date! Go buy more Audible books!";
@ -426,8 +411,7 @@ public class AudibleGUI implements BookListener, ConnectionListener {
msg = "You have " + conv.size() + " book(s) to convert to MP3\n";
LOG.info(msg + " autoConvert=" + prefs.autoConvert);
if (prefs.autoConvert || prefs.autoDownload)
{
if (prefs.autoConvert || prefs.autoDownload) {
if (prefs.autoConvert)
convertMP3(conv);
if (prefs.autoDownload)
@ -458,17 +442,15 @@ public class AudibleGUI implements BookListener, ConnectionListener {
public void ignoreSelected() {
List<Book> sel = getSelected();
if (!sel.isEmpty())
{
if (!sel.isEmpty()) {
Book first = sel.get(0);
String title = "Are you sure you want to ignore books";
String bodySingular = "Are you sure you want to ignore the following book? \n\t"+ first.getFullTitle()+"\n\nIt will be added to the ignore list and not shown in OpenAudible anymore.";
String bodyPlurel = "You selected "+sel.size()+" books. Are you sure you want to ignore them? \n\nThey will added to the ignore list and not be shown in OpenAudible anymore.";
String body = sel.size()==1 ? bodySingular:bodyPlurel;
String bodySingular = "Are you sure you want to ignore the following book? \n\t" + first.getFullTitle() + "\n\nIt will be added to the ignore list and not shown in OpenAudible anymore.";
String bodyPlurel = "You selected " + sel.size() + " books. Are you sure you want to ignore them? \n\nThey will added to the ignore list and not be shown in OpenAudible anymore.";
String body = sel.size() == 1 ? bodySingular : bodyPlurel;
boolean yn = MessageBoxFactory.showGeneralYesNo(getShell(), title,body);
if (yn)
{
boolean yn = MessageBoxFactory.showGeneralYesNo(getShell(), title, body);
if (yn) {
audible.addToIgnoreSet(sel);
BookNotifier.getInstance().booksUpdated(); // redraw all.
}
@ -561,9 +543,9 @@ public class AudibleGUI implements BookListener, ConnectionListener {
case MP3_Files:
return "" + audible.mp3Count();
case To_Download:
return "" + getDownloadCount();
return "" + audible.getDownloadCount();
case To_Convert:
return "" + getConvertCount();
return "" + audible.getConvertCount();
case Downloading:
int dl = audible.downloadQueue.jobsInProgress();
int dq = audible.downloadQueue.size();
@ -633,7 +615,6 @@ public class AudibleGUI implements BookListener, ConnectionListener {
}
}
public void exportWebPage(boolean showUserInterface) {
try {
File destDir = Directories.getDir(Directories.WEB);
@ -653,15 +634,14 @@ public class AudibleGUI implements BookListener, ConnectionListener {
try {
URI i = index.toURI();
String u = i.toString();
LOG.info("Book html file is: "+index.getAbsolutePath()+" url="+u);
if (showUserInterface )
LOG.info("Book html file is: " + index.getAbsolutePath() + " url=" + u);
if (showUserInterface)
AudibleGUI.instance.browse(u);
} catch (Exception e) {
showError(e, "displaying web page");
}
} else
{
assert(false);
} else {
assert (false);
}
} catch (Exception e) {
@ -732,11 +712,11 @@ public class AudibleGUI implements BookListener, ConnectionListener {
public void bookUpdated(Book book) {
}
@Override
public void booksUpdated() {
// TODO: Ensure this isn't called too frequently.
audible.updateFileCache();
/*
int d = 0;
int c = 0;
long seconds = 0;
@ -755,14 +735,21 @@ public class AudibleGUI implements BookListener, ConnectionListener {
downloadCount = d;
convertCount = c;
totalDuration = seconds;
*/
}
private boolean displayBook(Book b) {
if (audible.isIgnoredBook(b))
return false;
if (textFilter.isEmpty()) return true; // don't skip any books if no filter.
StatusPanel.Status status = isSpecialSearch(textFilter);
if (status!=null)
{
return displayBookByStatus(b, status);
}
String text = textFilter.toLowerCase();
BookElement elems[] = {BookElement.fullTitle, BookElement.author, BookElement.narratedBy, BookElement.shortTitle};
@ -773,13 +760,11 @@ public class AudibleGUI implements BookListener, ConnectionListener {
return false;
}
// if search text is filled, return books that match.
// otherwise, return all books (default)
public List<Book> getDisplayedBooks() {
ArrayList<Book> displayed = new ArrayList<>();
for (Book b : Audible.instance.getBooks())
{
for (Book b : Audible.instance.getBooks()) {
if (displayBook(b))
displayed.add(b);
}
@ -791,7 +776,6 @@ public class AudibleGUI implements BookListener, ConnectionListener {
bookNotifier.booksUpdated();
}
public void parseAAX() {
ProgressTask task = new ProgressTask("Parse AAX File") {
public void run() {
@ -826,17 +810,14 @@ public class AudibleGUI implements BookListener, ConnectionListener {
}
public void browse() {
browse(audible.getAudibleURL() + "/lib");
}
public String browseSettings() {
return audible.getAudibleURL() + "/account/settings";
}
public void browse(final String url) {
@ -882,7 +863,7 @@ public class AudibleGUI implements BookListener, ConnectionListener {
if (browser != null && !browser.isDisposed()) {
String url = userPass.audibleRegion.getBaseURL()+"/signout";
String url = userPass.audibleRegion.getBaseURL() + "/signout";
browser.setUrl(url);
browser.close();
}
@ -933,7 +914,7 @@ public class AudibleGUI implements BookListener, ConnectionListener {
// total book time, in seconds.
public long getTotalDuration() {
return totalDuration;
return audible.totalDuration;
}
public void test1() {
@ -950,97 +931,22 @@ public class AudibleGUI implements BookListener, ConnectionListener {
}
}
class BookQueueListener implements IQueueListener<Book> {
@Override
public void itemEnqueued(final ThreadedQueue<Book> queue, final Book o) {
bookNotifier.booksUpdated();
}
@Override
public void itemDequeued(final ThreadedQueue<Book> queue, final Book o) {
bookNotifier.booksUpdated();
}
@Override
public void jobStarted(final ThreadedQueue<Book> queue, final IQueueJob job, final Book o) {
bookNotifier.bookUpdated(o);
}
@Override
public void jobError(final ThreadedQueue<Book> queue, final IQueueJob job, final Book o, final Throwable th) {
bookNotifier.bookUpdated(o);
}
@Override
public void jobCompleted(final ThreadedQueue<Book> queue, final IQueueJob job, final Book o) {
booksUpdated();
bookNotifier.bookUpdated(o);
checkAutomation();
}
@Override
public void jobProgress(final ThreadedQueue<Book> queue, final IQueueJob job, final Book book, final String task, final String subtask) {
String msg = "";
if (queue == audible.downloadQueue)
msg = "Downloading ";
else
msg = "Converting ";
assert (msg.length() > 0);
if (subtask != null) {
msg += subtask;
}
bookNotifier.bookProgress(book, msg);
}
}
class PageBuilderTask extends ProgressTask {
final WebPage pageBuilder;
final List<Book> books;
PageBuilderTask(File destDir, final List<Book> list, boolean includeMP3) {
super("Creating Your Audiobook Web Page");
pageBuilder = new WebPage(destDir, this, includeMP3);
books = list;
}
@Override
public void run() {
try {
pageBuilder.buildPage(books);
} catch (Exception e) {
LOG.error("error", e);
if (!wasCanceled())
showError(e, "building web page");
}
}
}
// called after every book is downloaded or converted.
public void checkAutomation()
{
ArrayList<Book> dl = audible.toDownload();
ArrayList<Book> conv = audible.toConvert();
public void checkAutomation() {
Collection<Book> dl = audible.toDownload();
Collection<Book> conv = audible.toConvert();
if (prefs.autoConvert)
convertMP3(conv);
if (prefs.autoDownload)
downloadAAX(dl);
if (dl.size()==0 && conv.size()==0 && prefs.autoWebPage)
{
if (dl.size() == 0 && conv.size() == 0 && prefs.autoWebPage) {
exportWebPage(false);
}
}
public void load() throws IOException {
Audible.instance.load();
@ -1053,9 +959,9 @@ public class AudibleGUI implements BookListener, ConnectionListener {
prefs = gson.fromJson(content, Prefs.class);
}
if (prefs.concurrentConversions<1||prefs.concurrentConversions>10)
if (prefs.concurrentConversions < 1 || prefs.concurrentConversions > 10)
prefs.concurrentConversions = 5;
if (prefs.concurrentDownloads<1||prefs.concurrentDownloads>10)
if (prefs.concurrentDownloads < 1 || prefs.concurrentDownloads > 10)
prefs.concurrentDownloads = 3;
audible.convertQueue.setConcurrentJobs(prefs.concurrentConversions);
@ -1103,10 +1009,10 @@ public class AudibleGUI implements BookListener, ConnectionListener {
this.setTask("Updating");
BookNotifier.getInstance().booksUpdated();
audible.updateFileCache();
// audibleGUI.updateFileCache();
BookNotifier.getInstance().booksUpdated();
// audible.updateFileCache();
// // audibleGUI.updateFileCache();
//
// BookNotifier.getInstance().booksUpdated();
backgroundVersionCheck();
new Thread(() -> checkFFMPEG()).start();
@ -1142,7 +1048,6 @@ public class AudibleGUI implements BookListener, ConnectionListener {
}).start();
}
public void importAAXFiles() {
try {
String ext = "*.aax";
@ -1258,8 +1163,138 @@ public class AudibleGUI implements BookListener, ConnectionListener {
browse(url);
}
});
}
final String customSearchPrefix = ":";
public void setStatusFilter(StatusPanel.Status status) {
String searchText = "";
if (status!=null) searchText = customSearchPrefix+status.name();
Application.instance.mainWindow.searchField.setSearchText(searchText);
}
private StatusPanel.Status isSpecialSearch(String f) {
StatusPanel.Status result = null;
if (!f.startsWith(customSearchPrefix)) return null;
try {
result = StatusPanel.Status.valueOf(f.trim().substring(customSearchPrefix.length()));
if (result.canFilterByStatusType())
return result;
} catch (Throwable th) {
return null;
}
return null;
}
// return true if book should be displayed when status is set.
private boolean displayBookByStatus(Book b, StatusPanel.Status status) {
switch (status) {
case AAX_Files:
// display only files that have aax.
return audible.hasAAX(b);
case MP3_Files:
return audible.hasMP3(b);
case To_Download:
return audible.inDownloadSet(b);
case To_Convert:
return audible.inConvertSet(b);
case Downloading:
return audible.downloadQueue.inJob(b);
case Converting:
return audible.convertQueue.inJob(b);
default:
LOG.error("unexpected status:"+status);
break;
}
return false;
}
class BookQueueListener implements IQueueListener<Book> {
@Override
public void itemEnqueued(final ThreadedQueue<Book> queue, final Book o) {
bookNotifier.booksUpdated();
}
@Override
public void itemDequeued(final ThreadedQueue<Book> queue, final Book o) {
bookNotifier.booksUpdated();
}
@Override
public void jobStarted(final ThreadedQueue<Book> queue, final IQueueJob job, final Book o) {
bookNotifier.bookUpdated(o);
}
@Override
public void jobError(final ThreadedQueue<Book> queue, final IQueueJob job, final Book o, final Throwable th) {
bookNotifier.bookUpdated(o);
}
@Override
public void jobCompleted(final ThreadedQueue<Book> queue, final IQueueJob job, final Book o) {
booksUpdated();
bookNotifier.bookUpdated(o);
checkAutomation();
}
@Override
public void jobProgress(final ThreadedQueue<Book> queue, final IQueueJob job, final Book book, final String task, final String subtask) {
String msg = "";
if (queue == audible.downloadQueue)
msg = "Downloading ";
else
msg = "Converting ";
assert (msg.length() > 0);
if (subtask != null) {
msg += subtask;
}
bookNotifier.bookProgress(book, msg);
}
}
class PageBuilderTask extends ProgressTask {
final WebPage pageBuilder;
final List<Book> books;
PageBuilderTask(File destDir, final List<Book> list, boolean includeMP3) {
super("Creating Your Audiobook Web Page");
pageBuilder = new WebPage(destDir, this, includeMP3);
books = list;
}
@Override
public void run() {
try {
pageBuilder.buildPage(books);
} catch (Exception e) {
LOG.error("error", e);
if (!wasCanceled())
showError(e, "building web page");
}
}
}
}

View file

@ -3,6 +3,8 @@ package org.openaudible.desktop.swt.manager.views;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
@ -34,6 +36,7 @@ public class BookInfoPanel extends GridComposite implements BookListener {
BookElement elems[] = {
BookElement.fullTitle,
BookElement.author,
BookElement.author_link,
BookElement.narratedBy,
BookElement.duration,
BookElement.release_date,
@ -106,6 +109,15 @@ public class BookInfoPanel extends GridComposite implements BookListener {
c = parent;
}
MouseAdapter linkClickListener = new MouseAdapter() {
@Override
public void mouseUp(MouseEvent mouseEvent) {
super.mouseUp(mouseEvent);
Label l = (Label) mouseEvent.widget;
linkClicked(l);
}
};
for (BookElement s : elems) {
String labelName = getName(s);
Label l = c.newLabel();
@ -120,8 +132,10 @@ public class BookInfoPanel extends GridComposite implements BookListener {
d.setBackground(bgColor);
d.setData(s);
stats[s.ordinal()] = d;
d.addMouseListener(linkClickListener);
}
if (true) {
// Task Status:
Label l = c.newLabel();
@ -142,6 +156,22 @@ public class BookInfoPanel extends GridComposite implements BookListener {
return c;
}
private void linkClicked(Label s) {
if (curBook!=null) {
BookElement e = (BookElement) s.getData();
switch (e) {
case author:
LOG.info("click author: "+curBook+" link="+curBook.getAuthorLink());
break;
case fullTitle:
case shortTitle:
LOG.info("click title: "+curBook+" link="+curBook.getInfoLink());
break;
}
}
}
private void update(Book b) {
curBook = b;
@ -156,6 +186,22 @@ public class BookInfoPanel extends GridComposite implements BookListener {
value = getValue(e, b);
}
s.setText(value);
if (e==BookElement.author)
{
boolean hasLink = false;
if (b!=null) {
String link = b.getAuthorLink();
if (link.startsWith("http")) {
// s.setFont(FontShop.
hasLink = true;
}
}
}
}
String taskMsg = AudibleGUI.instance.getTaskString(curBook);

View file

@ -12,6 +12,7 @@ public class MainWindow extends GridComposite {
BookInfoPanel info;
StatusPanel status;
boolean useToolbar = false;
public SearchField searchField;
public MainWindow(Composite c) {
super(c, SWT.NONE);
@ -39,12 +40,12 @@ public class MainWindow extends GridComposite {
// Row 1, search bar and button bar.
if (useToolbar) {
row = new GridComposite(c, SWT.BORDER_DOT, useToolbar ? 2 : 1, false, GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_BEGINNING);
SearchField sf = new SearchField(row, 180, GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.VERTICAL_ALIGN_BEGINNING);
searchField = new SearchField(row, 180, GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.VERTICAL_ALIGN_BEGINNING);
BookButtonBar b = new BookButtonBar(row, GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_END | GridData.VERTICAL_ALIGN_BEGINNING);
} else {
row = new GridComposite(c, SWT.BORDER_DOT, 1, false, GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_BEGINNING);
row.noMargins();
SearchField sf = new SearchField(row, 120, GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.VERTICAL_ALIGN_BEGINNING);
searchField = new SearchField(row, 120, GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.VERTICAL_ALIGN_BEGINNING);
}
// Row 2. Status and selected book
@ -66,8 +67,6 @@ public class MainWindow extends GridComposite {
gd.widthHint = 170;
statusGroup.setLayoutData(gd);
status = new StatusPanel(statusGroup);
}
private void createBottom(Composite parent) {

View file

@ -37,4 +37,10 @@ public class SearchField extends GridComposite {
}
});
}
public void setSearchText(String searchText) {
text.setText(searchText);
AudibleGUI.instance.filterDisplayedBooks(text.getText());
}
}

View file

@ -1,6 +1,8 @@
package org.openaudible.desktop.swt.manager.views;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
@ -22,25 +24,48 @@ public class StatusPanel extends GridComposite implements BookListener, Connecti
Label stats[];
// Label connected;
class StatusClick extends MouseAdapter {
final Status status;
StatusClick(Status s) {
status = s;
}
@Override
public void mouseDown(MouseEvent mouseEvent) {
super.mouseDown(mouseEvent);
System.out.println("click:" + status);
if (status.canFilterByStatusType())
AudibleGUI.instance.setStatusFilter(status);
else
AudibleGUI.instance.setStatusFilter(null);
}
}
StatusPanel(Composite c) {
super(c, SWT.NONE);
initLayout(2, false, GridData.GRAB_HORIZONTAL | GridData.FILL_HORIZONTAL);
BookNotifier.getInstance().addListener(this);
ConnectionNotifier.getInstance().addListener(this);
Status elems[] = Status.values();
stats = new Label[elems.length];
for (int x = 0; x < elems.length; x++) {
if (!elems[x].display())
stats = new Label[Status.values().length];
int index=0;
for (Status e:Status.values())
{
if (!e.display())
continue;
String labelName = elems[x].displayName();
String labelName = e.displayName();
Label l = newLabel();
l.setText(Translate.getInstance().labelName(labelName) + ": ");
l.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING));
l.setFont(FontShop.tableFontBold());
l.setBackground(bgColor);
// TODO: Add more stats when user hovers over stats
// l.addListener(SWT.MouseHover);
Label d = newLabel();
GridData gd = new GridData(GridData.GRAB_HORIZONTAL | GridData.FILL_HORIZONTAL);
@ -49,8 +74,15 @@ public class StatusPanel extends GridComposite implements BookListener, Connecti
d.setFont(FontShop.tableFont());
d.setBackground(bgColor);
d.setData(elems[x]);
stats[x] = d;
d.setData(e);
// allow user to click on status and filter
StatusClick clickHandler = new StatusClick(e);
l.addMouseListener(clickHandler);
d.addMouseListener(clickHandler);
stats[index++] = d;
}
_update();
@ -121,7 +153,6 @@ public class StatusPanel extends GridComposite implements BookListener, Connecti
public boolean display() {
switch (this) {
case Books:
case To_Convert:
case Downloading:
case Converting:
@ -132,6 +163,7 @@ public class StatusPanel extends GridComposite implements BookListener, Connecti
case AAX_Files:
case To_Download:
case Books:
return false;
default:
@ -139,9 +171,34 @@ public class StatusPanel extends GridComposite implements BookListener, Connecti
}
return true;
}
public boolean canFilterByStatusType()
{
return canFilterByStatusType(this);
}
public static boolean canFilterByStatusType(Status result)
{
switch (result) {
case Connected:
case Hours:
return false;
case Books:
case AAX_Files:
case MP3_Files:
case To_Download:
case To_Convert:
case Downloading:
case Converting:
return true;
default: assert(false);
}
return false;
}
}
@Override

View file

@ -12,6 +12,8 @@ import org.openaudible.desktop.swt.i8n.Translate;
import org.openaudible.desktop.swt.manager.Application;
import org.openaudible.util.Platform;
import java.util.HashMap;
// ugly singleton swt font manager
public class FontShop {
public final static Log logger = LogFactory.getLog(FontShop.class);
@ -26,7 +28,9 @@ public class FontShop {
final Font regFonts[];
final Font boldFonts[];
final Font italicFonts[];
HashMap<String, Font> fontCache = new HashMap<>();
;
public FontShop(Display display) {
if (display == null) {
@ -46,6 +50,8 @@ public class FontShop {
}
;
/**
* Check the given Font for being NULL or disposed. Return false in that case.
*
@ -119,6 +125,30 @@ public class FontShop {
return curFonts.boldFonts[TREE_FONT];
}
public String getFontKey(String fontName, FontType fontType, FontStyle fontStyle, int sizeAdjust) {
return fontName + "_" + fontType.name() + "_" + fontStyle.name() + "_" + sizeAdjust;
}
public Font getFont(String fontName, FontType fontType, FontStyle fontStyle, int sizeAdjust) {
String key = getFontKey(fontName, fontType, fontStyle, sizeAdjust);
Font f = fontCache.get(key);
if (f == null) {
// create Font
f = createFont(fontName, fontType, fontStyle, sizeAdjust);
assert (f != null);
fontCache.put(key, f);
}
return f;
}
private Font createFont(String fontName, FontType fontType, FontStyle fontStyle, int sizeAdjust) {
return null; // TODO
}
public Font getFont(FontType fontType, FontStyle fontStyle) {
return null; // return getFont(defaultFontName(fontType), fontType,)
}
private Font newDefaultFont(int id) {
Display display = Display.getCurrent();
@ -186,7 +216,6 @@ public class FontShop {
return "Fonts";
}
public void checkFont() {
for (int i = 0; i < regFonts.length; i++) {
if (!isset(regFonts[i])) {
@ -203,5 +232,10 @@ public class FontShop {
}
}
public enum FontType {DIALOG, HEADER, TABLE, TEXT, TREE, LINK}
public enum FontStyle {REGULAR, BOLD, UNDERLINE, ITALIC}
}

View file

@ -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, narrated_by, mp3, image, description, summary, duration, rating_average, rating_count, link_url, purchase_date, release_date;
String title, author, narrated_by, mp3, image, description, summary, duration, rating_average, rating_count, link_url, purchase_date, release_date, author_url;
}

View file

@ -48,6 +48,7 @@ public class WebPage {
i.rating_count = b.get(BookElement.rating_count);
i.link_url = b.getInfoLink();
i.author_url = b.getAuthorLink();
i.description = b.get(BookElement.description);
i.purchase_date = b.getPurchaseDateSortable();

View file

@ -18,9 +18,18 @@ function asString(str, len) {
}
function mp3Link(book, content) {
if (!book || !book.mp3 || !content)
return "";
return "<a href='mp3/" + encodeURIComponent(book.mp3) + "'>" + content + "</a> ";
if (!book || !book.mp3 || !content)
return "";
return "<a href='mp3/" + encodeURIComponent(book.mp3) + "'>" + content + "</a> ";
}
function authorLink(book) {
if (!book || !book.author)
return "";
if (book.author_link && book.author_url.startsWith("http"))
{
return "<a href='"+book.author_url + "'>" + asString(book.author) + "</a> ";
}
return asString(book.author);
}
function bookImage(book, addLink) {
@ -48,7 +57,7 @@ function populateBooks(arr, table) {
row['book'] = book;
row['title'] = title;
row['narrated_by'] = narrated_by;
row['author'] = author;
row['author'] = authorLink(book); // author;
row['duration'] = duration;
row['purchase_date'] = asString(book.purchase_date);