Updates for change in audible web site. Some other cleanup and reformatting.

This commit is contained in:
openaudible 2018-05-28 17:26:05 -07:00
parent daa76551e5
commit 8bcd07bb0b
57 changed files with 1611 additions and 1165 deletions

View file

@ -170,6 +170,11 @@
<systemPath>${basedir}/swt/${swt_library}</systemPath>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20160212</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
@ -216,7 +221,5 @@
<artifactId>opencsv</artifactId>
<version>3.7</version>
</dependency>
</dependencies>
</project>

View file

@ -24,12 +24,10 @@ import org.openaudible.convert.LookupKey;
import org.openaudible.download.DownloadQueue;
import org.openaudible.progress.IProgressTask;
import org.openaudible.util.CopyWithProgress;
import org.openaudible.util.EventTimer;
import org.openaudible.util.HTMLUtil;
import org.openaudible.util.queues.IQueueJob;
import org.openaudible.util.queues.IQueueListener;
import org.openaudible.util.queues.ThreadedQueue;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.FileWriter;
@ -163,14 +161,7 @@ public class Audible implements IQueueListener<Book> {
}
public String checkBook(Book b) {
// BookElement required[] = { BookElement.product_id, BookElement.user_id, BookElement.cust_id };
BookElement required[] = {BookElement.product_id, BookElement.fullTitle};
for (BookElement e : required) {
if (!b.has(e))
return "required:" + e + " missing from " + b;
}
return "";
return b.checkBook();
}
public void load() throws IOException {
@ -181,8 +172,8 @@ public class Audible implements IQueueListener<Book> {
if (prefsFile.exists()) {
String content = HTMLUtil.readFile(prefsFile);
account = gson.fromJson(content, AudibleAccountPrefs.class);
if (account.audibleRegion==null)
account.audibleRegion=AudibleRegion.US;
if (account.audibleRegion == null)
account.audibleRegion = AudibleRegion.US;
}
} catch (Throwable th) {
@ -214,7 +205,6 @@ public class Audible implements IQueueListener<Book> {
LookupKey.instance.load(Directories.BASE.getDir(keysFileName));
}
public synchronized void save() throws IOException {
@ -252,13 +242,13 @@ public class Audible implements IQueueListener<Book> {
public HashSet<File> getFileSet(Directories dir) {
HashSet<File> set = new HashSet<>();
File d = dir.getDir();
for (File f:d.listFiles()) {
for (File f : d.listFiles()) {
String name = f.getName();
if (name.startsWith("."))
continue;
if (dir==Directories.MP3 && !name.toLowerCase().endsWith(".mp3"))
if (dir == Directories.MP3 && !name.toLowerCase().endsWith(".mp3"))
continue;
if (dir==Directories.AAX && !name.toLowerCase().endsWith(".aax"))
if (dir == Directories.AAX && !name.toLowerCase().endsWith(".aax"))
continue;
set.add(f);
}
@ -323,12 +313,9 @@ public class Audible implements IQueueListener<Book> {
}
public void updateFileCache() {
EventTimer evt = new EventTimer();
mp3Files = getFileSet(Directories.MP3);
aaxFiles = getFileSet(Directories.AAX);
needFileCacheUpdate = System.currentTimeMillis();
// LOG.info(evt.reportString("updateFileCache mp3:" + mp3Files.size() + " aax:" + aaxFiles.size()));
}
public int mp3Count() {
@ -373,8 +360,6 @@ public class Audible implements IQueueListener<Book> {
LOG.info("book purchase date:" + updatedBook.getPurchaseDate());
}
return BookMerge.instance.merge(book, updatedBook);
}
return new HashSet<>();
@ -484,7 +469,7 @@ public class Audible implements IQueueListener<Book> {
writer.writeNext(line);
for (Book b:bookList) {
for (Book b : bookList) {
for (BookElement e : items) {
String v = b.get(e);
line[e.ordinal()] = v;
@ -496,17 +481,15 @@ public class Audible implements IQueueListener<Book> {
}
public void export(File f) throws IOException {
try (FileWriter out = new FileWriter(f))
{
try (FileWriter out = new FileWriter(f)) {
export(out, getBooks());
}
}
public void exportJSON(List<Book>list, File f) throws IOException {
public void exportJSON(List<Book> list, File f) throws IOException {
Gson gson = new Gson();
String json = gson.toJson(list);
try (FileWriter writer = new FileWriter(f))
{
try (FileWriter writer = new FileWriter(f)) {
writer.write(json);
}
}
@ -549,6 +532,7 @@ public class Audible implements IQueueListener<Book> {
}
}
// refreshes book with latest from audible.com
public void updateInfo() throws Exception {
AudibleScraper s = getScraper();
ArrayList<Book> list = new ArrayList<>();
@ -639,9 +623,6 @@ public class Audible implements IQueueListener<Book> {
}
public void redeemGiftCode(String s) throws IOException, SAXException {
audibleScraper.redeemGiftCode(s);
}
String inspectCookies(Collection<Cookie> col) {
String out = "";
@ -709,7 +690,7 @@ public class Audible implements IQueueListener<Book> {
@Override
public void jobStarted(ThreadedQueue<Book> queue, IQueueJob job, Book b) {
LOG.info(queue.toString()+" started:"+b+" size:"+queue.size());
LOG.info(queue.toString() + " started:" + b + " size:" + queue.size());
BookNotifier.getInstance().bookUpdated(b);
}
@ -720,8 +701,8 @@ public class Audible implements IQueueListener<Book> {
@Override
public void jobCompleted(ThreadedQueue<Book> queue, IQueueJob job, Book b) {
LOG.info(queue.toString()+" completed:"+b+" size:"+queue.size());
if (queue==downloadQueue) {
LOG.info(queue.toString() + " completed:" + b + " size:" + queue.size());
if (queue == downloadQueue) {
try {
AAXParser.instance.update(b);
} catch (Exception e) {
@ -753,34 +734,33 @@ public class Audible implements IQueueListener<Book> {
public void logout() {
if (audibleScraper!=null)
if (audibleScraper != null)
audibleScraper.logout();
}
public String getAudibleURL()
{
public String getAudibleURL() {
return getAccount().audibleRegion.getBaseURL();
}
// used for drag and drop into the app to convert any aax file.
public Book importAAX(final File aaxFile, final IProgressTask task) throws InterruptedException, IOException, CannotReadException, ReadOnlyFileException, InvalidAudioFrameException, TagException {
task.setTask("Parsing "+aaxFile.getName());
task.setTask("Parsing " + aaxFile.getName());
final Book book = AAXParser.instance.parseAAX(aaxFile, null, AAXParser.CoverImageAction.useBiggerImage);
if (!hasBook(book))
{
task.setTask("Importing "+book);
if (!hasBook(book)) {
task.setTask("Importing " + book);
// Copy it to the default directory.
File dest = getAAXFileDest(book);
if (dest.exists())
throw new IOException("Book already exists:"+dest.getAbsolutePath());
if (task!=null)
throw new IOException("Book already exists:" + dest.getAbsolutePath());
if (task != null)
CopyWithProgress.copyWithProgress(task, aaxFile, dest);
else
IO.copy(aaxFile, dest);
IO.copy(aaxFile, dest);
updateFileCache();
boolean test = hasAAX(book);
assert(test);
assert (test);
boolean ok = takeBook(book);
if (ok)

View file

@ -4,7 +4,7 @@ package org.openaudible;
public class AudibleAccountPrefs {
public String audibleUser = "";
public String audiblePassword = "";
public AudibleRegion audibleRegion=AudibleRegion.US;
public AudibleRegion audibleRegion = AudibleRegion.US;
}

View file

@ -17,7 +17,7 @@ import java.util.logging.Level;
public class AudibleCLI {
private static final Log LOG = LogFactory.getLog(AudibleCLI.class);
final Audible audible= new Audible();
final Audible audible = new Audible();
volatile boolean quit = false;
public AudibleCLI() {
@ -131,12 +131,6 @@ public class AudibleCLI {
}
break;
case redeem:
if (args.length() == 0) throw new Exception("Enter one or more gift codes.");
for (int x = 1; x < r.length; x++) {
audible.redeemGiftCode(r[x].trim());
}
break;
case updateInfo:
for (Book b : audible.find(args))
@ -269,7 +263,7 @@ public class AudibleCLI {
}
enum AudibleCmd {
help, update, info, convert, export, save, load, find, has, asText, asXML, home, quit, library, names, signout, title, list, redeem, forms, web, access, toDownload, setURL, gettest2, test1, test2, act, retag, parseAAX, toConvert, download, updateInfo, cookies, queues
help, update, info, convert, export, save, load, find, has, asText, asXML, home, quit, library, names, signout, title, list, forms, web, access, toDownload, setURL, gettest2, test1, test2, act, retag, parseAAX, toConvert, download, updateInfo, cookies, queues
}
}

View file

@ -8,7 +8,7 @@ public enum AudibleRegion {
// audible.de, audible.fr, audible.com.au, audible.it, audible.jp, audible.ca
public String getBaseURL() {
return "https://"+this.getBaseDomain();
return "https://" + this.getBaseDomain();
}
public String getBaseDomain() {
@ -47,6 +47,6 @@ public enum AudibleRegion {
}
public String displayName() {
return getBaseDomain()+" ("+this.name().toUpperCase()+")";
return getBaseDomain() + " (" + this.name().toUpperCase() + ")";
}
}

View file

@ -9,6 +9,7 @@ import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
/**
* Created 6/26/2017.
*/
@ -64,9 +65,7 @@ public enum Directories {
if (dir.isDirectory()) continue;
boolean ok = dir.mkdir();
if (!ok) throw new IOException("Unable to create directory: " + dir.getAbsolutePath());
}
}
public static void save() throws IOException {
@ -78,19 +77,15 @@ public enum Directories {
public static void load() throws IOException {
File f = META.getDir(dirPrefsName);
if (f.length() > 0) {
JsonParser parser = new JsonParser();
String json = FileUtils.readFileToString(f, "utf8");
JsonElement jsonElement = parser.parse(json); // new FileReader(f));
JsonObject j = jsonElement.getAsJsonObject();
fromJSON(j);
}
}
public static File getAppFile(String path)
{
public static File getAppFile(String path) {
File dir = new File(getDir(Directories.APP), path);
if (!dir.exists()) {
File dir2 = new File(getDir(Directories.APP), "src" + File.separator + "main" + File.separator + path);
@ -121,18 +116,33 @@ public enum Directories {
public static void fromJSON(JsonObject j) {
for (Directories d : Directories.values()) {
JsonElement e = j.get(d.name());
if (e != null)
setPath(d, e.getAsString());
if (e != null) {
String path = e.getAsString();
if (isValid(path))
setPath(d, e.getAsString());
}
}
}
public static boolean setPath(Directories d, String path) {
public static boolean isValid(String path) {
assert (paths != null);
if (path != null && path.length() > 0) {
File f = new File(path);
assert (f.exists());
return f.isDirectory() && f.exists();
}
return false;
}
File f = new File(path);
assert (f.exists());
paths[d.ordinal()] = path;
return f.exists();
public static boolean setPath(Directories d, String path) {
assert (isValid(path));
boolean valid = isValid(path);
assert (valid);
if (valid) {
paths[d.ordinal()] = path;
}
return valid;
}
public static String getPath(Directories d) {

View file

@ -1,19 +1,17 @@
package org.openaudible.audible;
import com.gargoylesoftware.htmlunit.*;
import com.gargoylesoftware.htmlunit.CookieManager;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.html.*;
import com.gargoylesoftware.htmlunit.util.Cookie;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.eclipse.jetty.util.IO;
import org.openaudible.Audible;
import org.openaudible.AudibleAccountPrefs;
import org.openaudible.Directories;
import org.openaudible.books.Book;
@ -23,15 +21,11 @@ import org.openaudible.util.EventTimer;
import org.openaudible.util.HTMLUtil;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
// audible.com web page scraper
@ -43,14 +37,13 @@ public class AudibleScraper {
final AudibleAccountPrefs account;
final static String cookiesFileName = "cookies.json";
boolean debugCust = false;
boolean debugCust = true;
boolean loggedIn = false;
String clickToDownload = "Click to download ";
private IProgressTask progress;
public AudibleScraper(AudibleAccountPrefs account) {
webClient = new AudibleClient();
this.account = account;
@ -62,102 +55,37 @@ public class AudibleScraper {
}
private static String extract_activation_bytes(byte b[]) throws Exception {
LOG.info("extract_activation_bytes");
String data = new String(b, "UTF-8");
LOG.info(data);
if (data.contains("BAD_LOGIN"))
throw new Exception("BAD_LOGIN");
if (data.contains("Whoops"))
throw new Exception("Whoops");
if (!data.contains("group_id"))
throw new Exception("no group_id");
int index = findKeyStart(b);
if (index == -1)
throw new Exception("No key found");
String code = "";
for (int x = 0; x < 4; x++) {
byte i = b[index + 3 - x];
String o = Integer.toHexString((i & 0xFF)).toUpperCase();
if (o.length() == 1) // Make byte two chars long.
o = "0" + o;
code += o;
}
LOG.info("code=" + code);
return code;
}
// Find first "line start" that doesn't end with )\n
private static int findKeyStart(byte[] b) {
int index = -1;
for (int x = 1; x < b.length - 2; x++) {
if (b[x] == '\n') {
if (b[x - 1] != ')')
return index;
index = x + 1;
}
}
return -1;
}
public static String extract(String c, DomNode h) {
return HTMLUtil.text(HTMLUtil.findByClass(c, h));
}
public static String extractParagraph(String c, DomNode h) {
String out = "";
DomNode node = (DomNode) HTMLUtil.findByClass(c, h);
if (node != null) {
NodeList cn = node.getChildNodes();
for (int x = 0; x < cn.getLength(); x++) {
Node y = cn.item(x);
String text = y.getTextContent();
if (text != null) {
text = text.trim();
if (out.length() > 0)
out += "\n";
out += text;
}
}
}
return out;
}
public HtmlPage getPage() {
return page;
}
public void setPage(HtmlPage page) {
assert(page!=null);
assert (page != null);
this.page = page;
LOG.info("pageLoaded:"+page.getUrl()+" "+page.getTitleText());
LOG.info("pageLoaded:" + page.getUrl() + " " + page.getTitleText());
// progress.setSubTask("page.getTitleText());
if (page != null && debugCust) {
String xml = page.asXml();
int i1 = xml.indexOf("cust_id");
int i2 = xml.indexOf("downloadCustId");
int i2 = xml.indexOf("order_number");
String s1 = "", s2 = "";
if (i1 != -1) {
s1 = xml.substring(i1 - 100, i1 + 100);
s1 = xml.substring(i1 - 300, i1 + 300);
}
if (i2 != -1) {
s2 = xml.substring(i2 - 100, i2 + 100);
s2 = xml.substring(i2 - 300, i2 + 300);
}
if (s1.length() > 0 || s2.length() > 0) {
LOG.info("Found cust_id:s1=" + s1 + "\ns2=" + s2);
}
}
// progress.setSubTask("page.getTitleText());
}
public boolean isLoggedIn() {
@ -167,11 +95,11 @@ public class AudibleScraper {
public void setLoggedIn(boolean loggedIn) {
if (loggedIn != this.loggedIn) {
this.loggedIn = loggedIn;
String title="null";
if (page!=null)
String title = "null";
if (page != null)
title = page.getTitleText();
LOG.info("Setting logged in to " + loggedIn+" page="+title);
LOG.info("Setting logged in to " + loggedIn + " page=" + title);
ConnectionNotifier.getInstance().connectionChanged(loggedIn);
if (loggedIn) {
@ -180,6 +108,12 @@ public class AudibleScraper {
} catch (IOException e) {
e.printStackTrace();
}
} else {
if (page != null) {
String u = page.getUrl().toString();
ConnectionNotifier.getInstance().setLastURL(u);
}
}
}
}
@ -235,142 +169,13 @@ public class AudibleScraper {
}
ArrayList<Book> parseLibraryFragment(DomDocumentFragment fragment) {
ArrayList<Book> list = new ArrayList<>();
ArrayList<String> colNames = new ArrayList<>();
HtmlTable table = fragment.getFirstByXPath("//table");
if (table == null)
return list;
int purchaseDateIndex = -1;
List<HtmlElement> header = table.getElementsByTagName("th");
int index = 0;
for (HtmlElement h : header) {
String xml = h.asXml();
if (xml.contains("PURCHASE_DATE")) {
// <th><a href="#" class="adbl-sort-by adbl-link adbl-sort-up" id="SortByDtPurchased" name="" title="">Purchase Date</a></th>
purchaseDateIndex = index;
}
index++;
}
if (purchaseDateIndex == -1) {
LOG.debug("No purchase date column!?");
}
for (HtmlTableRow r : table.getRows()) {
for (HtmlTableCell cell : r.getCells()) {
String cx = cell.asXml();
if (cx.contains("adbl-download-it")) {
Book b = new Book();
for (DomElement e : cell.getHtmlElementDescendants()) {
String clz = e.getAttribute("class");
String n = e.getAttribute("name");
String v = e.getAttribute("value");
if ("adbl-download-it".equals(clz)) {
String fullTitle = e.getAttribute("title");
if (fullTitle.startsWith(clickToDownload)) {
fullTitle = fullTitle.substring(clickToDownload.length(), fullTitle.length());
}
b.setFullTitle(fullTitle);
}
if ("productId".equals(n))
b.setProduct_id(v);
}
if (b.getProduct_id() != null && !b.partial()) {
for (HtmlTableCell c : r.getCells()) {
if ("titleInfo".equals(c.getAttribute("name"))) {
for (DomNode d : c.getChildNodes()) {
if (d instanceof HtmlAnchor) {
HtmlAnchor link = (HtmlAnchor) d;
if ("tdTitle".equals(link.getAttribute("name"))) {
b.setInfoLink(link.getAttribute("href"));
}
}
}
}
}
String downloadCustId = getHidden("downloadCustId");
if (downloadCustId != null)
b.set(BookElement.cust_id, downloadCustId);
else
LOG.info("Warning: No cust_id found for book: " + b);
String downloadUserId = getHidden("cust_id");
if (downloadUserId != null)
b.set(BookElement.user_id, downloadUserId);
else
LOG.info("Warning: No user_id found for book: " + b);
List<HtmlElement> cells = r.getElementsByTagName("td");
if (purchaseDateIndex != -1 && purchaseDateIndex < cells.size()) {
HtmlElement pd = cells.get(purchaseDateIndex);
String purchaseDateText = pd.asText().trim();
if (purchaseDateText.length() == 0)
LOG.info("Purchase date blank! " + pd.asXml());
b.setPurchaseDate(purchaseDateText);
LOG.info(b + "->" + purchaseDateText + " " + b.getPurchaseDate());
} else {
if (purchaseDateIndex != -1)
LOG.info("No purchase date for " + b);
}
String err = "";
if (!b.has(BookElement.user_id))
err += "no user_id ";
if (!b.has(BookElement.cust_id))
err += "no cust_id ";
if (err.length() > 0)
LOG.info("Error: " + b + " " + err);
list.add(b);
if (getProgress() != null)
getProgress().setSubTask(b.toString());
}
}
}
}
LOG.info("Library page contains: " + list.size() + " book(s)");
return list;
}
private boolean login() throws IOException {
protected boolean login() throws IOException {
AudibleAccountPrefs copy = account;
if (account.audibleUser.length() == 0 || account.audiblePassword.length() == 0)
{
if (account.audibleUser.length() == 0 || account.audiblePassword.length() == 0) {
copy = ConnectionNotifier.getInstance().getAccountPrefs(account);
if (copy==null) return false; // exit
if (copy == null) return false; // exit
}
@ -380,7 +185,6 @@ public class AudibleScraper {
throw new IOException("audiblePass not set");
if (getProgress() != null)
getProgress().setTask("Logging on to audible...");
@ -455,14 +259,12 @@ public class AudibleScraper {
}
HtmlAnchor signOut = getAnchor("/signout");
HtmlAnchor accountDetails = getAnchor("/account-details");
if (accountDetails != null || signOut != null) {
assert(signIn==null);
assert (signIn == null);
setLoggedIn(true);
return true;
@ -476,18 +278,14 @@ public class AudibleScraper {
return isLoggedIn();
}
public String homeURL()
{
public String homeURL() {
return "/access";
}
public String getPageURL()
{
if (page!=null)
{
public String getPageURL() {
if (page != null) {
URL u = page.getUrl();
if (u!=null)
{
if (u != null) {
return u.toString();
}
}
@ -552,16 +350,21 @@ public class AudibleScraper {
return null;
}
public void clickLib() throws Exception {
/*
public boolean clickLib() throws Exception {
HtmlAnchor lib=null;
for (HtmlAnchor n : page.getAnchors()) {
if (n.getHrefAttribute().contains("/lib"))
lib = n;
}
if (lib!=null) {
setPage(lib.click());
return true;
} else {
lib();
return false;
}
@ -570,28 +373,20 @@ public class AudibleScraper {
}
*/
public void lib() throws Exception {
if (!checkLoggedIn())
throw new Exception("Got logged out. Try logging in with Browser and try again..");
if (getProgress() != null)
getProgress().setTask("Loading Library");
clickLib();
setURL("/lib");
if (!checkLoggedIn()) {
// getWebClient().waitForBackgroundJavaScript(10000); // needed? prob. not.
HTMLUtil.debugNode(page, "got logged out 1");
// trouble.. try again
login();
if (!checkLoggedIn()) {
HTMLUtil.debugNode(page, "got logged out 2");
throw new Exception("lib got logged out");
}
if (!checkLoggedIn())
throw new Exception("Got logged out. Try logging in with Browser and try again..");
setURL("/lib");
}
String downloadCustId = getHidden("downloadCustId");
String downloadUserId = getHidden("cust_id");
if (downloadCustId == null || downloadUserId == null) {
HTMLUtil.debugNode(page, "lib-no-cust");
throw new IOException("Unable to determine required library variables in library:");
}
}
private HtmlElement findById(String id) {
@ -605,24 +400,20 @@ public class AudibleScraper {
}
public String getAudibleBase()
{
public String getAudibleBase() {
return account.audibleRegion.getBaseURL();
}
public Page setURL(String u) throws FailingHttpStatusCodeException, IOException {
LOG.info("setURL:"+u);
if (getProgress() != null)
getProgress().setSubTask(u);
return setURL(u, u);
}
public Page setURL(String u, String task) throws FailingHttpStatusCodeException, IOException {
LOG.info("setURL:" + u);
getProgress().setSubTask(task);
EventTimer evt = new EventTimer();
if (u.startsWith("/"))
u = getAudibleBase()+ u;
if (getProgress() != null)
getProgress().setSubTask(u);
u = getAudibleBase() + u;
Page p = getWebClient().getPage(u);
@ -634,47 +425,39 @@ public class AudibleScraper {
return p;
}
String replaceAll(String haystack, String find, String replacement) {
while (haystack.contains(find))
haystack = haystack.replaceAll(find, replacement);
return haystack;
}
public String cleanString(String out) {
out = replaceAll(out, "\r", "\n");
out = replaceAll(out, " ", " ");
out = replaceAll(out, "\t\t", "\t");
out = replaceAll(out, " \n", "\n");
out = replaceAll(out, "\t\n", "\n");
out = replaceAll(out, "\n\n", "\n");
return out;
}
String getHidden(String n) {
return HTMLUtil.findHidden(page, n);
}
String escape(String s) throws Exception {
char bad[] = {'\n', '/', '#'};
for (char c : bad) {
if (s.indexOf(c) != -1)
throw new Exception("TODO: Fix");
}
return s;
}
void setTask(String task, String subTask) {
}
private ArrayList<Book> libraryPage(int pageNum, int items) throws IOException, SAXException, InterruptedException {
if (getProgress() != null) {
if (getProgress().wasCanceled()) throw new InterruptedException("Canceled");
getProgress().setTask("Retrieving page " + pageNum + " of audible library.", "Fetching...");
public void getCustAndOrder(Book b) throws Exception {
if (!checkLoggedIn())
throw new Exception("Not logged in.");
String link = b.getInfoLink();
if (link.startsWith("/")) {
link = Audible.instance.getAudibleURL() + link;
if (link.startsWith("http")) {
HtmlPage page = (HtmlPage) setURL(link);
String xml = HTMLUtil.debugNode(page, "book_details");
int ch = xml.indexOf("cds.audible.com");
if (ch != -1) {
String dl = xml.substring(ch - 50, ch + 300);
System.out.println(dl);
}
}
}
return parseLibraryFragment(getLibraryFragment(pageNum, items));
}
/*
private DomDocumentFragment getLibraryFragment(int pageNum, int items) throws IOException, SAXException {
EventTimer evt = new EventTimer();
String url = getAudibleBase()+"/lib-ajax";
@ -688,24 +471,6 @@ public class AudibleScraper {
return frag;
}
public DomDocumentFragment redeemGiftCode(String code) throws IOException, SAXException {
if (!loggedIn)
throw new IOException("Not logged in");
setURL("/mt/giftmembership");
String url = getAudibleBase()+"/gift-redemption?isGM=gm";
WebRequest webRequest = new WebRequest(new URL(url), HttpMethod.POST);
webRequest.setRequestBody("giftClaimCode=" + code);
WebResponse webResponse = getWebClient().getWebConnection().getResponse(webRequest);
String content = webResponse.getContentAsString();
LOG.info(content);
DomDocumentFragment frag = new DomDocumentFragment(page);
HTMLParser.parseFragment(frag, content);
return frag;
}
public Collection<Book> fetchFirstLibraryPage() throws Exception {
if (page == null)
@ -715,6 +480,7 @@ public class AudibleScraper {
lib();
return results;
}
*/
public Collection<Book> fetchLibraryQuick(HashMap<String, Book> books) throws Exception {
return _fetchLibrary(books);
@ -725,17 +491,64 @@ public class AudibleScraper {
home();
LOG.info("Accessing audible library...");
HashSet<Book> results = new HashSet<>();
// getWebClient().setJavascriptEnabled(false);
progress.setTask("Scanning your library to get your list of books...", "");
int pageNum = 0;
HtmlElement next = null;
String prev = "";
getWebClient().setJavascriptEnabled(false);
while (true) {
progress.throwCanceled();
lib();
if (!checkLoggedIn()) {
login();
if (!checkLoggedIn()) {
throw new Exception("Unable to remain logged in");
}
}
pageNum++;
if (next == null) {
assert (pageNum == 1);
setURL("/lib", "Reading Library...");
} else {
// getProgress().setTask("Getting a list of your library. );
EventTimer evt = new EventTimer();
String u = next.getAttribute("data-url");
if (u != null) {
u = URLDecoder.decode(u);
if (!u.endsWith("&")) u += "&";
u += "page=" + pageNum;
setURL(u, "Reading Library page " + pageNum + "... Found " + results.size() + " books");
} else {
page = next.click(); // go to next page.
LOG.info(next.getClass() + " " + evt.reportString("next-click") + next.asXml());
}
}
String cur = page.getUrl().toString();
LOG.info("curr=" + cur + "\nprev=" + prev);
assert (!prev.equals(cur));
prev = cur;
progress.throwCanceled();
ArrayList<Book> list = LibraryParser.instance.parseLibraryFragment(page);
for (int x = 1; x < 2000; x++) {
LOG.info("Getting page " + x + " of audible library...");
ArrayList<Book> list = libraryPage(x, 100);
int newBooks = 0;
for (Book b : list) {
LOG.info(b.toString());
if (b.partial())
continue;
if (results.contains(b)) {
LOG.error("duplicate book:" + b);
assert (false);
}
results.add(b);
if (existingBooks != null) {
if (!existingBooks.containsKey(b.getProduct_id()))
@ -744,9 +557,14 @@ public class AudibleScraper {
newBooks++;
}
}
if (newBooks == 0)
break;
next = LibraryParser.instance.getNextPage(page);
if (next == null)
break;
}
return results;
}
@ -758,214 +576,14 @@ public class AudibleScraper {
if (getWebClient() != null) {
getWebClient().close();
}
}
public String getAudibleBookURL(Book b) {
String url = getAudibleBase() + b.getInfoLink();
return url;
}
/*
public String getActivationBytes() {
return activationBytes;
}
public void setActivationBytes(String activationBytes) {
this.activationBytes = activationBytes;
}
*/
public String getURLField(String u, String which) throws URISyntaxException {
for (NameValuePair nvp : URLEncodedUtils.parse(new URI(u), "UTF-8")) {
LOG.info(nvp.getName() + "=" + nvp.getValue());
if (nvp.getName().equals(which)) {
return nvp.getValue();
}
}
return null;
}
//// BOOK INFO PAGE CODE
// Based on https://github.com/inAudible-NG/audible-activator
// Gets activation bytes that are needed to decrypt audible content
// A player is registered.. the key extracted.. then unregistered.
// If this fails, the most common cause is that you need to request
// Audible reset your Audible desktop player devices. This can't
// currently be done using the audible deactivate devices web page
// as those only appear to show devices such as iOS and Kindle.
// But a quick online chat should suffice.
//
public String fetchDecrpytionKey() throws Exception {
if (!loggedIn)
throw new Exception("Not logged in");
if (getProgress() != null)
getProgress().setTask("Fetching player");
String playerId = "";
String r = RandomStringUtils.randomAlphabetic(20);
LOG.info("randomString=" + r + " len=" + r.length());
String b64 = Base64.encodeBase64String(r.getBytes()).trim();
LOG.info(b64);
playerId = b64;
// Object page=null;
String oldAgent = getWebClient().getBrowserVersion().getUserAgent();
try {
getWebClient().getBrowserVersion().setUserAgent("Audible Download Manager");
if (getProgress() != null)
getProgress().setSubTask("Authorizing");
String u = getAudibleBase()+"/player-auth-token?playerType=software&bp_ua=y&playerModel=Desktop&playerId=" + playerId + "&playerManufacturer=Audible&serial=";
LOG.info(u);
Page p = getWebClient().getPage(u);
page = null;
if (p instanceof HtmlPage) {
page = (HtmlPage) p;
HTMLUtil.debugNode(page, "player-auth-token");
}
String outURL = p.getUrl().toString();
LOG.info("outURL=" + outURL);
String playerToken = getURLField(outURL, "playerToken");
// {u'playerToken': u'eyJQTEFZRVJfVFlQRSI6InNvZnR3YXJlIiwiQ1JFQVRJT05fVElNRV9MT05HIjoxNDUwODIyMDY5MzAxLCJDVVNUT01FUl9JRCI6IkEzU1RKTVE1M0NZOEdQIiwiTUFSS0VUUExBQ0UiOiJBRjJNMEtDOTRSQ0VBIiwiUExBWUVSX0lEIjoiZGEzOWEzZWU1ZTZiNGIwZDMyNTViZmVmOTU2MDE4OTBhZmQ4MDcwOSIsIlBMQVlFUl9NT0RFTCI6IkRlc2t0b3AiLCJQTEFZRVJfTUFOVUYiOiJBdWRpYmxlIn0='}
String content = p.getWebResponse().getContentAsString();
if (playerToken == null || playerToken.isEmpty()) {
LOG.info("content=" + content);
List<com.gargoylesoftware.htmlunit.util.NameValuePair> headers = p.getWebResponse().getResponseHeaders();
LOG.info("headers=" + headers);
login();
outURL = p.getUrl().toString();
playerToken = getURLField(outURL, "playerToken");
if (playerToken == null)
throw new Exception("Unable to get activation player token.");
}
LOG.info("playerToken=" + playerToken);
// # Step 2
getWebClient().getBrowserVersion().setUserAgent("Audible Download Manager");
// Step 3, de-register first, in order to stop hogging all activation slots (there are 8 of them!)
// I don't know why this is needed.. didn't we just create this player token?
unregisterPlayer(playerToken);
// # Step 4
u = getAudibleBase()+"/license/licenseForCustomerToken?" + "customer_token=" + playerToken;
LOG.info(u);
if (getProgress() != null)
getProgress().setSubTask("Registering player");
p = getWebClient().getPage(u);
// get raw bytes. (Do not get as string as it will be corrupted by the key's binary.)
ByteArrayOutputStream bas = new ByteArrayOutputStream();
InputStream is = p.getWebResponse().getContentAsStream();
IO.copy(is, bas);
is.close();
unregisterPlayer(playerToken);
return extract_activation_bytes(bas.toByteArray());
} finally {
getWebClient().getBrowserVersion().setUserAgent(oldAgent);
}
}
private Throwable unregisterPlayer(String playerToken) {
try {
if (getProgress() != null)
getProgress().setSubTask("Unregistering client");
LOG.info("Unregistering client");
String u = "/license/licenseForCustomerToken?customer_token=" + playerToken + "&action=de-register";
setURL(u);
Page resultPage = getWebClient().getPage(u);
WebResponse response = resultPage.getWebResponse();
String content = response.getContentAsString();
LOG.info("De-register Response: " + content);
} catch (Throwable th) {
th.printStackTrace();
return th;
}
return null;
}
private boolean parseBookPage(HtmlPage page, Book b) {
DomNode h = page;
if (getProgress() != null)
getProgress().setTask("Parsing book", b.toString());
String asin = HTMLUtil.findHidden(page, "asin");
if (asin == null) {
for (HtmlAnchor a : page.getAnchors()) {
String d1 = a.getAttribute("data-asin");
if (d1 != null && d1.length() > 0) {
asin = d1;
break;
}
}
}
if (asin != null && asin.length() > 0) {
b.setAsin(asin);
} else {
// trouble ahead... ?
}
if (b.getAsin().length() == 0) {
LOG.info("No ASIN Found for " + b + ", " + b.getInfoLink());
HTMLUtil.debugNode(page, "parse_book_no_asin");
}
String c = "";
String narrator = "";
Node narratedBy = HTMLUtil.findByClass("adbl-narrator-row", h);
if (narratedBy != null) {
NodeList list = narratedBy.getChildNodes();
int narratorListCount = list.getLength();
for (int x = 0; x < narratorListCount; x++) {
Node cn = list.item(x);
c = HTMLUtil.text(cn.getAttributes().getNamedItem("class"));
if ("adbl-prod-author".equals(c)) {
narrator = HTMLUtil.text(cn);
}
}
b.setNarratedBy(narrator);
}
b.setSummary(extractParagraph("adbl-content", h)); //
b.setFullTitle(extract("adbl-prod-h1-title", h));
b.setAuthor(extract("adbl-prod-author", h));
b.setFormat(extract("adbl-format-type", h));
b.setDuration(extract("adbl-run-time", h));
b.setRating_average(extract("rating-average", h));
b.setRating_count(extract("rating-count", h));
b.setRelease_date(extract("adbl-date adbl-release-date", h));
LOG.info(b.inspect(","));
return true;
}
public boolean hasInfo(Book b) {
BookElement required[] = {BookElement.asin, BookElement.narratedBy};
@ -981,7 +599,7 @@ public class AudibleScraper {
String link = b.getInfoLink();
if (link.length() == 0)
return false;
throw new Exception("Product link page unavailable. Book may be discontinued or a library scan is needed.");
setURL(link);
URL u = page.getUrl();
String path = u.getPath();
@ -993,7 +611,7 @@ public class AudibleScraper {
if (getProgress() != null)
getProgress().setTask("Parsing book " + b);
parseBookPage(page, b);
BookPageParser.instance.parseBookPage(page, b);
return true;
}

View file

@ -0,0 +1,245 @@
package org.openaudible.audible;
import com.gargoylesoftware.htmlunit.html.DomNode;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.google.gson.JsonArray;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONArray;
import org.json.JSONObject;
import org.openaudible.books.Book;
import org.openaudible.books.BookElement;
import org.openaudible.util.HTMLUtil;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.ArrayList;
import java.util.List;
public enum BookPageParser {
instance;
private static final Log LOG = LogFactory.getLog(BookPageParser.class);
public String extract(String c, DomNode h) {
return HTMLUtil.text(HTMLUtil.findByClass(c, h));
}
public String extractParagraph(String c, DomNode h) {
String out = "";
DomNode node = (DomNode) HTMLUtil.findByClass(c, h);
if (node != null) {
NodeList cn = node.getChildNodes();
for (int x = 0; x < cn.getLength(); x++) {
Node y = cn.item(x);
String text = y.getTextContent();
if (text != null) {
text = text.trim();
if (out.length() > 0)
out += "\n";
out += text;
}
}
}
return out;
}
List<String> getCDATATags(String html)
{
ArrayList<String> list = new ArrayList<>();
String startTag="<![CDATA[";
String endTag = "//]]>";
int ch = 0;
for (;;)
{
int start = html.indexOf(startTag, ch);
if (start == -1) break;
int end = html.indexOf(endTag, ch);
assert(end!=-1);
if (end == -1) break;
String cdata = html.substring(start+startTag.length(), end).trim();
list.add(cdata);
ch = end+endTag.length();
}
return list;
}
public boolean parseBookPage(HtmlPage page, Book b) {
DomNode h = page;
HTMLUtil.debugNode(page, "book_info");
String xml = page.asXml();
List<String> cdataList = getCDATATags(xml);
for (String cd:cdataList)
{
if (cd.startsWith("["))
{
cd = cd.replace("\n", ""); // getting parse errors.
try {
JSONArray jsonArray = new JSONArray(cd);
for (int x=0;x<jsonArray.length();x++)
{
JSONObject obj = jsonArray.getJSONObject(x);
extractFromJSON(obj, b);
}
}catch(Throwable th)
{
LOG.info(cd);
LOG.error("cdata json parse error", th);
}
}
}
return true;
}
/*
"image": "https://m.media-amazon.com/images/I/51u1om96bmL._SL500_.jpg",
"@type": "Audiobook",
"author": [{
"@type": "Person",
"name": "Amanda Hodgkinson"
}],
"readBy": [{
"@type": "Person",
"name": "Robin Sachs"
}],
"description": "<p>A tour de force that echoes modern classics like <i>Suite Francaise<\/i> and <i>The Postmistress<\/i>. <\/p><p>\"Housekeeper or housewife?\" the soldier asks Silvana as she and eight-year-old Aurek board the ship that will take them from Poland to England at the end of World War II. There her husband, Janusz, is already waiting for them at the little house at 22 Britannia Road. But the war has changed them all so utterly that they'll barely recognize one another when they are reunited. \"Survivor,\" she answers.<\/p><p>Silvana and Aurek spent the war hiding in the forests of Poland. Wild, almost feral Aurek doesn't know how to tie his own shoes or sleep in a bed. Janusz is an Englishman now-determined to forget Poland, forget his own ghosts from the way, and begin a new life as a proper English family. But for Silvana, who cannot escape the painful memory of a shattering wartime act, forgetting is not a possibility.<\/p>",
"abridged": "false",
"inLanguage": "english",
"bookFormat": "AudiobookFormat",
"@context": "https://schema.org",
"datePublished": "2011-04-28",
"duration": "PT11H19M",
"name": "22 Britannia Road",
"publisher": "Penguin Audio",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "3.6842105263157894",
"ratingCount": "171"
}
*/
private void extractFromJSON(JSONObject obj, Book b) {
String typ = obj.optString("@type");
if (typ == null || typ.isEmpty())
return;
if (!"AudioBook".equalsIgnoreCase(typ)) // && !"Product".equalsIgnoreCase(typ))
return;
LOG.info(obj.toString(2));
for (String k:obj.keySet())
{
System.out.println(k+" = "+ obj.get(k));
Object value = obj.get(k);
String str = value!=null ? value.toString():"";
BookElement elem = null;
switch(k)
{
case "description":
elem = BookElement.description;
break;
case "sku":
// elem = BookElement.product_id;
break;
case "duration":
// format is like "PT11H19M" .. skipping for now.
break;
case "productID":
elem = BookElement.asin;
if (b.has(elem)) {
assert (b.get(elem).equals(str));
}
break;
case "datePublished":
elem = BookElement.release_date;
break;
case "author":
str = personToString(obj.getJSONArray(k));
elem = BookElement.author;
break;
case "readBy":
str = personToString(obj.getJSONArray(k));
elem = BookElement.narratedBy;
break;
case "aggregateRating":
JSONObject rating = obj.getJSONObject(k);
double rvalue = rating.optDouble("ratingValue", 0);
int rcount = rating.optInt("ratingCount",0);
if (rvalue>0)
b.setRating_average(rvalue);
if (rcount>0)
b.setRating_count(rcount);
break;
case "name":
elem = BookElement.fullTitle;
break;
case "publisher":
elem = BookElement.publisher;
break;
default:
LOG.info("Skipping "+k+" = "+ str);
break;
}
if (elem!=null && !str.isEmpty())
{
if (!str.equals(b.get(elem))) {
LOG.info("set " + elem + " from " + b.get(elem) + " to " + str);
b.set(elem, str);
}
}
}
}
// "author": [{
// "@type": "Person",
// "name": "Susan Smith"
// }],
// "readBy": [{
// "@type": "Person",
// "name": "Robin Racer"
// }],
private String personToString(JSONArray arr) {
String out = "";
for (int x=0;x<arr.length();x++)
{
JSONObject p = arr.getJSONObject(x);
assert(p.getString("@type").equals("Person"));
String name = p.optString("name","");
if (!name.isEmpty())
{
if (!out.isEmpty())
out+=",";
out += name;
}
}
return out;
}
}

View file

@ -7,6 +7,7 @@ import org.openaudible.AudibleAccountPrefs;
*/
public interface ConnectionListener {
void connectionChanged(boolean connected);
AudibleAccountPrefs getAccountPrefs(AudibleAccountPrefs in);
}

View file

@ -30,13 +30,12 @@ public class ConnectionNotifier extends EventNotifier<ConnectionListener> implem
}
// allow gui to pass back new credentials.
public AudibleAccountPrefs getAccountPrefs(AudibleAccountPrefs in)
{
public AudibleAccountPrefs getAccountPrefs(AudibleAccountPrefs in) {
AudibleAccountPrefs out = in;
for (ConnectionListener l : getListeners()) {
out = l.getAccountPrefs(out);
if (out==null) return null; // canceled
if (out == null) return null; // canceled
}
return out;
}
@ -51,13 +50,20 @@ public class ConnectionNotifier extends EventNotifier<ConnectionListener> implem
}
public boolean isDisconnected() {
return getState()==State.Disconnected;
return getState() == State.Disconnected;
}
public void setLastURL(String u) {
}
// not connected is unknown.
// connected means in account
// disconnected means a password is being asked for.
enum State {Not_Connected, Connected, Disconnected}
enum State {
Not_Connected, Connected, Disconnected
}
}

View file

@ -0,0 +1,147 @@
package org.openaudible.audible;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openaudible.AudibleAccountPrefs;
import org.openaudible.Directories;
import org.openaudible.util.HTMLUtil;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
public class KindleScraper extends AudibleScraper {
public static KindleScraper instance;
private static final Log LOG = LogFactory.getLog(KindleScraper.class);
HashSet<String> digitalProducts = new HashSet<>();
int maxPages = 1;
public KindleScraper(AudibleAccountPrefs account) {
super(account);
instance = this;
}
public void test() throws Exception {
home();
if (!loggedIn())
login();
getFreeBooks();
}
public HtmlAnchor findBooks() throws IOException {
HtmlAnchor next = null;
ArrayList<HtmlAnchor> nextLinks = new ArrayList<>();
for (HtmlAnchor n : page.getAnchors()) {
boolean print = false;
String dp = getDigitalProduct(n);
if (dp != null) {
digitalProducts.add(dp);
// print = true;
}
if (n.toString().toLowerCase().contains("next")) {
print = true;
String clz = n.getAttribute("class");
if (clz == null) clz = "";
if ("pagnNextLink".equalsIgnoreCase(n.getId()) || clz.contains("pagnNext"))// .equalsIgnoreCase(n.getAttribute("class")))
next = n;
nextLinks.add(n);
}
if (print) {
LOG.info(n);
// LOG.info(n.getHrefAttribute());
}
}
LOG.info("next links:" + nextLinks.size());
LOG.info("digitalProducts:" + digitalProducts.size());
return next;
}
private String getDigitalProduct(HtmlAnchor n) {
String find = "/dp/";
String ref = n.getHrefAttribute();
int ch = ref.indexOf(find);
if (ch != -1) {
String id = ref.substring(ch + find.length(), ref.length());
ch = id.indexOf("/");
assert (ch != -1);
if (ch != -1) {
id = id.substring(0, ch);
int len = id.length();
assert (len < 15);
if (len < 15)
return id;
}
}
return null;
}
public void getProductInfo(String dp) throws IOException {
File x = new File(Directories.getTmpDir(), "dp_" + dp + ".html");
if (!x.exists()) {
String base = "https://www.amazon.com/dp/" + dp;
setURL(base);
parseDigitalProductPage(page);
HTMLUtil.writeFile(x, page.getDocumentElement().asXml());
}
}
private void parseDigitalProductPage(Page p) {
}
public String getAudibleBase() {
return "https://amazon.com/";
// return account.audibleRegion.getBaseURL();
}
public void getFreeBooks() throws IOException {
int pages = 0;
String base = "https://www.amazon.com/Books-with-Narration-in-Kindle-Unlimited/b?ie=UTF8&node=9630682011";
setURL(base);
checkLoggedIn();
for (; ; ) {
int startBooks = digitalProducts.size();
HtmlAnchor next = findBooks();
if (next == null) break;
int endBooks = digitalProducts.size();
if (startBooks == endBooks) break;
if (++pages > maxPages) break;
page = next.click();
}
LOG.info("digitalProducts:" + digitalProducts.size());
for (String dp : digitalProducts) {
getProductInfo(dp);
}
}
}

View file

@ -0,0 +1,272 @@
package org.openaudible.audible;
import com.gargoylesoftware.htmlunit.html.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openaudible.books.Book;
import org.openaudible.books.BookElement;
import org.openaudible.util.HTMLUtil;
import org.openaudible.util.Util;
import java.util.ArrayList;
import java.util.List;
// Parse a page of library inforation.
// http://audible.com/lib
// When audible changes the format of the above web page... this class will need to be updated.
public enum LibraryParser {
instance;
private static final Log LOG = LogFactory.getLog(LibraryParser.class);
boolean debug = false;
// Expected Columns:
// Image
// Title
// Author
// Length
// Date Added
// Rate and Review
// Downloaded
// Other Actions
private enum BookColumns {
Image, Title, Author, Length, Date_Added, Ratings, Download, Other;
public static int size() {
return values().length;
}
// get product id and asin first.
static BookColumns [] parseOrder = {Download, Other, Image, Title, Author, Length, Date_Added, Ratings};
}
// return "next" button for next page of results.
public HtmlElement getNextPage(HtmlPage page) {
HtmlElement next = null;
DomNodeList<HtmlElement> buttons = page.getDocumentElement().getElementsByTagName("button");
for (HtmlElement a : buttons) {
// System.out.println(a);
if (a.toString().toLowerCase().contains("pagenext")) {
assert (next == null);
next = a;
}
}
return next;
}
public ArrayList<Book> parseLibraryFragment(DomNode fragment) {
ArrayList<Book> list = new ArrayList<>();
ArrayList<String> colNames = new ArrayList<>();
HtmlTable table = fragment.getFirstByXPath("//table");
if (table == null)
return list;
if (debug) HTMLUtil.debugNode(table, "lib_table");
int purchaseDateIndex = -1;
List<HtmlElement> header = table.getElementsByTagName("th");
if (header.size() != BookColumns.size())
{
LOG.info("Skipping table with:"+header.size()+" cols");
return list;
}
int index = 0;
for (HtmlElement h : header) {
String xml = h.asXml();
colNames.add(h.asText());
if (xml.contains("PURCHASE_DATE")) {
purchaseDateIndex = index;
}
index++;
}
assert (purchaseDateIndex == BookColumns.Date_Added.ordinal());
int rindex = 0;
for (HtmlTableRow r : table.getRows()) {
rindex++;
if (rindex == 1) continue; // skip header row.
Book b = parseLibraryRow(r);
if (b != null && b.isOK())
list.add(b);
}
LOG.info("Library page contains: " + list.size() + " book(s)");
return list;
}
String debugString = "BK_PENG_003023xxx";
private Book parseLibraryRow(HtmlTableRow r) {
String xml = Util.cleanString(r.asXml());
if (r.getCells().size() == 0)
return null; // empty row.
if (r.getCells().size() != BookColumns.size()) {
LOG.error("wrong number of columns found: " + r.getCells().size() + " != " + BookColumns.size());
LOG.error(xml);
HTMLUtil.debugNode(r, "bad_col.xml");
return null;
}
Book b = new Book();
String asin = HTMLUtil.findHidden(r, "asin");
b.setAsin(asin);
int count = Util.substringCount(debugString, xml);
LOG.info("Found " + count + " product_id");
if (debug) HTMLUtil.debugNode(r, "cur_row");
List<HtmlElement> cells = r.getElementsByTagName("td");
for (BookColumns col : BookColumns.parseOrder) {
HtmlElement cell = cells.get(col.ordinal());
parseBookColumn(col, cell, b);
}
return b;
}
private void parseBookColumn(BookColumns col, HtmlElement cell, Book b) {
// HTMLUtil.debugNode(cell, col.name()+".xml");
String text = Util.cleanString(cell.asText());
String xml = Util.cleanString(cell.asXml());
DomNodeList<HtmlElement> anchors;
if (xml.contains(debugString)) {
LOG.info("Found product_id in " + col);
LOG.info(col.name() + "=" + text);
LOG.info("xml=" + Util.cleanString(xml));
}
int ch = text.indexOf("\n");
if (ch!=-1)
text = text.substring(0, ch);
switch (col) {
case Image:
break;
case Title:
anchors = cell.getElementsByTagName("a");
for (int x = 0; x < anchors.size(); x++) {
HtmlAnchor a = (HtmlAnchor) anchors.get(x);
String url = a.getHrefAttribute();
// /pd/Fiction/Exodus-Audiobook/B008I3VMMQ?
if (url.startsWith("/pd/")) {
int q = url.indexOf("?");
if (q!=-1)
url = url.substring(0, q);
boolean ok = false;
if (b.has(BookElement.asin) && url.contains(b.getAsin()))
ok=true;
if (b.has(BookElement.product_id) && url.contains(b.getProduct_id()))
ok=true;
if (ok)
b.setInfoLink(url);
else
LOG.info("Unknown product link for "+b+" at "+url);
}
}
if (text.contains("by parts"))
{
LOG.error("error with title: "+text);
HTMLUtil.debugNode(cell, col.name()+".xml");
// bug check.
}
b.setFullTitle(text);
break;
case Author:
b.setAuthor(text);
break;
case Length:
b.setDuration(text);
break;
case Date_Added:
b.setPurchaseDate(text);
break;
case Ratings:
// TODO: Update ratings.
break;
case Download:
break;
case Other:
anchors = cell.getElementsByTagName("a");
for (int x = 0; x < anchors.size(); x++) {
HtmlAnchor a = (HtmlAnchor) anchors.get(x);
String url = a.getHrefAttribute();
if (url.contains("cds.audible")) {
parseDownloadURL(a, b);
}
}
break;
}
}
/*
String url = "https://cds.audible.com/download/admhelper?user_id=xxx-yyy&amp;product_id=BK_PENG_00000&amp;domain=www.audible.com&amp;order_number=xxxx&amp;
cust_id=xxx&amp;DownloadType=Now&amp;transfer_player=1&amp;title=Book Title&amp;codec=LC_32_22050_Mono&amp;awtype=AAX";
*/
private void parseDownloadURL(HtmlAnchor a, Book b) {
String url = a.getHrefAttribute();
String obj = a.toString();
try {
String args = url.substring(url.indexOf("?") + 1, url.length());
String split[] = args.split("&");
for (String params : split) {
String kv[] = params.split("=");
if (kv.length == 2) {
LOG.info(kv[0] + "=" + kv[1]);
BookElement elem = BookElement.findByName(kv[0]);
if (elem != null) {
b.set(elem, kv[1]);
}
} else {
LOG.error("bad url param:" + params + " for " + url); /// happens when there is an & in title.
}
}
} catch (Throwable th) {
LOG.error("Error parsing anchor:" + url + " for " + obj);
}
}
}

View file

@ -1,5 +1,6 @@
package org.openaudible.books;
import java.io.Serializable;
import java.util.HashMap;
@ -67,6 +68,34 @@ 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());
// boolean e2 = this.getAsin().equals(that.getAsin());
// assert (e1 == e2);
return e1;
}
public boolean isOK() {
return checkBook().isEmpty();
}
// product_id is our primary key.
public String checkBook() {
// BookElement required[] = { BookElement.product_id, BookElement.user_id, BookElement.cust_id };
BookElement required[] = {BookElement.product_id, BookElement.fullTitle};
for (BookElement e : required) {
if (!this.has(e))
return "required:" + e + " missing from " + this;
}
return "";
}
// unique value for book.
public String id() {
return getProduct_id();
@ -74,7 +103,10 @@ public class Book implements Comparable<Book>, Serializable {
// isPartialBook. A part of a single book. Not to be confused with one of a series.
public boolean partial() {
char last = getProduct_id().charAt(getProduct_id().length() - 1);
String pid = getProduct_id();
if (pid.isEmpty()) throw new IllegalStateException("Undefined");
char last = pid.charAt(pid.length() - 1);
// last character is lowercase letter..
return !Character.isDigit(last);
}
@ -181,6 +213,10 @@ public class Book implements Comparable<Book>, Serializable {
set(BookElement.rating_average, rating_average);
}
public void setRating_average(double rating_average) {
set(BookElement.rating_average, ""+ rating_average);
}
public String getRating_count() {
return get(BookElement.rating_count);
}
@ -188,6 +224,9 @@ public class Book implements Comparable<Book>, Serializable {
public void setRating_count(String rating_count) {
set(BookElement.rating_count, rating_count);
}
public void setRating_count(int rating_count) {
set(BookElement.rating_count, ""+rating_count);
}
public String getRelease_date() {
return get(BookElement.release_date);
@ -239,6 +278,12 @@ 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);
}

View file

@ -3,7 +3,18 @@ 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;
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;
public static BookElement findByName(String s) {
try {
return BookElement.valueOf(s);
} catch (Throwable th) {
System.out.println("No BookElement:" + s);
}
return null;
}
public String displayName() {
String o = this.name();
@ -73,6 +84,10 @@ public enum BookElement {
case cust_id:
o = "Customer ID";
break;
case order_number:
o = "Order #";
break;
default:
assert (false);
}

View file

@ -3,14 +3,19 @@ package org.openaudible.books;
import java.util.List;
public interface BookListener {
default void booksSelected(final List<Book> list){}
default void booksSelected(final List<Book> list) {
}
default void bookAdded(final Book book){}
default void bookAdded(final Book book) {
}
default void bookUpdated(final Book book){}
default void bookUpdated(final Book book) {
}
default void booksUpdated() {} // refresh all books
default void booksUpdated() {
} // refresh all books
default void bookProgress(final Book book, final String msg) {}
default void bookProgress(final Book book, final String msg) {
}
}

View file

@ -11,6 +11,7 @@ import java.util.List;
/**
* Created 6/27/2017.
* Used as singleton to notify all book listeners about a book event
* Events are defined in interface BookListener
*/
public class BookNotifier extends EventNotifier<BookListener> implements BookListener {
private static final Log LOG = LogFactory.getLog(BookNotifier.class);
@ -75,7 +76,7 @@ public class BookNotifier extends EventNotifier<BookListener> implements BookLis
public void bookProgress(final Book book, final String task) {
if (enabled) {
for (BookListener l : getListeners())
l.bookProgress(book,task);
l.bookProgress(book, task);
}
}

View file

@ -120,8 +120,7 @@ public enum AAXParser {
ffmpeg(b, aaxFile);
if (imageDest==null && Audible.instance!=null)
{
if (imageDest == null && Audible.instance != null) {
// hack.
imageDest = Audible.instance.getImageFileDest(b);

View file

@ -14,7 +14,8 @@ public enum BookMerge {
if (book.getProduct_id().equals(book2.getProduct_id())) {
for (BookElement e : BookElement.values()) {
if (mergeItem(book, book2, e)) list.add(e);
if (mergeItem(book, book2, e))
list.add(e);
}
} else {

View file

@ -28,7 +28,7 @@ public class ConvertJob implements IQueueJob, LineListener {
private Process proc = null;
private IProgressTask progress;
final String duration;
final static int mp3Qscale=6; // audio quality value 0 to 9. See https://trac.ffmpeg.org/wiki/Encode/MP3
final static int mp3Qscale = 6; // audio quality value 0 to 9. See https://trac.ffmpeg.org/wiki/Encode/MP3
public ConvertJob(Book b) {
book = b;
@ -60,25 +60,24 @@ public class ConvertJob implements IQueueJob, LineListener {
public void takeLine(String s) {
String find = "time=";
int ch = s.indexOf(find);
if (ch!=-1) {
if (ch != -1) {
nextMeta = false;
long now = System.currentTimeMillis();
if (now > next) {
next = now + interval;
// interval*=2;
// System.err.println(s);
String time = s.substring(ch+find.length());
String time = s.substring(ch + find.length());
int end = time.indexOf(".");
if (end==-1) end = time.indexOf(" ");
if (end == -1) end = time.indexOf(" ");
if (end==-1) end = time.length();
if (end == -1) end = time.length();
time = time.substring(0, end);
String status=time;
if (duration.length()>0)
status += " of "+ duration;
String status = time;
if (duration.length() > 0)
status += " of " + duration;
if (progress != null) {
progress.setTask(null, status.trim());
@ -119,10 +118,10 @@ public class ConvertJob implements IQueueJob, LineListener {
args.add("libmp3lame"); // see: https://trac.ffmpeg.org/wiki/Encode/MP3
args.add("-qscale:a"); // https://trac.ffmpeg.org/wiki/Encode/MP3
if (mp3Qscale<0 || mp3Qscale>9)
throw new IOException("Invalid qscale:"+mp3Qscale);
if (mp3Qscale < 0 || mp3Qscale > 9)
throw new IOException("Invalid qscale:" + mp3Qscale);
args.add(""+mp3Qscale);
args.add("" + mp3Qscale);
args.add(temp.getAbsolutePath());
@ -140,7 +139,7 @@ public class ConvertJob implements IQueueJob, LineListener {
ProcessBuilder pb = new ProcessBuilder(args);
InputStreamReporter err;
InputStream errStream=null;
InputStream errStream = null;
boolean success = false;
@ -155,15 +154,15 @@ public class ConvertJob implements IQueueJob, LineListener {
err.finish();
while (!proc.waitFor(1, TimeUnit.SECONDS)) {
if (quit)
throw new IOException("conversion quit"); }
throw new IOException("conversion quit");
}
int exitValue = proc.exitValue();
LOG.info("createMP3:"+exitValue);
if (exitValue!=0)
throw new IOException("Conversion got non-zero response:"+exitValue);
LOG.info("createMP3:" + exitValue);
if (exitValue != 0)
throw new IOException("Conversion got non-zero response:" + exitValue);
success = true;
@ -235,14 +234,12 @@ public class ConvertJob implements IQueueJob, LineListener {
ok = true;
if (progress != null)
progress.setTask(null, "Complete");
}
catch(Exception e){
if (progress!=null) {
} catch (Exception e) {
if (progress != null) {
progress.setSubTask(e.getMessage());
}
throw e;
}
finally {
} finally {
if (!ok) {
if (temp.exists())
temp.delete();

View file

@ -11,11 +11,11 @@ import org.openaudible.util.queues.ThreadedQueue;
public class ConvertQueue extends ThreadedQueue<Book> {
// Queue to convert audio books, one thread at a time.
// Queue to convert audio books to mp3
private static final Log LOG = LogFactory.getLog(ConvertQueue.class);
public ConvertQueue() {
super(2);
super(3);
}
@Override
@ -30,8 +30,7 @@ public class ConvertQueue extends ThreadedQueue<Book> {
}
@Override
public String toString()
{
public String toString() {
return "ConvertQueue";
}

View file

@ -1 +1 @@
package org.openaudible.convert; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openaudible.util.Platform; import org.openaudible.util.SimpleProcess; import java.io.File; import java.io.IOException; import java.util.ArrayList; public class FFMPEG { private static final Log LOG = LogFactory.getLog(FFMPEG.class); // Return ffmpeg meta data file for media file specified in path static String getMetaData(String path) throws IOException, InterruptedException { if (!new File(path).exists()) throw new IOException("File not found:" + path); ArrayList<String> args = new ArrayList<>(); args.add(getExecutable()); args.add("-i"); args.add(path); args.add("-f"); args.add("ffmetadata"); args.add("-"); SimpleProcess ffmpeg = new SimpleProcess(args); SimpleProcess.Results results = ffmpeg.getResults(); System.err.println(results.getErrorString()); System.err.println(results.getOutputString()); return results.getOutputString(); } static String getExecutable() { String s = "bin" + File.separatorChar + Platform.getPlatform().name() + "_ffmpeg"; File f = new File(s); if (!f.exists()) { LOG.info("Unable to find executable:"+f.getAbsolutePath()); } if (f.exists()) return f.getAbsolutePath(); return "ffmpeg"; // hope it is in system path. } // Sets (modifed) ffmpeg meta data file for media file specified in path // Returns the File Path of the newly created media file. static File setMetaData(String path, String metaDataPath, String newFilePath) throws IOException, InterruptedException { if (!new File(path).exists()) throw new IOException("File not found:" + path); if (!new File(metaDataPath).exists()) throw new IOException("File not found:" + metaDataPath); ArrayList<String> args = new ArrayList<>(); args.add(getExecutable()); args.add("-i"); args.add(path); args.add("-i"); args.add(metaDataPath); args.add("-map_metadata"); args.add("1"); args.add("-codec"); args.add("copy"); args.add(newFilePath); SimpleProcess ffmpeg = new SimpleProcess(args); ffmpeg.run(); File t = new File(newFilePath); assert (t.exists()); return t; } public static String getVersion() throws IOException, InterruptedException { ArrayList<String> args = new ArrayList<>(); args.add(getExecutable()); args.add("-version"); SimpleProcess ffmpeg = new SimpleProcess(args); SimpleProcess.Results results = ffmpeg.getResults(); String r = results.getOutputString(); int ch = r.indexOf("\r"); if (ch > 0) r = r.substring(0, ch - 1); if (r.trim().isEmpty()) { String err = results.getErrorString(); if (!err.isEmpty()) throw new IOException(err); throw new IOException("unknown ffmpeg error:" + getExecutable()); } return r; } }
package org.openaudible.convert; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openaudible.util.Platform; import org.openaudible.util.SimpleProcess; import java.io.File; import java.io.IOException; import java.util.ArrayList; public class FFMPEG { private static final Log LOG = LogFactory.getLog(FFMPEG.class); // Return ffmpeg meta data file for media file specified in path static String getMetaData(String path) throws IOException, InterruptedException { if (!new File(path).exists()) throw new IOException("File not found:" + path); ArrayList<String> args = new ArrayList<>(); args.add(getExecutable()); args.add("-i"); args.add(path); args.add("-f"); args.add("ffmetadata"); args.add("-"); SimpleProcess ffmpeg = new SimpleProcess(args); SimpleProcess.Results results = ffmpeg.getResults(); System.err.println(results.getErrorString()); System.err.println(results.getOutputString()); return results.getOutputString(); } static String getExecutable() { String s = "bin" + File.separatorChar + Platform.getPlatform().name() + "_ffmpeg"; File f = new File(s); if (!f.exists()) { LOG.info("Unable to find executable:" + f.getAbsolutePath()); } if (f.exists()) return f.getAbsolutePath(); return "ffmpeg"; // hope it is in system path. } // Sets (modifed) ffmpeg meta data file for media file specified in path // Returns the File Path of the newly created media file. static File setMetaData(String path, String metaDataPath, String newFilePath) throws IOException, InterruptedException { if (!new File(path).exists()) throw new IOException("File not found:" + path); if (!new File(metaDataPath).exists()) throw new IOException("File not found:" + metaDataPath); ArrayList<String> args = new ArrayList<>(); args.add(getExecutable()); args.add("-i"); args.add(path); args.add("-i"); args.add(metaDataPath); args.add("-map_metadata"); args.add("1"); args.add("-codec"); args.add("copy"); args.add(newFilePath); SimpleProcess ffmpeg = new SimpleProcess(args); ffmpeg.run(); File t = new File(newFilePath); assert (t.exists()); return t; } public static String getVersion() throws IOException, InterruptedException { ArrayList<String> args = new ArrayList<>(); args.add(getExecutable()); args.add("-version"); SimpleProcess ffmpeg = new SimpleProcess(args); SimpleProcess.Results results = ffmpeg.getResults(); String r = results.getOutputString(); int ch = r.indexOf("\r"); if (ch > 0) r = r.substring(0, ch - 1); if (r.trim().isEmpty()) { String err = results.getErrorString(); if (!err.isEmpty()) throw new IOException(err); throw new IOException("unknown ffmpeg error:" + getExecutable()); } return r; } }

View file

@ -18,8 +18,8 @@ public enum LookupKey {
instance;
private static final Log LOG = LogFactory.getLog(LookupKey.class);
HashMap<String,String> map=new HashMap<>(); // hash, key
File prefs=null;
HashMap<String, String> map = new HashMap<>(); // hash, key
File prefs = null;
public void load(File prefsFile) throws IOException {
@ -28,26 +28,23 @@ public enum LookupKey {
Gson gson = new GsonBuilder().create();
String content = HTMLUtil.readFile(prefsFile);
HashMap m = gson.fromJson(content, HashMap.class);
if (m!=null)
map.putAll(m);
if (m != null)
map.putAll(m);
}
prefs = prefsFile;
}
public void save() throws IOException {
if (prefs!=null && map.size()>0)
{
if (prefs != null && map.size() > 0) {
Gson gson = new Gson();
String json = gson.toJson(map);
try (FileWriter writer = new FileWriter(prefs))
{
try (FileWriter writer = new FileWriter(prefs)) {
writer.write(json);
}
}
}
public String getFileChecksum(String path) throws IOException, InterruptedException {
assert (path.toLowerCase().endsWith(".aax")); // expected
@ -108,16 +105,14 @@ public enum LookupKey {
ArrayList<String> args = new ArrayList<>();
args.add(getExecutable());
// I don't know wjh
if (!Platform.isWindows())
{
for (File f:tablesDir.listFiles())
if (f.getName().contains(".rt"))
args.add(f.getAbsolutePath());
} else
{
args.add(tablesDir.getAbsolutePath());
if (!Platform.isWindows()) {
for (File f : tablesDir.listFiles())
if (f.getName().contains(".rt"))
args.add(f.getAbsolutePath());
} else {
args.add(tablesDir.getAbsolutePath());
}
args.add("-h");
args.add(hash);

View file

@ -286,10 +286,10 @@ public class EnumTable<E extends Object, F extends Enum> implements SelectionLis
assertTable();
}
AtomicInteger cache=new AtomicInteger(0);
AtomicInteger cache = new AtomicInteger(0);
public void populateData() {
if (cache.getAndIncrement()==0) {
if (cache.getAndIncrement() == 0) {
SWTAsync.run(new SWTAsync("populateData") {
@Override
public void task() {

View file

@ -9,11 +9,11 @@ public abstract class MultiColumnData extends SingleColumnData {
@Override
public abstract String getTextValue(SuperTable table, int col);
/*
/*
* { switch (col) { case 0: return data.toString(); default: return ""; }
*
* }
*/
*
* }
*/
public abstract int getSortCol();

View file

@ -1067,183 +1067,183 @@ public class SuperTable<E extends SuperTableData<? extends Comparable>> implemen
// got one report of an error somewhere in this function.
// search bug reports for mccary@hotmail.com
/*
*
* error_stacktrace=java.lang.ArrayIndexOutOfBoundsException: 4 at
* org.eclipse.swt.widgets.Table._getItem(int) at
* org.eclipse.swt.widgets.Table.wmNotifyChild(NMHDR, int, int) at
* org.eclipse.swt.widgets.Control.wmNotify(NMHDR, int, int) at
* org.eclipse.swt.widgets.Composite.wmNotify(NMHDR, int, int) at
* org.eclipse.swt.widgets.Control.WM_NOTIFY(int, int) at
* org.eclipse.swt.widgets.Control.windowProc(int, int, int, int) at
* org.eclipse.swt.widgets.Display.windowProc(int, int, int, int)
* <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.CallWindowProcW(int, int, int,
* int, int) at
* org.eclipse.swt.internal.win32.OS.CallWindowProc(int, int, int,
* int, int) at org.eclipse.swt.widgets.Table.callWindowProc(int,
* int, int, int, boolean) at
* org.eclipse.swt.widgets.Table.callWindowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Control.windowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Table.windowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Display.windowProc(int, int, int, int)
* <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.CallWindowProcW(int, int, int,
* int, int) at
* org.eclipse.swt.internal.win32.OS.CallWindowProc(int, int, int,
* int, int) at org.eclipse.swt.widgets.Table.callWindowProc(int,
* int, int, int, boolean) at
* org.eclipse.swt.widgets.Table.callWindowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Control.windowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Table.windowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Display.windowProc(int, int, int, int)
* <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.CallWindowProcW(int, int, int,
* int, int) at
* org.eclipse.swt.internal.win32.OS.CallWindowProc(int, int, int,
* int, int) at org.eclipse.swt.widgets.Table.callWindowProc(int,
* int, int, int, boolean) at
* org.eclipse.swt.widgets.Table.callWindowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Table.windowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Display.windowProc(int, int, int, int)
* <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.CallWindowProcW(int, int, int,
* int, int) at
* org.eclipse.swt.internal.win32.OS.CallWindowProc(int, int, int,
* int, int) at org.eclipse.swt.widgets.Table.callWindowProc(int,
* int, int, int, boolean) at
* org.eclipse.swt.widgets.Table.callWindowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Control.windowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Table.windowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Display.windowProc(int, int, int, int)
* <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.SendMessageW(int, int, int,
* int) at org.eclipse.swt.internal.win32.OS.SendMessage(int, int,
* int, int) at org.eclipse.swt.widgets.TableColumn.setWidth(int) at
* ss.tables.SuperTable.noHorizontalScroll() at
* ss.tables.SuperTable$ResizeEvent.handleEvent(Event) at
* org.eclipse.swt.widgets.EventTable.sendEvent(Event) at
* org.eclipse.swt.widgets.Widget.sendEvent(Event) at
* org.eclipse.swt.widgets.Widget.sendEvent(int, Event, boolean) at
* org.eclipse.swt.widgets.Widget.sendEvent(int) at
* org.eclipse.swt.widgets.Table.setDeferResize(boolean) at
* org.eclipse.swt.widgets.Table.setBounds(int, int, int, int, int,
* boolean) at org.eclipse.swt.widgets.Control.setBounds(int, int,
* int, int, int) at org.eclipse.swt.widgets.Control.setBounds(int,
* int, int, int) at
* org.eclipse.swt.custom.SashFormLayout.layout(Composite, boolean)
* at org.eclipse.swt.widgets.Composite.updateLayout(boolean,
* boolean) at org.eclipse.swt.widgets.Composite.WM_SIZE(int, int)
* at org.eclipse.swt.widgets.Control.windowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Display.windowProc(int, int, int, int)
* <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.DefWindowProcW(int, int, int,
* int) at org.eclipse.swt.internal.win32.OS.DefWindowProc(int, int,
* int, int) at
* org.eclipse.swt.widgets.Scrollable.callWindowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Control.WM_WINDOWPOSCHANGED(int,
* int) at org.eclipse.swt.widgets.Control.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Display.windowProc(int, int, int,
* int) <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.EndDeferWindowPos(int) at
* org.eclipse.swt.widgets.Composite.resizeChildren(boolean,
* WINDOWPOS[]) at
* org.eclipse.swt.widgets.Composite.resizeChildren() at
* org.eclipse.swt.widgets.Composite.setResizeChildren(boolean) at
* org.eclipse.swt.widgets.Composite.WM_SIZE(int, int) at
* org.eclipse.swt.widgets.Control.windowProc(int, int, int, int) at
* org.eclipse.swt.widgets.Display.windowProc(int, int, int, int)
* <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.DefWindowProcW(int, int, int,
* int) at org.eclipse.swt.internal.win32.OS.DefWindowProc(int, int,
* int, int) at
* org.eclipse.swt.widgets.Scrollable.callWindowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Control.WM_WINDOWPOSCHANGED(int,
* int) at org.eclipse.swt.widgets.Control.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Display.windowProc(int, int, int,
* int) <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.EndDeferWindowPos(int) at
* org.eclipse.swt.widgets.Composite.resizeChildren(boolean,
* WINDOWPOS[]) at
* org.eclipse.swt.widgets.Composite.resizeChildren() at
* org.eclipse.swt.widgets.Composite.setResizeChildren(boolean) at
* org.eclipse.swt.widgets.Composite.WM_SIZE(int, int) at
* org.eclipse.swt.widgets.Control.windowProc(int, int, int, int) at
* org.eclipse.swt.widgets.Display.windowProc(int, int, int, int)
* <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.DefWindowProcW(int, int, int,
* int) at org.eclipse.swt.internal.win32.OS.DefWindowProc(int, int,
* int, int) at
* org.eclipse.swt.widgets.Scrollable.callWindowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Control.WM_WINDOWPOSCHANGED(int,
* int) at org.eclipse.swt.widgets.Control.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Display.windowProc(int, int, int,
* int) <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.EndDeferWindowPos(int) at
* org.eclipse.swt.widgets.Composite.resizeChildren(boolean,
* WINDOWPOS[]) at
* org.eclipse.swt.widgets.Composite.resizeChildren() at
* org.eclipse.swt.widgets.Composite.setResizeChildren(boolean) at
* org.eclipse.swt.widgets.Composite.WM_SIZE(int, int) at
* org.eclipse.swt.widgets.Canvas.WM_SIZE(int, int) at
* org.eclipse.swt.widgets.Decorations.WM_SIZE(int, int) at
* org.eclipse.swt.widgets.Control.windowProc(int, int, int, int) at
* org.eclipse.swt.widgets.Canvas.windowProc(int, int, int, int) at
* org.eclipse.swt.widgets.Decorations.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Shell.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Display.windowProc(int, int, int,
* int) <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.DefWindowProcW(int, int, int,
* int) at org.eclipse.swt.internal.win32.OS.DefWindowProc(int, int,
* int, int) at org.eclipse.swt.widgets.Shell.callWindowProc(int,
* int, int, int) at
* org.eclipse.swt.widgets.Control.WM_WINDOWPOSCHANGED(int, int) at
* org.eclipse.swt.widgets.Canvas.WM_WINDOWPOSCHANGED(int, int) at
* org.eclipse.swt.widgets.Control.windowProc(int, int, int, int) at
* org.eclipse.swt.widgets.Canvas.windowProc(int, int, int, int) at
* org.eclipse.swt.widgets.Decorations.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Shell.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Display.windowProc(int, int, int,
* int) <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.DefWindowProcW(int, int, int,
* int) at org.eclipse.swt.internal.win32.OS.DefWindowProc(int, int,
* int, int) at org.eclipse.swt.widgets.Shell.callWindowProc(int,
* int, int, int) at org.eclipse.swt.widgets.Control.windowProc(int,
* int, int, int) at org.eclipse.swt.widgets.Canvas.windowProc(int,
* int, int, int) at
* org.eclipse.swt.widgets.Decorations.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Shell.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Display.windowProc(int, int, int,
* int) <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.DefWindowProcW(int, int, int,
* int) at org.eclipse.swt.internal.win32.OS.DefWindowProc(int, int,
* int, int) at org.eclipse.swt.widgets.Shell.callWindowProc(int,
* int, int, int) at org.eclipse.swt.widgets.Control.windowProc(int,
* int, int, int) at org.eclipse.swt.widgets.Canvas.windowProc(int,
* int, int, int) at
* org.eclipse.swt.widgets.Decorations.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Shell.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Display.windowProc(int, int, int,
* int) <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.DispatchMessageW(MSG) at
* org.eclipse.swt.internal.win32.OS.DispatchMessage(MSG) at
* org.eclipse.swt.widgets.Display.readAndDispatch() at
* ss.controller.GUI.runEventLoop()
*/
*
* error_stacktrace=java.lang.ArrayIndexOutOfBoundsException: 4 at
* org.eclipse.swt.widgets.Table._getItem(int) at
* org.eclipse.swt.widgets.Table.wmNotifyChild(NMHDR, int, int) at
* org.eclipse.swt.widgets.Control.wmNotify(NMHDR, int, int) at
* org.eclipse.swt.widgets.Composite.wmNotify(NMHDR, int, int) at
* org.eclipse.swt.widgets.Control.WM_NOTIFY(int, int) at
* org.eclipse.swt.widgets.Control.windowProc(int, int, int, int) at
* org.eclipse.swt.widgets.Display.windowProc(int, int, int, int)
* <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.CallWindowProcW(int, int, int,
* int, int) at
* org.eclipse.swt.internal.win32.OS.CallWindowProc(int, int, int,
* int, int) at org.eclipse.swt.widgets.Table.callWindowProc(int,
* int, int, int, boolean) at
* org.eclipse.swt.widgets.Table.callWindowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Control.windowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Table.windowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Display.windowProc(int, int, int, int)
* <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.CallWindowProcW(int, int, int,
* int, int) at
* org.eclipse.swt.internal.win32.OS.CallWindowProc(int, int, int,
* int, int) at org.eclipse.swt.widgets.Table.callWindowProc(int,
* int, int, int, boolean) at
* org.eclipse.swt.widgets.Table.callWindowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Control.windowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Table.windowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Display.windowProc(int, int, int, int)
* <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.CallWindowProcW(int, int, int,
* int, int) at
* org.eclipse.swt.internal.win32.OS.CallWindowProc(int, int, int,
* int, int) at org.eclipse.swt.widgets.Table.callWindowProc(int,
* int, int, int, boolean) at
* org.eclipse.swt.widgets.Table.callWindowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Table.windowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Display.windowProc(int, int, int, int)
* <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.CallWindowProcW(int, int, int,
* int, int) at
* org.eclipse.swt.internal.win32.OS.CallWindowProc(int, int, int,
* int, int) at org.eclipse.swt.widgets.Table.callWindowProc(int,
* int, int, int, boolean) at
* org.eclipse.swt.widgets.Table.callWindowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Control.windowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Table.windowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Display.windowProc(int, int, int, int)
* <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.SendMessageW(int, int, int,
* int) at org.eclipse.swt.internal.win32.OS.SendMessage(int, int,
* int, int) at org.eclipse.swt.widgets.TableColumn.setWidth(int) at
* ss.tables.SuperTable.noHorizontalScroll() at
* ss.tables.SuperTable$ResizeEvent.handleEvent(Event) at
* org.eclipse.swt.widgets.EventTable.sendEvent(Event) at
* org.eclipse.swt.widgets.Widget.sendEvent(Event) at
* org.eclipse.swt.widgets.Widget.sendEvent(int, Event, boolean) at
* org.eclipse.swt.widgets.Widget.sendEvent(int) at
* org.eclipse.swt.widgets.Table.setDeferResize(boolean) at
* org.eclipse.swt.widgets.Table.setBounds(int, int, int, int, int,
* boolean) at org.eclipse.swt.widgets.Control.setBounds(int, int,
* int, int, int) at org.eclipse.swt.widgets.Control.setBounds(int,
* int, int, int) at
* org.eclipse.swt.custom.SashFormLayout.layout(Composite, boolean)
* at org.eclipse.swt.widgets.Composite.updateLayout(boolean,
* boolean) at org.eclipse.swt.widgets.Composite.WM_SIZE(int, int)
* at org.eclipse.swt.widgets.Control.windowProc(int, int, int, int)
* at org.eclipse.swt.widgets.Display.windowProc(int, int, int, int)
* <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.DefWindowProcW(int, int, int,
* int) at org.eclipse.swt.internal.win32.OS.DefWindowProc(int, int,
* int, int) at
* org.eclipse.swt.widgets.Scrollable.callWindowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Control.WM_WINDOWPOSCHANGED(int,
* int) at org.eclipse.swt.widgets.Control.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Display.windowProc(int, int, int,
* int) <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.EndDeferWindowPos(int) at
* org.eclipse.swt.widgets.Composite.resizeChildren(boolean,
* WINDOWPOS[]) at
* org.eclipse.swt.widgets.Composite.resizeChildren() at
* org.eclipse.swt.widgets.Composite.setResizeChildren(boolean) at
* org.eclipse.swt.widgets.Composite.WM_SIZE(int, int) at
* org.eclipse.swt.widgets.Control.windowProc(int, int, int, int) at
* org.eclipse.swt.widgets.Display.windowProc(int, int, int, int)
* <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.DefWindowProcW(int, int, int,
* int) at org.eclipse.swt.internal.win32.OS.DefWindowProc(int, int,
* int, int) at
* org.eclipse.swt.widgets.Scrollable.callWindowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Control.WM_WINDOWPOSCHANGED(int,
* int) at org.eclipse.swt.widgets.Control.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Display.windowProc(int, int, int,
* int) <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.EndDeferWindowPos(int) at
* org.eclipse.swt.widgets.Composite.resizeChildren(boolean,
* WINDOWPOS[]) at
* org.eclipse.swt.widgets.Composite.resizeChildren() at
* org.eclipse.swt.widgets.Composite.setResizeChildren(boolean) at
* org.eclipse.swt.widgets.Composite.WM_SIZE(int, int) at
* org.eclipse.swt.widgets.Control.windowProc(int, int, int, int) at
* org.eclipse.swt.widgets.Display.windowProc(int, int, int, int)
* <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.DefWindowProcW(int, int, int,
* int) at org.eclipse.swt.internal.win32.OS.DefWindowProc(int, int,
* int, int) at
* org.eclipse.swt.widgets.Scrollable.callWindowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Control.WM_WINDOWPOSCHANGED(int,
* int) at org.eclipse.swt.widgets.Control.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Display.windowProc(int, int, int,
* int) <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.EndDeferWindowPos(int) at
* org.eclipse.swt.widgets.Composite.resizeChildren(boolean,
* WINDOWPOS[]) at
* org.eclipse.swt.widgets.Composite.resizeChildren() at
* org.eclipse.swt.widgets.Composite.setResizeChildren(boolean) at
* org.eclipse.swt.widgets.Composite.WM_SIZE(int, int) at
* org.eclipse.swt.widgets.Canvas.WM_SIZE(int, int) at
* org.eclipse.swt.widgets.Decorations.WM_SIZE(int, int) at
* org.eclipse.swt.widgets.Control.windowProc(int, int, int, int) at
* org.eclipse.swt.widgets.Canvas.windowProc(int, int, int, int) at
* org.eclipse.swt.widgets.Decorations.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Shell.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Display.windowProc(int, int, int,
* int) <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.DefWindowProcW(int, int, int,
* int) at org.eclipse.swt.internal.win32.OS.DefWindowProc(int, int,
* int, int) at org.eclipse.swt.widgets.Shell.callWindowProc(int,
* int, int, int) at
* org.eclipse.swt.widgets.Control.WM_WINDOWPOSCHANGED(int, int) at
* org.eclipse.swt.widgets.Canvas.WM_WINDOWPOSCHANGED(int, int) at
* org.eclipse.swt.widgets.Control.windowProc(int, int, int, int) at
* org.eclipse.swt.widgets.Canvas.windowProc(int, int, int, int) at
* org.eclipse.swt.widgets.Decorations.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Shell.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Display.windowProc(int, int, int,
* int) <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.DefWindowProcW(int, int, int,
* int) at org.eclipse.swt.internal.win32.OS.DefWindowProc(int, int,
* int, int) at org.eclipse.swt.widgets.Shell.callWindowProc(int,
* int, int, int) at org.eclipse.swt.widgets.Control.windowProc(int,
* int, int, int) at org.eclipse.swt.widgets.Canvas.windowProc(int,
* int, int, int) at
* org.eclipse.swt.widgets.Decorations.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Shell.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Display.windowProc(int, int, int,
* int) <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.DefWindowProcW(int, int, int,
* int) at org.eclipse.swt.internal.win32.OS.DefWindowProc(int, int,
* int, int) at org.eclipse.swt.widgets.Shell.callWindowProc(int,
* int, int, int) at org.eclipse.swt.widgets.Control.windowProc(int,
* int, int, int) at org.eclipse.swt.widgets.Canvas.windowProc(int,
* int, int, int) at
* org.eclipse.swt.widgets.Decorations.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Shell.windowProc(int, int, int,
* int) at org.eclipse.swt.widgets.Display.windowProc(int, int, int,
* int) <Break in method call trace. Could be due to JIT compiler
* inlining of method.> at
* org.eclipse.swt.internal.win32.OS.DispatchMessageW(MSG) at
* org.eclipse.swt.internal.win32.OS.DispatchMessage(MSG) at
* org.eclipse.swt.widgets.Display.readAndDispatch() at
* ss.controller.GUI.runEventLoop()
*/
log.error("error in noHorizontalScroll", t);
}

View file

@ -9,53 +9,49 @@ import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.openaudible.Directories;
import org.openaudible.desktop.swt.gui.GUI;
import org.openaudible.desktop.swt.gui.MessageBoxFactory;
import org.openaudible.desktop.swt.util.shop.FontShop;
import org.openaudible.util.Console;
import java.io.File;
import java.io.IOException;
public class AppLoader implements Version {
public class AppLoader {
public final static Log logger = LogFactory.getLog(GUI.class);
private static final boolean useSleak = false;
static boolean guiBuilt = false;
private static boolean didStartup;
Display display;
Shell invisibleShell;
public AppLoader() {
this(new String[0]);
}
final Application application;
/**
* Load GUI and display a Splashscreen while loading
* Load GUI
*/
public AppLoader(String args[]) {
// application uses console for logging.
Console.instance.install();
if (didStartup == false) {
// System.out.println("LM: Startup...");
startupProcess(args);
}
String java = System.getProperty("java.version");
logger.info("Starting " + getAppName() + " build " + Version.appVersion + " for " + SWT.getPlatform() + " swt " + SWT.getVersion() + " jvm " + java);
// System.out.println("LM: syncloader.. "+Globals.WORKING_DIR);
/* Apply application name to Display */
Display.setAppName(getAppName());
display = new Display();
/* Shell should not be visible in the taskbar */
invisibleShell = new Shell(display, SWT.NONE);
/* Create the Working Directory if it does not yet exist */
try {
createWorkingDir();
} catch (Throwable th) {
th.printStackTrace();
String msg = "Unable to create or load required directories. Check that the drives are read/writable.\nError:" + th.getMessage();
MessageBoxFactory.showError(new Shell(SWT.NONE), "Error creating directories!", msg);
System.exit(1);
}
new FontShop(display);
GUI g = createApp(display);
g.startUp();
guiBuilt = true;
g.run();
application = new Application(display);
application.startUp();
application.run();
// Call exit to close any audio threads...
display.dispose();
Runtime.getRuntime().exit(0);
@ -63,26 +59,15 @@ public class AppLoader implements Version {
}
public static void main(String[] args) {
startupProcess(args);
new AppLoader();
new AppLoader(args);
}
/**
* Create the Home Directory
*/
private static void createWorkingDir(String path) throws IOException {
File dir = null;
private void createWorkingDir() throws IOException {
String homePath = null;
if (path != null && !path.equals("")) {
dir = new File(path);
if (dir.exists() && dir.isDirectory()) {
homePath = dir.getAbsolutePath();
return;
}
System.err.println("Invalid argument:" + path);
System.exit(0);
}
homePath = (GUI.isMac() ? System.getProperty("user.home") : System.getProperty("user.home"));
homePath = System.getProperty("user.home");
File hp = new File(homePath);
if (!hp.exists()) {
System.err.println("Bad path for home dir: " + hp.getAbsolutePath());
@ -94,82 +79,19 @@ public class AppLoader implements Version {
hp = new File(homePath);
}
File prefs = new File(hp, appName);
File prefs = new File(hp, Version.appName);
if (!prefs.isDirectory()) {
boolean ok = prefs.mkdirs();
if (!ok) {
throw new IOException("Unable to create preference directory:" + prefs.getAbsolutePath());
}
}
Directories.init(prefs, prefs);
Directories.init(prefs, prefs); // base and etc are in same dir..
}
/**
* Check if the given argument is a valid URI or URL
*
* @param arg The argument that has been passed to RSSOwl
* @return TRUE if the argument is valid
*/
private static boolean isValidArgument(String arg) {
File f = new File(arg);
return f.exists() && f.isDirectory();
}
/**
* Write OS specific DWOrds into System properties
*/
private static void setUpProperties() {
/* Mac: Disable the blue focus ring on most Widgets */
if (GUI.isMac())
System.setProperty("org.eclipse.swt.internal.carbon.noFocusRing", "true");
/* Mac: Use small fonts */
if (GUI.isMac())
System.setProperty("org.eclipse.swt.internal.carbon.smallFonts", "true");
}
/**
* Things to do before launching
*
* @param args Arguments
*/
public static void startupProcess(String[] args) {
try {
didStartup = true;
String java = System.getProperty("java.version");
logger.info("Starting " + getAppName() + " build " + Version.appVersion + " for " + SWT.getPlatform() + " swt " + SWT.getVersion() + " jvm " + java);
// checkNIC();
GUI.userArgs = null;
/* Inform MainController about argument if it is valid */
if (args.length > 0) {
if (isValidArgument(args[0]))
GUI.userArgs = args[0];
}
/* Create the Working Directory if it does not yet exist */
createWorkingDir(GUI.userArgs);
// System.out.println("LM: WD="+Globals.WORKING_DIR);
/* Setup OS specific properties (DWords) */
setUpProperties();
} catch (Throwable e) {
e.printStackTrace();
logger.error(e);
System.exit(1);
}
}
public static final String getAppName() {
return Version.appName;
}
public GUI createApp(Display d) {
return new Application(d);
}
}

View file

@ -76,7 +76,6 @@ public class Application extends GUI {
}
protected void shutDown() {
quit();
super.shutDown();

View file

@ -12,10 +12,7 @@ import org.eclipse.swt.widgets.Shell;
import org.openaudible.Audible;
import org.openaudible.AudibleAccountPrefs;
import org.openaudible.Directories;
import org.openaudible.audible.AudibleLoginError;
import org.openaudible.audible.AudibleScraper;
import org.openaudible.audible.ConnectionListener;
import org.openaudible.audible.ConnectionNotifier;
import org.openaudible.audible.*;
import org.openaudible.books.Book;
import org.openaudible.books.BookElement;
import org.openaudible.books.BookListener;
@ -33,6 +30,7 @@ import org.openaudible.desktop.swt.manager.views.StatusPanel;
import org.openaudible.feeds.pagebuilder.WebPage;
import org.openaudible.util.HTMLUtil;
import org.openaudible.util.Platform;
import org.openaudible.util.TimeToSeconds;
import org.openaudible.util.queues.IQueueJob;
import org.openaudible.util.queues.IQueueListener;
import org.openaudible.util.queues.ThreadedQueue;
@ -59,6 +57,7 @@ public class AudibleGUI implements BookListener, ConnectionListener {
public Prefs prefs = new Prefs();
final String appPrefsFileName = "settings.json";
private long totalDuration;
public AudibleGUI() {
assert (instance == null);
@ -101,7 +100,6 @@ public class AudibleGUI implements BookListener, ConnectionListener {
audible.convertQueue.addListener(queueListener);
ConnectionNotifier.instance.addListener(this);
@ -320,7 +318,7 @@ public class AudibleGUI implements BookListener, ConnectionListener {
public void refreshLibrary(final boolean quickRescan) {
final ProgressTask task = new ProgressTask("Refresh Library") {
final ProgressTask task = new ProgressTask("Audible Library Update...") {
public void run() {
try {
@ -335,10 +333,10 @@ public class AudibleGUI implements BookListener, ConnectionListener {
loggedIn = true;
} catch (AudibleLoginError e) {
MessageBoxFactory.showGeneral(null, 0, "Log in via web browser...", "Unable to connect right now.\n\nTry logging on to Audible from this web page and try again.");
MessageBoxFactory.showGeneral(null, 0, "Log in via web browser...", "Unable to connect right now.\n\nTry logging on to Audible from this web page and try again.\n\nIf this keeps ");
} catch (Exception e) {
LOG.info("Error connecting", e);
} catch (Throwable e) {
LOG.info("Error refreshing library", e);
if (!wasCanceled())
showError(e, "refreshing library");
} finally {
@ -361,7 +359,7 @@ public class AudibleGUI implements BookListener, ConnectionListener {
progressTask.setTask("Connecting...", "");
final AudibleScraper s = audible.getScraper(false);
if (s != null && !s.isLoggedIn()) {
if (browser!=null) {
if (browser != null) {
LOG.info("Setting cookies 1");
SWTAsync.block(new SWTAsync("connect") {
@ -380,9 +378,6 @@ public class AudibleGUI implements BookListener, ConnectionListener {
try {
s.home();
if (ConnectionNotifier.getInstance().isConnected()) {
s.clickLib();
}
if (ConnectionNotifier.getInstance().isConnected())
return s;
@ -415,7 +410,7 @@ public class AudibleGUI implements BookListener, ConnectionListener {
msg = "You have " + conv.size() + " book(s) to convert to MP3\n";
msg += "Would you like to start these job(s) now?";
LOG.info(msg+" autoConvert="+prefs.autoConvert);
LOG.info(msg + " autoConvert=" + prefs.autoConvert);
boolean ok = prefs.autoConvert;
if (!ok) ok = MessageBoxFactory.showGeneralYesNo(null, "Start jobs?", msg);
@ -473,8 +468,6 @@ public class AudibleGUI implements BookListener, ConnectionListener {
// has login credentials.
public boolean hasLogin() {
return true;
// return audible.hasLogin();
}
public boolean canViewInAudible() {
@ -490,15 +483,12 @@ public class AudibleGUI implements BookListener, ConnectionListener {
public boolean canViewInSystem() {
if (GUI.isLinux()) return false; // TODO: Fix for Linux
if (GUI.isLinux())
return false; // TODO: Fix for Linux. How to display a file in "desktop"
Book b = onlyOneSelected();
if (b != null) {
return audible.hasMP3(b);
}
return false;
}
@ -510,13 +500,18 @@ public class AudibleGUI implements BookListener, ConnectionListener {
String out = "";
switch (e) {
case Hours:
float hours = AudibleGUI.instance.getTotalDuration() / 3600.0f;
if (hours < 100)
return "" + Math.round(hours * 10) / 10.0;
else return "" + Math.round(hours);
case AAX_Files:
return "" + audible.aaxCount();
case Books:
return "" + audible.getBookCount();
case MP3_Files:
return "" + audible.mp3Count();
// case Connection: return ConnectionNotifier.getInstance().getStateString();
case To_Download:
return "" + getDownloadCount();
case To_Convert:
@ -695,12 +690,15 @@ 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;
for (Book b : audible.getBooks()) {
boolean m = audible.hasMP3(b);
if (!audible.hasAAX(b)) {
@ -709,9 +707,12 @@ public class AudibleGUI implements BookListener, ConnectionListener {
if (!m) c++;
}
seconds += TimeToSeconds.parseTimeStringToSeconds(b.getDuration());
}
downloadCount = d;
convertCount = c;
totalDuration = seconds;
}
@ -848,26 +849,53 @@ public class AudibleGUI implements BookListener, ConnectionListener {
// book may be null.
public String getTaskString(final Book b) {
String out = "";
if (b!=null)
{
if (b != null) {
if (hasMP3(b))
return "Converted to MP3";
if (hasAAX(b))
{
if (audible.convertQueue.isQueued(b)) return "In convert queue";
if (audible.convertQueue.inJob(b)) return "Converting...";
if (hasAAX(b)) {
if (audible.convertQueue.isQueued(b))
return "In convert queue";
if (audible.convertQueue.inJob(b))
return "Converting...";
if (!audible.convertQueue.canAdd(b))
return "Unable to convert"; // ?
return "Ready to convert to MP3";
}
if (audible.downloadQueue.isQueued(b)) return "In download queue";
if (audible.downloadQueue.inJob(b)) return "Downloading...";
if (ConnectionNotifier.getInstance().isConnected())
return "Ready to download";
return "Not downloaded";
if (audible.downloadQueue.isQueued(b))
return "In download queue";
if (audible.downloadQueue.inJob(b))
return "Downloading...";
if (!audible.downloadQueue.canAdd(b))
return "Unable to download";
if (ConnectionNotifier.getInstance().isConnected())
return "Ready to download";
return "Not downloaded";
}
return out;
}
// total book time, in seconds.
public long getTotalDuration() {
return totalDuration;
}
public void test1() {
if (KindleScraper.instance == null) {
new KindleScraper(audible.getAccount());
}
try {
KindleScraper.instance.test();
} catch (Throwable th) {
LOG.debug("Error", th);
}
}
class BookQueueListener implements IQueueListener<Book> {
@Override
@ -903,11 +931,11 @@ public class AudibleGUI implements BookListener, ConnectionListener {
if (queue == audible.downloadQueue)
msg = "Downloading ";
else
msg ="Converting ";
msg = "Converting ";
assert(msg.length()>0);
assert (msg.length() > 0);
if (subtask!=null) {
if (subtask != null) {
msg += subtask;
}
bookNotifier.bookProgress(book, msg);
@ -937,6 +965,7 @@ public class AudibleGUI implements BookListener, ConnectionListener {
}
}
}
public void load() throws IOException {
Audible.instance.load();
@ -1013,8 +1042,7 @@ public class AudibleGUI implements BookListener, ConnectionListener {
}
// SWT Shell accessor.
private Shell getShell()
{
private Shell getShell() {
return GUI.shell;
}
@ -1044,16 +1072,14 @@ public class AudibleGUI implements BookListener, ConnectionListener {
String files[] = dialog.getFileNames();
System.out.println(path);
if (files!= null && files.length>0)
{
if (files != null && files.length > 0) {
File test = new File(path);
File dir = test.getParentFile();
ArrayList <File>aaxFiles= new ArrayList();
for (String s:files)
{
ArrayList<File> aaxFiles = new ArrayList();
for (String s : files) {
File f = new File(dir, s);
assert(f.exists());
assert (f.exists());
aaxFiles.add(f);
}
@ -1067,8 +1093,7 @@ public class AudibleGUI implements BookListener, ConnectionListener {
}
public void importBooks(final List<File> aaxFiles) {
if (SWTAsync.inDisplayThread())
{
if (SWTAsync.inDisplayThread()) {
// hack. Need to release current GUI thread and start progress in new thread.
new Thread(() -> importBooks(aaxFiles)).start();
return;
@ -1080,8 +1105,7 @@ public class AudibleGUI implements BookListener, ConnectionListener {
public void run() {
try {
for (File f:aaxFiles)
{
for (File f : aaxFiles) {
setTask("Importing", f.getName());
Audible.instance.importAAX(f, this);
}
@ -1109,7 +1133,7 @@ public class AudibleGUI implements BookListener, ConnectionListener {
File f = new File(path);
audible.export(f);
if (f.exists())
LOG.info("exported books to: "+f.getAbsolutePath());
LOG.info("exported books to: " + f.getAbsolutePath());
}
} catch (Exception e) {
@ -1130,7 +1154,7 @@ public class AudibleGUI implements BookListener, ConnectionListener {
File f = new File(path);
audible.export(f);
if (f.exists())
LOG.info("exported books to: "+f.getAbsolutePath());
LOG.info("exported books to: " + f.getAbsolutePath());
}
} catch (Exception e) {

View file

@ -3,10 +3,9 @@ package org.openaudible.desktop.swt.manager;
public interface Version {
String appName = "OpenAudible";
String appVersion = "1.0";
String appVersion = "1.1";
boolean appDebug = false;
String appLink = "http://openaudible.org";
String versionLink = "http://openaudible.org/swt_version.json";
String client = "org.openaudible.desktop.swt";
}

View file

@ -6,6 +6,7 @@ import org.apache.commons.logging.LogFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Shell;
import org.openaudible.desktop.swt.gui.MessageBoxFactory;
import org.openaudible.desktop.swt.manager.menu.CommandCenter;
import org.openaudible.util.HTTPGet;
import org.openaudible.util.Platform;
@ -16,7 +17,6 @@ public enum VersionCheck {
private static final Log LOG = LogFactory.getLog(VersionCheck.class);
// if verbose return state regardless.
// if !verbose, only alert when new version is available.
public void checkForUpdate(Shell shell, boolean verbose) {
@ -27,17 +27,14 @@ public enum VersionCheck {
int diff = obj.get("diff").getAsInt();
if (diff < 0) {
MessageBoxFactory.showGeneral(shell, SWT.ICON_INFORMATION, title, msg);
if (obj.has("site"))
{
if (obj.has("site")) {
String url = obj.get("site").getAsString();
AudibleGUI.instance.browse(url);
}
// TODO: Add buttons: go to web site (openaudible.org) or download update (go to mac,win, or linux download url)
} else
{
if (verbose)
{
} else {
if (verbose) {
MessageBoxFactory.showGeneral(shell, SWT.ICON_INFORMATION, title, msg);
}
}
@ -58,17 +55,16 @@ public enum VersionCheck {
/**
* Compares two version strings.
*
* <p>
* Use this instead of String.compareTo() for a non-lexicographical
* comparison that works for version strings. e.g. "1.10".compareTo("1.6").
*
* @note It does not work if "1.10" is supposed to be equal to "1.10.0".
*
* @param str1 a string of ordinal numbers separated by decimal points.
* @param str2 a string of ordinal numbers separated by decimal points.
* @return The result is a negative integer if str1 is _numerically_ less than str2.
* The result is a positive integer if str1 is _numerically_ greater than str2.
* The result is zero if the strings are _numerically_ equal.
* The result is a positive integer if str1 is _numerically_ greater than str2.
* The result is zero if the strings are _numerically_ equal.
* @note It does not work if "1.10" is supposed to be equal to "1.10.0".
*/
public static int versionCompare(String str1, String str2) {
String[] vals1 = str1.split("\\.");
@ -88,9 +84,8 @@ public enum VersionCheck {
return Integer.signum(vals1.length - vals2.length);
}
// return "" if current version.
public JsonObject versionCheck() {
JsonObject obj =null;
JsonObject obj = null;
try {
obj = getVersion();
@ -99,29 +94,44 @@ public enum VersionCheck {
String releaseVersion = obj.get("version").getAsString();
int diff = versionCompare(Version.appVersion, releaseVersion);
obj.addProperty("diff", ""+diff);
obj.addProperty("diff", "" + diff);
String msg, title;
if (diff<0) {
if (diff < 0) {
title = "Update Available";
msg = "An update is available!\nYour version: " + Version.appVersion + "\nRelease Version:" + releaseVersion;
if (obj.has("required") && obj.get("required").getAsBoolean())
{
msg += "\nThis upgrade is required. Old versions no longer supported.";
CommandCenter.instance.expiredApp = true;
}
}else if (diff>0) {
} else if (diff > 0) {
title = "Using Pre-release";
msg = "You appear to be using a pre-release version\nYour version: " + Version.appVersion + "\nLatest Version:" + releaseVersion;
}
else
{
} else {
title = "No update at this time";
msg = "Using the latest release version";
msg = "Using the latest release version.";
// allow a news field
}
if (obj.has("news"))
{
msg +="\n"+obj.get("news").getAsString();
}
if (obj.has("kill"))
{
msg +="\n"+obj.get("kill").getAsString();
CommandCenter.instance.expiredApp = true;
}
obj.addProperty("msg", msg);
obj.addProperty("title", title);
} catch (IOException e) {
if (obj==null)
obj = new JsonObject();
if (obj == null)
obj = new JsonObject();
obj.addProperty("msg", "Error checking for latest version.\nError message: " + e.getMessage());
obj.addProperty("title", "Version check failed");
}

View file

@ -8,6 +8,7 @@ import org.eclipse.swt.widgets.*;
import org.openaudible.desktop.swt.i8n.ITranslatable;
import org.openaudible.desktop.swt.i8n.Translate;
import org.openaudible.desktop.swt.manager.Application;
import org.openaudible.desktop.swt.manager.Version;
import org.openaudible.desktop.swt.util.shop.PaintShop;
import java.util.ArrayList;
@ -28,10 +29,11 @@ public class AppMenu implements ITranslatable, SelectionListener {
private Menu editMenu;
private Menu controlMenu;
private Menu aboutMenu;
private final Command[] actionCommands = {Command.ViewInAudible, Command.Show_MP3, Command.Play, Command.Download,
Command.Convert, Command.Refresh_Book_Info};
private final Command[] appCommands = {Command.Connect, Command.Quick_Refresh, Command.Rescan_Library, Command.Download_All, Command.Convert_All,
Command.MenuSeparator, Command.Browser}; // , Command.MenuSeparator, Command.Logout};
private final Command[] controlCommands = {Command.Connect, Command.Quick_Refresh, Command.Rescan_Library, Command.Download_All, Command.Convert_All,
Command.MenuSeparator, Command.Browser}; // , Command.MenuSeparator, Command.Logout};
private final Command[] aboutCommands = {Command.Help, Command.AppWebPage, Command.Check_For_Update, Command.About};
@ -112,8 +114,7 @@ public class AppMenu implements ITranslatable, SelectionListener {
MenuItem item = installSystemMenu(cmd);
if (item != null)
return item; // is a special Mac OS menu
if (cmd==Command.MenuSeparator)
{
if (cmd == Command.MenuSeparator) {
return newSeparator(parent);
}
@ -190,9 +191,12 @@ public class AppMenu implements ITranslatable, SelectionListener {
newMItem(editMenu, Command.Preferences);
controlMenu = newMenu("Controls");
for (Command c : appCommands) {
for (Command c : controlCommands) {
newMItem(controlMenu, c);
}
if (Version.appDebug)
newMItem(controlMenu, Command.Test1);
actionMenu = newMenu("Actions");
for (Command c : actionCommands) {
@ -259,7 +263,7 @@ public class AppMenu implements ITranslatable, SelectionListener {
// public void initMnemonics() {
private void initMnemonics(MenuItem items[]) {
/* Store chars that have been used as mnemonic */
/* Store chars that have been used as mnemonic */
Vector chars = new Vector();
/* For each MenuItem */
for (MenuItem item : items) {
@ -268,19 +272,19 @@ public class AppMenu implements ITranslatable, SelectionListener {
name = name.replaceAll("&", "");
/* For each char in the name */
for (int b = 0; b < name.length(); b++) {
/* Check if char is available and no whitespace */
/* Check if char is available and no whitespace */
if (name.substring(b, b + 1) != null && !name.substring(b, b + 1).equals(" ")) {
/* Check if char has been used as mnemonic before */
/* Check if char has been used as mnemonic before */
if (!chars.contains(name.substring(b, b + 1).toLowerCase())) {
/* Set mnemonic */
/* Set mnemonic */
item.setText(name.substring(0, b) + "&" + name.substring(b, name.length()));
/* Add char as used mnemonic */
/* Add char as used mnemonic */
chars.add(name.substring(b, b + 1).toLowerCase());
break;
}
}
}
/* Also check MenuItems ob possible Sub-Menus */
/* Also check MenuItems ob possible Sub-Menus */
if (item.getMenu() != null)
initMnemonics(item.getMenu().getItems());
}

View file

@ -29,7 +29,9 @@ public enum Command {
Browser,
Check_For_Update,
AppWebPage,
Logout, MenuSeparator;
Logout,
Test1,
MenuSeparator;
public char getKeyEquiv() {

View file

@ -29,6 +29,7 @@ import org.openaudible.desktop.swt.util.shop.WidgetShop;
public class CommandCenter {
public final static Log logger = LogFactory.getLog(CommandCenter.class);
public static CommandCenter instance;
public boolean expiredApp =false;
boolean confirmQuit = true;
boolean confirmSave = true;
private Clipboard cb;
@ -254,6 +255,9 @@ public class CommandCenter {
case Convert_All:
AudibleGUI.instance.convertAll();
break;
case Test1:
AudibleGUI.instance.test1();
break;
case Console:
LogWindow.show();
break;
@ -284,6 +288,8 @@ public class CommandCenter {
}
public boolean getEnabled(Command c) {
if (expiredApp)
return c==Command.Quit || c==Command.Check_For_Update || c==Command.About;
switch (c) {
case Convert:
return AudibleGUI.instance.canConvert();
@ -317,6 +323,7 @@ public class CommandCenter {
case Browser:
case AppWebPage:
case Import_AAX_Files:
case Test1:
return true;
case Copy:
case Cut:

View file

@ -52,12 +52,12 @@ public class AboutDialog extends Window implements Version, Listener {
c.newImage(splashImage);
c.addListener(SWT.MouseDown, this);
String compileDate = ManifestReader.instance.getBuildVersion(); // from jar's manifest, if available
String build = "Build: " + Version.appName+" " + Version.appVersion;
String build = "Build: " + Version.appName + " " + Version.appVersion;
// c.newLabel(Version.appName).setFont(FontShop.dialogFontBold());
c.newLabel(build.trim()).setFont(FontShop.dialogFont());
if (!compileDate.isEmpty())
c.newLabel("Released: "+ compileDate.trim()).setFont(FontShop.dialogFont());
c.newLabel("Released: " + compileDate.trim()).setFont(FontShop.dialogFont());
c.newLabel("");
c.newLabel("An open source project").setFont(FontShop.dialogFont());

View file

@ -17,6 +17,7 @@ import org.openaudible.Directories;
import org.openaudible.audible.AudibleClient;
import org.openaudible.desktop.swt.gui.MessageBoxFactory;
import org.openaudible.desktop.swt.gui.SWTAsync;
import org.openaudible.util.Platform;
import java.io.File;
import java.net.URI;
@ -25,7 +26,6 @@ import java.util.Collection;
public class AudibleBrowser {
public final static Log logger = LogFactory.getLog(AudibleBrowser.class);
// static ResourceBundle resourceBundle = ResourceBundle.getBundle("examples_browser");
int index;
boolean busy;
Image icon = null;
@ -45,6 +45,8 @@ public class AudibleBrowser {
public AudibleBrowser(Composite parent, String url) {
this.parent = parent;
try {
if (Platform.isWindows())
silenceWindowsExplorer();
browser = new Browser(parent, SWT.BORDER);
browser.addTitleListener(event -> getShell().setText(event.title));
Object t = browser.getWebBrowser();
@ -72,6 +74,22 @@ public class AudibleBrowser {
}
// Silence Windows SWT.browser widget from making awful clicks.
// For windows 32 and 64 bit SWT applications.
// Uses reflection to call OS.CoInternetSetFeatureEnabled(OS.FEATURE_DISABLE_NAVIGATION_SOUNDS, OS.SET_FEATURE_ON_PROCESS, true);
// Without importing platform specific
// #import org.eclipse.swt.internal.win32.OS
private void silenceWindowsExplorer() {
try {
Class<?> c = Class.forName("org.eclipse.swt.internal.win32.OS");
java.lang.reflect.Method method = c.getDeclaredMethod("CoInternetSetFeatureEnabled", Integer.TYPE, Integer.TYPE, Boolean.TYPE);
method.invoke(null, new Object[]{21, 2, true});
} catch (Throwable th) {
// Might fail.. but probably will never do harm.
th.printStackTrace();
}
}
/**
* Gets a string from the resource bundle. We don't want to crash because of a missing String. Returns the key if not found.
@ -349,11 +367,11 @@ public class AudibleBrowser {
SWTAsync.block(new SWTAsync("getCookies") {
@Override
public void task() {
String listCookies="document.cookie.split( ';' ).map( function( x ) { return x.trim().split( '=' ); } )";
String pageInfo="";// document.cookie.split( ';' ).map( function( x ) { return x.trim().split( '=' ); } )";
String listCookies = "document.cookie.split( ';' ).map( function( x ) { return x.trim().split( '=' ); } )";
String pageInfo = "";// document.cookie.split( ';' ).map( function( x ) { return x.trim().split( '=' ); } )";
browser.execute("cookieCallback("+listCookies+");");
browser.execute("pageInfoCallback("+pageInfo+");");
browser.execute("cookieCallback(" + listCookies + ");");
browser.execute("pageInfoCallback(" + pageInfo + ");");
}

View file

@ -141,10 +141,10 @@ public class BookInfoPanel extends GridComposite implements BookListener {
return c;
}
private void updateTask(Book b) {
String t = "";
if (curBook!=null)
{
if (curBook != null) {
}
@ -248,10 +248,11 @@ public class BookInfoPanel extends GridComposite implements BookListener {
private void refresh() {
refresh(curBook);
}
AtomicInteger cache= new AtomicInteger();
AtomicInteger cache = new AtomicInteger();
private void refresh(final Book b) {
if (cache.getAndIncrement()==0) {
if (cache.getAndIncrement() == 0) {
SWTAsync.run(new SWTAsync("refresh") {
@Override
public void task() {
@ -303,8 +304,7 @@ public class BookInfoPanel extends GridComposite implements BookListener {
@Override
public void bookProgress(final Book book, final String msg) {
if (book.equals(curBook))
{
if (book.equals(curBook)) {
SWTAsync.run(new SWTAsync("bookProgress") {
@Override
public void task() {

View file

@ -91,10 +91,10 @@ public class BookTable extends EnumTable<Book, BookTableColumn> implements BookL
return hasNothing;
}
AtomicInteger cache=new AtomicInteger();
AtomicInteger cache = new AtomicInteger();
public void populate() {
if (cache.getAndIncrement()==0) {
if (cache.getAndIncrement() == 0) {
SWTAsync.run(new SWTAsync("populate_table") {
@Override
public void task() {

View file

@ -10,16 +10,14 @@ import java.io.File;
import java.util.ArrayList;
public class FileDropTarget extends DropTargetAdapter
{
public class FileDropTarget extends DropTargetAdapter {
public final FileTransfer fileTransfer = FileTransfer.getInstance();
public Transfer[] types = new Transfer[] {fileTransfer};
public Transfer[] types = new Transfer[]{fileTransfer};
static FileDropTarget instance = new FileDropTarget();
private static final Log LOG = LogFactory.getLog(FileDropTarget.class);
public static void attach(Control control)
{
DropTarget target = new DropTarget(control, DND.DROP_COPY );
public static void attach(Control control) {
DropTarget target = new DropTarget(control, DND.DROP_COPY);
target.setTransfer(new Transfer[]{instance.fileTransfer});
target.addDropListener(instance);
}
@ -27,8 +25,8 @@ public class FileDropTarget extends DropTargetAdapter
@Override
public void drop(DropTargetEvent event) {
System.out.println("drop:" + event+" data="+event.data);
ArrayList <File> aaxFiles = new ArrayList<File>();
System.out.println("drop:" + event + " data=" + event.data);
ArrayList<File> aaxFiles = new ArrayList<File>();
if (fileTransfer.isSupportedType(event.currentDataType)) {
@ -36,33 +34,29 @@ public class FileDropTarget extends DropTargetAdapter
//list out selected file
String[] files = (String[]) event.data;
for (String name:files) {
for (String name : files) {
String[] split = name.split("\\.");
String ext = split[split.length - 1];
File file = new File(name);
if (file.getName().toLowerCase().endsWith(".aax"))
{
if (file.getName().toLowerCase().endsWith(".aax")) {
aaxFiles.add(file);
}
System.out.println("file:"+name+" exists="+file.exists());
System.out.println("file:" + name + " exists=" + file.exists());
}//end for loop event.detail = DND.DROP_COPY;
}
if (aaxFiles.size()>0)
{
if (aaxFiles.size() > 0) {
AudibleGUI.instance.importBooks(aaxFiles);
}
}
@Override
public void dragEnter(DropTargetEvent event){
public void dragEnter(DropTargetEvent event) {
event.detail = DND.DROP_COPY;
}
}

View file

@ -9,6 +9,7 @@ import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.openaudible.desktop.swt.util.shop.PaintShop;
import org.openaudible.util.Console;
import org.openaudible.util.Platform;
@ -16,6 +17,8 @@ import org.openaudible.util.Platform;
public class LogWindow {
public static LogWindow instance = null;
private final Text commandLine;
boolean cmdLine = true;
Shell shell;
ConsoleView textPanel;
private static final Log LOG = LogFactory.getLog(LogWindow.class);
@ -49,7 +52,6 @@ public class LogWindow {
}
public LogWindow(String title) {
shell = new Shell(SWT.BORDER | SWT.TITLE | SWT.CLOSE | SWT.RESIZE);
if (!Platform.isMac()) {
@ -67,6 +69,10 @@ public class LogWindow {
data.horizontalSpan = numCols;
textView.setLayoutData(data);
commandLine = new Text(shell, SWT.SINGLE | SWT.BORDER);
data = new GridData(GridData.VERTICAL_ALIGN_END);
data.horizontalSpan = numCols;
commandLine.setLayoutData(data);
shell.addListener(SWT.Close, event -> close());
@ -79,7 +85,6 @@ public class LogWindow {
}
public void addListener(int id, Listener l) {
shell.addListener(id, l);
}

View file

@ -25,7 +25,9 @@ import org.openaudible.util.Platform;
*/
public class PasswordDialog extends TitleAreaDialog implements KeyListener {
/** Min. width of the dialog in DLUs */
/**
* Min. width of the dialog in DLUs
*/
private static final int dialogMinWidth = 320;
private String dialogMessage;
@ -36,7 +38,6 @@ public class PasswordDialog extends TitleAreaDialog implements KeyListener {
private String title;
// private Text username;
/**
@ -46,7 +47,6 @@ public class PasswordDialog extends TitleAreaDialog implements KeyListener {
* <p>
* Note that the <code>open</code> method blocks for input dialogs.
* </p>
*
*/
public PasswordDialog(Shell parentShell, String dialogTitle, String dialogMessage, String user,
String pass) {
@ -123,7 +123,7 @@ public class PasswordDialog extends TitleAreaDialog implements KeyListener {
((GridLayout) parent.getLayout()).marginHeight = 10;
((GridLayout) parent.getLayout()).marginWidth = 10;
/** Create Buttons */
/** Create Buttons */
createButton(parent, IDialogConstants.OK_ID, GUI.i18n.getTranslation("BUTTON_OK"), true)
.setFont(FontShop.dialogFont());
createButton(parent, IDialogConstants.CANCEL_ID, GUI.i18n.getTranslation("BUTTON_CANCEL"),
@ -255,8 +255,7 @@ public class PasswordDialog extends TitleAreaDialog implements KeyListener {
* Set the layout data of the button to a GridData with appropriate widths
* This method was slightly modified so that it is not setting a heightHint.
*
* @param button
* The button to layout
* @param button The button to layout
*/
@Override
protected void setButtonLayoutData(Button button) {
@ -270,12 +269,11 @@ public class PasswordDialog extends TitleAreaDialog implements KeyListener {
public static PasswordDialog getPasswordForSite(Shell shell, String user, String pass) {
PasswordDialog gp = new PasswordDialog(shell, GUI.i18n.getTranslation("PASSWORD_TITLE"),
GUI.i18n.getTranslation("PASSWORD_MESSAGE"),
user, pass);
user, pass);
int status = gp.open();
if (status == Window.OK)
{
if (status == Window.OK) {
return gp;
}
return null;

View file

@ -50,8 +50,7 @@ public class Preferences extends Dialog {
try {
Preferences p = instance = new Preferences(s);
int result = instance.open();
if (result==0)
{
if (result == 0) {
try {
AudibleGUI.instance.save();
} catch (IOException e) {
@ -130,7 +129,7 @@ public class Preferences extends Dialog {
final Text text = GridComposite.newTextPair(group, d.displayName());
text.setData(d);
text.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL | GridData.FILL_HORIZONTAL));
text.setEditable(false);
Button b = c.newButton(group, "Set");
b.addSelectionListener(new SelectionAdapter() {
@Override
@ -194,8 +193,7 @@ public class Preferences extends Dialog {
GridData gd;
region = GridComposite.newCombo(group, "Region");
for (AudibleRegion r:AudibleRegion.values())
{
for (AudibleRegion r : AudibleRegion.values()) {
region.add(r.displayName());
}
gd = new GridData();

View file

@ -56,7 +56,7 @@ public class StatusPanel extends GridComposite implements BookListener, Connecti
_update();
}
AtomicInteger cache=new AtomicInteger(); // caches gui drawing.
AtomicInteger cache = new AtomicInteger(); // caches gui drawing.
private void _update() {
boolean update = cache.getAndIncrement() == 0;
@ -113,11 +113,11 @@ public class StatusPanel extends GridComposite implements BookListener, Connecti
}
public enum Status {
Connected, Books, AAX_Files, MP3_Files, To_Download, To_Convert, Downloading, Converting; //Connection,
Connected, Books, Hours, AAX_Files, MP3_Files, To_Download, To_Convert, Downloading, Converting; //Connection,
public String displayName() {
return name().replace('_', ' ');
}
} // TODO: Translations
public boolean display() {
switch (this) {
@ -127,6 +127,7 @@ public class StatusPanel extends GridComposite implements BookListener, Connecti
case Converting:
case MP3_Files:
case Connected:
case Hours:
return true;
case AAX_Files:

View file

@ -26,10 +26,12 @@ public class SummaryPanel implements BookListener {
summary.setLayoutData(gd);
BookNotifier.getInstance().addListener(this);
}
AtomicInteger cache = new AtomicInteger();
@Override
public void booksSelected(List<Book> list) {
if (cache.getAndIncrement()>0) return;
if (cache.getAndIncrement() > 0) return;
SWTAsync.run(new SWTAsync("update") {
@Override
public void task() {

View file

@ -45,14 +45,11 @@ public class DownloadJob implements IQueueJob {
assert (!destFile.exists());
}
public void download() throws IOException {
String cust_id = b.get(BookElement.cust_id);
String user_id = b.get(BookElement.user_id);
if (cust_id.length() == 0)
throw new IOException("cust_id required");
if (user_id.length() == 0)
throw new IOException("user_id required");
String awtype = "AAX";
@ -60,11 +57,21 @@ public class DownloadJob implements IQueueJob {
if (codec.isEmpty())
codec = "LC_64_22050_stereo";
/*
http://cds.audible.com/download?
user_id=xxx[about 60 chars] &
product_id=BK_RAND_003188&
codec=LC_32_22050_Mono&
awtype=AAX&
cust_id= [ same as user-id currently? ]
*/
String url = "http://cds.audible.com/download";
url += "?user_id=" + user_id;
url += "&product_id=" + b.getProduct_id();
url += "&codec=" + codec;
url += "&awtype=" + awtype;
url += "?user_id=" + user_id; // crypt
url += "&product_id=" + b.getProduct_id(); // BK_ABCD_123456
url += "&codec=" + codec; // LC_32_22050_Mono
url += "&awtype=" + awtype; // AAX
url += "&cust_id=" + cust_id;
LOG.info("Download book: " + b + " url=" + url);
@ -167,12 +174,11 @@ public class DownloadJob implements IQueueJob {
double seconds = (System.currentTimeMillis() - startTime) / 1000.0;
double bps = total / seconds;
String rate = Util.instance.byteCountToString((long) bps) + "/sec";
String t = "Downloading "+b;
String s = Util.instance.byteCountToString(total) +" at "+rate;
String t = "Downloading " + b;
String s = Util.instance.byteCountToString(total) + " at " + rate;
if (task!=null)
{
task.setTask(t,s);
if (task != null) {
task.setTask(t, s);
}
// LOG.info(task+" "+ s);

View file

@ -4,6 +4,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openaudible.Audible;
import org.openaudible.books.Book;
import org.openaudible.books.BookElement;
import org.openaudible.util.queues.IQueueJob;
import org.openaudible.util.queues.JobProgress;
import org.openaudible.util.queues.ThreadedQueue;
@ -27,16 +28,19 @@ public class DownloadQueue extends ThreadedQueue<Book> {
return aaxDownloader;
}
public boolean canAdd(Book e) {
return super.canAdd(e) && !Audible.instance.getAAXFileDest(e).exists();
public boolean canAdd(Book b) {
assert(b.has(BookElement.user_id));
assert(b.has(BookElement.product_id));
if (!super.canAdd(b)) return false;
if (!b.has(BookElement.user_id)) return false;
if (!b.has(BookElement.product_id)) return false;
if (Audible.instance.hasAAX(b)) return false;
return true;
}
@Override
public String toString()
{
public String toString() {
return "DownloadQueue";
}
}

View file

@ -160,13 +160,11 @@ public class WebPage {
String json = gson.toJson(list);
try ( FileWriter writer = new FileWriter(new File(webDir, "books.json")))
{
try (FileWriter writer = new FileWriter(new File(webDir, "books.json"))) {
writer.write(json);
}
try (FileWriter writer = new FileWriter(new File(webDir, "books.js")))
{
try (FileWriter writer = new FileWriter(new File(webDir, "books.js"))) {
writer.write("window.myBooks=");
writer.write(json);
writer.write(";");
@ -174,7 +172,7 @@ public class WebPage {
// add basic html pages from webapp directory: src/main/webapp which is at the root dir in installed app.
File templateDir = Directories.getWebTemplateDirectory();
assert(templateDir.exists());
assert (templateDir.exists());
FileUtils.copyDirectory(templateDir, webDir);
}

View file

@ -5,10 +5,20 @@ package org.openaudible.progress;
public interface IProgressTask {
void setTask(final String task, final String subtask);
default void setTask(final String task) { setTask(task,null); }
default void setTask(final String task) {
setTask(task, null);
}
default void setSubTask(final String subtask) { setTask(null, subtask); }
default void setSubTask(final String subtask) {
setTask(null, subtask);
}
default boolean wasCanceled() { return false; }
default boolean wasCanceled() {
return false;
}
default void throwCanceled() throws Exception {
if (wasCanceled()) throw new Exception("Operation canceled");
}
}

View file

@ -10,42 +10,36 @@ import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
public class Console extends ConsoleHandler
{
public interface ILogRecordPublisher
{
public class Console extends ConsoleHandler {
public interface ILogRecordPublisher {
void publish(LogRecord l);
}
public final static Console instance = new Console();
boolean installed = false;
public void install()
{
assert(!installed);
installed=true;
public void install() {
assert (!installed);
installed = true;
Logger.getLogger("").addHandler(this);
// LogFactory.getFactory().setAttribute("");
// captureStdMessages();
}
public void uninstall()
{
assert(installed);
public void uninstall() {
assert (installed);
installed=false;
installed = false;
Logger.getLogger("").removeHandler(this);
}
public Console()
{
public Console() {
System.currentTimeMillis();
}
public void setListener(ILogRecordPublisher l)
{
listener=l;
public void setListener(ILogRecordPublisher l) {
listener = l;
}
ILogRecordPublisher listener;
@ -54,37 +48,33 @@ public class Console extends ConsoleHandler
final LinkedList<LogRecord> history = new LinkedList<>();
public final List<LogRecord> getHistory()
{
synchronized(history)
{
public final List<LogRecord> getHistory() {
synchronized (history) {
return Collections.unmodifiableList(history);
}
}
/**
* Publish a <tt>LogRecord</tt>.
* <p>
* The logging request was made initially to a <tt>Logger</tt> object,
* which initialized the <tt>LogRecord</tt> and forwarded it here.
* <p>
* @param record description of the log event. A null record is
* silently ignored and is not published
*
* @param record description of the log event. A null record is
* silently ignored and is not published
*/
@Override
public void publish(LogRecord record) {
super.publish(record);
synchronized(history)
{
synchronized (history) {
history.add(record);
if (history.size()>maxHistory)
if (history.size() > maxHistory)
history.removeFirst();
}
if (listener!=null)
{
if (listener != null) {
listener.publish(record);
}
}
@ -100,17 +90,15 @@ public class Console extends ConsoleHandler
}
class ConsoleOutputStream extends ByteArrayOutputStream
{
ConsoleOutputStream(int c)
{
class ConsoleOutputStream extends ByteArrayOutputStream {
ConsoleOutputStream(int c) {
color = c;
}
final int color;
@Override
public void flush()
{
public void flush() {
String message = toString().trim();
if (message.length() == 0) return;
takeLine(message);
@ -125,14 +113,11 @@ public class Console extends ConsoleHandler
private void takeLine(String line) {
Level level = Level.INFO; // (color==0) ? Level.INFO:Level.WARNING;
LogRecord r = new LogRecord(level, line);
if (listener!=null)
if (listener != null)
listener.publish(r);
}
}
}

View file

@ -27,4 +27,5 @@ public abstract class EventNotifier<T> {
protected Object getLock() {
return listeners;
}
}

View file

@ -78,8 +78,8 @@ public class HTMLUtil {
Files.write(Paths.get(f.getAbsolutePath()), str.getBytes());
}
public static String findHidden(HtmlPage page, String n) {
List<HtmlElement> test = page.getDocumentElement().getElementsByAttribute("input", "name", n);
public static String findHidden(HtmlElement elem, String n) {
List<HtmlElement> test = elem.getElementsByAttribute("input", "name", n);
if (!test.isEmpty()) {
HtmlInput i = (HtmlInput) test.get(0);
String v = i.getValueAttribute();
@ -95,8 +95,10 @@ public class HTMLUtil {
try {
xml = p.asXml();
if (!what.contains("."))
what += ".html";
File x = new File(Directories.getTmpDir(), what + ".html");
File x = new File(Directories.getTmpDir(), what);
HTMLUtil.writeFile(x, xml);
// File h = new File(what + ".html");

View file

@ -9,7 +9,6 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.openaudible.desktop.swt.manager.Version;
import java.io.IOException;
@ -18,7 +17,7 @@ public enum HTTPGet {
public JsonObject getJSON(String url) throws IOException {
try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
HttpGet httpget = new HttpGet(Version.versionLink);
HttpGet httpget = new HttpGet(url);
try (CloseableHttpResponse httpResponse = httpclient.execute(httpget)) {
HttpEntity httpEntity = httpResponse.getEntity();

View file

@ -0,0 +1,47 @@
package org.openaudible.util;
public class TimeToSeconds {
// given: mm:ss or hh:mm:ss or hhh:mm:ss, return number of seconds.
// bad input throws NumberFormatException.
// bad includes: "", null, :50, 5:-4
public static long parseTime(String str) throws NumberFormatException {
if (str == null)
throw new NumberFormatException("parseTimeString null str");
if (str.isEmpty())
throw new NumberFormatException("parseTimeString empty str");
int h = 0;
int m, s;
String units[] = str.split(":");
switch (units.length) {
case 2:
// mm:ss
m = Integer.parseInt(units[0]);
s = Integer.parseInt(units[1]);
break;
case 3:
// hh:mm:ss
h = Integer.parseInt(units[0]);
m = Integer.parseInt(units[1]);
s = Integer.parseInt(units[2]);
break;
default:
throw new NumberFormatException("parseTimeString failed:" + str);
}
if (m < 0 || m > 60 || s < 0 || s > 60 || h < 0)
throw new NumberFormatException("parseTimeString range error:" + str);
return h * 3600 + m * 60 + s;
}
// given time string (hours:minutes:seconds, or mm:ss, return number of seconds.
public static long parseTimeStringToSeconds(String str) {
try {
return parseTime(str);
} catch (NumberFormatException nfe) {
return 0;
}
}
}

View file

@ -99,4 +99,46 @@ public enum Util {
return "" + k + "K";
}
public static String replaceAll(String haystack, String find, String replacement) {
while (haystack.contains(find))
haystack = haystack.replaceAll(find, replacement);
return haystack;
}
public static String escape(String s) throws Exception {
char bad[] = {'\n', '/', '#'};
for (char c : bad) {
if (s.indexOf(c) != -1)
throw new Exception("TODO: Fix");
}
return s;
}
public static String cleanString(String out) {
out = replaceAll(out, "\r", "\n");
out = replaceAll(out, " ", " ");
out = replaceAll(out, "\t\t", "\t");
out = replaceAll(out, " \n", "\n");
out = replaceAll(out, "\t\n", "\n");
out = replaceAll(out, "\n\n", "\n");
return out.trim();
}
public static int substringCount(String needle, String haystack) {
int lastIndex = 0;
int count = 0;
while (lastIndex != -1) {
lastIndex = haystack.indexOf(needle, lastIndex);
if (lastIndex != -1) {
count++;
lastIndex += needle.length();
}
}
return count;
}
}

View file

@ -2,5 +2,6 @@ package org.openaudible.util.queues;
public interface IQueueJob {
void processJob() throws Exception;
void quitJob();
}

View file

@ -8,7 +8,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
public class ThreadedQueue<E> implements IQueueListener<E> {
public abstract class ThreadedQueue<E> implements IQueueListener<E> {
private static final Log LOG = LogFactory.getLog(ThreadedQueue.class);
final int concurrentJobs; // number of jobs that can be run at once
@ -47,12 +47,11 @@ public class ThreadedQueue<E> implements IQueueListener<E> {
return queue.size();
}
// return null if not can't or shouldn't create.
public IQueueJob createJob(E e) {
assert (false); // This should be overridden
return null;
}
// return null if not can't or shouldn't create. Otherwise create a job.
public abstract IQueueJob createJob(E e);
// Can add a job?
// For default behavior, the same job can't already be running or in queue.
public boolean canAdd(E e) {
if (quit)
return false;
@ -139,7 +138,6 @@ public class ThreadedQueue<E> implements IQueueListener<E> {
}
@Override
public void jobProgress(ThreadedQueue<E> queue, IQueueJob job, E o, String task, String subtask) {
for (IQueueListener<E> i : getListeners()) {
@ -149,7 +147,6 @@ public class ThreadedQueue<E> implements IQueueListener<E> {
}
public void itemEnqueued(ThreadedQueue<E> queue, E item) {
for (IQueueListener<E> i : getListeners()) {
i.itemEnqueued(queue, item);
@ -171,7 +168,7 @@ public class ThreadedQueue<E> implements IQueueListener<E> {
public void jobStarted(ThreadedQueue<E> queue, IQueueJob job, E item) {
synchronized (jobs) {
jobs.add(job);
jobMap.put(item,job);
jobMap.put(item, job);
}
for (IQueueListener<E> i : getListeners()) {
i.jobStarted(queue, job, item);
@ -185,7 +182,7 @@ public class ThreadedQueue<E> implements IQueueListener<E> {
// this assumes one job per object at a time.
IQueueJob x = jobMap.remove(item);
assert(x==job);
assert (x == job);
}
for (IQueueListener<E> i : getListeners()) {

View file

@ -55,7 +55,7 @@ MESSAGE_BOX_TITLE_ERROR=Error
MESSAGE_REQUIRES_RESTART=Directory changes require a restart of the application.
MESSAGE_SET_CREDENTIALS=Set the audible name and password in the preferences.
BUTTON_NO=No
TITLE_REQUIRES_RESTART=Restart org.openaudible.desktop.Application
TITLE_REQUIRES_RESTART=Restart OpenAudible
TITLE_SET_CREDENTIALS=Credentials Required
BUTTON_YES=Yes
en=English

View file

@ -0,0 +1,38 @@
package org.openaudible.util;
import org.junit.Test;
import static org.junit.Assert.*;
public class TimeToSecondsTest {
@Test
public void parseTimeStringToSeconds() {
assertEquals(TimeToSeconds.parseTimeStringToSeconds("1:00"), 60);
assertEquals(TimeToSeconds.parseTimeStringToSeconds("00:55"), 55);
assertEquals(TimeToSeconds.parseTimeStringToSeconds("5:55"), 5 * 60 + 55);
assertEquals(TimeToSeconds.parseTimeStringToSeconds(""), 0);
assertEquals(TimeToSeconds.parseTimeStringToSeconds("6:01:05"), 6 * 3600 + 1*60 + 5);
}
@Test
public void parseTime() {
// make sure all these tests fail.
String fails[] = {null, "", "abc", ":::", "A:B:C", "1:2:3:4", "1:99", "1:99:05", ":50", "-4:32", "-99:-2:4", "2.2:30"};
for (String t: fails)
{
try {
long seconds = TimeToSeconds.parseTime(t);
assertFalse("FAIL: Expected failure:"+t+" got "+seconds, true);
} catch (NumberFormatException nfe)
{
assertNotNull(nfe);
assertTrue(nfe instanceof NumberFormatException);
// expected this nfe.
}
}
}
}