clicks and fixes
This commit is contained in:
parent
2f89d5d6c6
commit
caeb70deea
16 changed files with 537 additions and 282 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -37,4 +37,10 @@ public class SearchField extends GridComposite {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void setSearchText(String searchText) {
|
||||
text.setText(searchText);
|
||||
AudibleGUI.instance.filterDisplayedBooks(text.getText());
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue