Improve login. Add ignore book feature. Bump version to 1.1.6
This commit is contained in:
parent
4d100cec3d
commit
2f89d5d6c6
12 changed files with 207 additions and 85 deletions
|
@ -59,6 +59,9 @@ public class Audible implements IQueueListener<Book> {
|
|||
private final HashMap<String, Book> books = new HashMap<>(); // Book.id(), Book
|
||||
// AudibleRegion region = AudibleRegion.US;
|
||||
|
||||
private final HashSet<String> ignoreSet = new HashSet<>(); // book ID's to ignore.
|
||||
|
||||
|
||||
public Audible() {
|
||||
|
||||
}
|
||||
|
@ -155,19 +158,61 @@ public class Audible implements IQueueListener<Book> {
|
|||
}
|
||||
|
||||
Book removeBook(Book b) {
|
||||
Book out = null;
|
||||
Book out;
|
||||
synchronized (books) {
|
||||
out = books.remove(b.getProduct_id());
|
||||
out = books.remove(b.id());
|
||||
}
|
||||
assert (out != null);
|
||||
return out;
|
||||
}
|
||||
|
||||
public void addToIgnoreSet(Collection<Book> books)
|
||||
{
|
||||
for (Book b:books) {
|
||||
removeBook(b);
|
||||
ignoreSet.add(b.id());
|
||||
}
|
||||
saveIgnoreSet();
|
||||
}
|
||||
|
||||
final static String ignoreSetFileName = "ignore.json";
|
||||
private void loadIgnoreSet() {
|
||||
try {
|
||||
Gson gson = new GsonBuilder().create();
|
||||
File prefsFile = Directories.META.getDir(ignoreSetFileName);
|
||||
|
||||
if (prefsFile.exists()) {
|
||||
String content = HTMLUtil.readFile(prefsFile);
|
||||
HashSet set = gson.fromJson(content, HashSet.class);
|
||||
if (set!=null) {
|
||||
ignoreSet.clear();
|
||||
ignoreSet.addAll(set);
|
||||
}
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
LOG.info("Error loadIgnoreSet", th);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void saveIgnoreSet() {
|
||||
if (!ignoreSet.isEmpty()) {
|
||||
Gson gson = new GsonBuilder().create();
|
||||
try {
|
||||
HTMLUtil.writeFile(Directories.META.getDir(ignoreSetFileName), gson.toJson(ignoreSet));
|
||||
}catch(Throwable th)
|
||||
{
|
||||
LOG.error("Error saving ignore list!",th);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected boolean ignoreBook(Book b) {
|
||||
String id = b.getProduct_id();
|
||||
String id = b.id();
|
||||
if (id.length() == 0)
|
||||
return true;
|
||||
return id.contains("FR_PRMO_");
|
||||
return ignoreSet.contains(b);
|
||||
}
|
||||
|
||||
public boolean ok(Book b) {
|
||||
|
@ -218,8 +263,7 @@ public class Audible implements IQueueListener<Book> {
|
|||
}
|
||||
updateFileCache();
|
||||
LookupKey.instance.load(Directories.BASE.getDir(keysFileName));
|
||||
|
||||
|
||||
loadIgnoreSet();
|
||||
}
|
||||
|
||||
public synchronized void save() throws IOException {
|
||||
|
@ -807,5 +851,8 @@ public class Audible implements IQueueListener<Book> {
|
|||
return book;
|
||||
}
|
||||
|
||||
public boolean isIgnoredBook(Book b) {
|
||||
return ignoreSet.contains(b.id());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,9 +3,11 @@ package org.openaudible;
|
|||
import java.security.InvalidParameterException;
|
||||
|
||||
public enum AudibleRegion {
|
||||
US, UK, DE, FR, AU, IT, JP, CA;
|
||||
US, UK, DE, FR, AU, /* IT, */ JP, CA;
|
||||
|
||||
// audible.de, audible.fr, audible.com.au, audible.it, audible.jp, audible.ca
|
||||
// italy doesn't seem to have a way to list all books.
|
||||
// all sites untested except US.
|
||||
|
||||
public String getBaseURL() {
|
||||
return "https://" + this.getBaseDomain();
|
||||
|
@ -23,8 +25,7 @@ public enum AudibleRegion {
|
|||
return "audible.fr";
|
||||
case AU:
|
||||
return "audible.com.au";
|
||||
case IT:
|
||||
return "audible.it";
|
||||
// case IT: return "audible.it";
|
||||
case JP:
|
||||
return "audible.co.jp";
|
||||
case CA:
|
||||
|
|
|
@ -14,7 +14,7 @@ import java.util.logging.Handler;
|
|||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
// WebClient to connect to audible.
|
||||
// WebClient to connect to audible.com site
|
||||
// Cookies are used to track logged in state and can be transfered from external web browser.
|
||||
public class AudibleClient extends WebClient {
|
||||
private static final Log LOG = LogFactory.getLog(AudibleClient.class);
|
||||
|
|
|
@ -120,7 +120,7 @@ public class AudibleScraper {
|
|||
ConnectionNotifier.getInstance().signout();
|
||||
|
||||
try {
|
||||
setURL("/signout");
|
||||
setURL("/signout", "Signing out");
|
||||
} catch(Throwable th)
|
||||
{
|
||||
LOG.info("signout error, ignorning...");
|
||||
|
@ -319,7 +319,7 @@ public class AudibleScraper {
|
|||
if (true)
|
||||
getWebClient().setJavascriptEnabled(true);
|
||||
try {
|
||||
setURL(homeURL(), "Loading web page...");
|
||||
setURL(homeURL());
|
||||
|
||||
if (checkLoggedIn())
|
||||
return;
|
||||
|
@ -380,11 +380,17 @@ public class AudibleScraper {
|
|||
public void lib() throws Exception {
|
||||
String browserURL = ConnectionNotifier.instance.getLastURL();
|
||||
|
||||
if (!browserURL.isEmpty())
|
||||
if (browserURL.startsWith(getAudibleBase()))
|
||||
{
|
||||
// a bit of a hack.. try to log in using library URL in browser.
|
||||
LOG.info("Using library location from browser: "+browserURL);
|
||||
if (setURLAndLogIn(browserURL))
|
||||
return;
|
||||
try {
|
||||
if (setURLAndLogIn(browserURL))
|
||||
return;
|
||||
} catch (IOException e)
|
||||
{
|
||||
LOG.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!setURLAndLogIn("/lib"))
|
||||
|
@ -455,16 +461,23 @@ public class AudibleScraper {
|
|||
}
|
||||
|
||||
public Page setURL(String u) throws FailingHttpStatusCodeException, IOException {
|
||||
return setURL(u, u);
|
||||
return setURL(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 (!task.isEmpty())
|
||||
{
|
||||
task += ": ";
|
||||
}
|
||||
task+=u;
|
||||
getProgress().setSubTask(task);
|
||||
LOG.info("setURL:" + u);
|
||||
Page p = getWebClient().getPage(u);
|
||||
|
||||
if (p instanceof HtmlPage) {
|
||||
|
@ -581,19 +594,21 @@ public class AudibleScraper {
|
|||
ArrayList<Book> list = LibraryParser.instance.parseLibraryFragment(page);
|
||||
|
||||
int newBooks = 0;
|
||||
int partialBooks = 0;
|
||||
|
||||
for (Book b : list) {
|
||||
// LOG.info(b.toString());
|
||||
if (isPartialBook(b))
|
||||
{
|
||||
LOG.error("ignore partial book: " + b);
|
||||
continue;
|
||||
}
|
||||
if (results.contains(b)) {
|
||||
LOG.error("duplicate book:" + b);
|
||||
assert (false);
|
||||
}
|
||||
|
||||
if (isPartialBook(b))
|
||||
{
|
||||
partialBooks++;
|
||||
continue;
|
||||
}
|
||||
|
||||
results.add(b);
|
||||
if (existingBooks != null) {
|
||||
if (!existingBooks.containsKey(b.getProduct_id()))
|
||||
|
@ -602,6 +617,8 @@ public class AudibleScraper {
|
|||
newBooks++;
|
||||
}
|
||||
}
|
||||
LOG.info("pageNum="+pageNum+" total="+list.size()+" new="+newBooks+" partial="+partialBooks);
|
||||
|
||||
|
||||
if (newBooks == 0) {
|
||||
|
||||
|
|
|
@ -74,6 +74,10 @@ public class ConnectionNotifier extends EventNotifier<ConnectionListener> implem
|
|||
|
||||
public void setLastURL(String lastURL) {
|
||||
this.lastURL = lastURL;
|
||||
if (!lastURL.contains("audible"))
|
||||
{
|
||||
LOG.warn("expected audible");
|
||||
}
|
||||
LOG.info("Setting lastURL to:"+lastURL);
|
||||
}
|
||||
|
||||
|
|
|
@ -455,6 +455,28 @@ public class AudibleGUI implements BookListener, ConnectionListener {
|
|||
downloadAAX(getSelected());
|
||||
}
|
||||
|
||||
public void ignoreSelected() {
|
||||
List<Book> sel = getSelected();
|
||||
|
||||
if (!sel.isEmpty())
|
||||
{
|
||||
Book first = sel.get(0);
|
||||
String title = "Are you sure you want to ignore books";
|
||||
String bodySingular = "Are you sure you want to ignore the following book? \n\t"+ first.getFullTitle()+"\n\nIt will be added to the ignore list and not shown in OpenAudible anymore.";
|
||||
String bodyPlurel = "You selected "+sel.size()+" books. Are you sure you want to ignore them? \n\nThey will added to the ignore list and not be shown in OpenAudible anymore.";
|
||||
String body = sel.size()==1 ? bodySingular:bodyPlurel;
|
||||
|
||||
boolean yn = MessageBoxFactory.showGeneralYesNo(getShell(), title,body);
|
||||
if (yn)
|
||||
{
|
||||
audible.addToIgnoreSet(sel);
|
||||
BookNotifier.getInstance().booksUpdated(); // redraw all.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void convertSelected() {
|
||||
convertMP3(getSelected());
|
||||
}
|
||||
|
@ -618,7 +640,7 @@ public class AudibleGUI implements BookListener, ConnectionListener {
|
|||
|
||||
ArrayList<Book> list = new ArrayList<>();
|
||||
list.addAll(audible.getBooks());
|
||||
Collections.sort(list);
|
||||
// Collections.sort(list);
|
||||
// sort by purchase date.
|
||||
list.sort((b1, b2) -> -1 * b1.getPurchaseDate().compareTo(b2.getPurchaseDate()));
|
||||
|
||||
|
@ -736,18 +758,30 @@ public class AudibleGUI implements BookListener, ConnectionListener {
|
|||
}
|
||||
|
||||
|
||||
private boolean displayBook(Book b) {
|
||||
if (audible.isIgnoredBook(b))
|
||||
return false;
|
||||
|
||||
if (textFilter.isEmpty()) return true; // don't skip any books if no filter.
|
||||
String text = textFilter.toLowerCase();
|
||||
BookElement elems[] = {BookElement.fullTitle, BookElement.author, BookElement.narratedBy, BookElement.shortTitle};
|
||||
|
||||
for (BookElement e : elems) {
|
||||
if (b.has(e) && b.get(e).toLowerCase().contains(text))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// if search text is filled, return books that match.
|
||||
// otherwise, return all books (default)
|
||||
public List<Book> getDisplayedBooks() {
|
||||
ArrayList<Book> displayed = new ArrayList<>();
|
||||
if (textFilter.isEmpty())
|
||||
displayed.addAll(Audible.instance.getBooks());
|
||||
else {
|
||||
for (Book b : Audible.instance.getBooks()) {
|
||||
if (bookContainsText(b, textFilter))
|
||||
displayed.add(b);
|
||||
}
|
||||
|
||||
for (Book b : Audible.instance.getBooks())
|
||||
{
|
||||
if (displayBook(b))
|
||||
displayed.add(b);
|
||||
}
|
||||
return displayed;
|
||||
}
|
||||
|
@ -757,16 +791,6 @@ public class AudibleGUI implements BookListener, ConnectionListener {
|
|||
bookNotifier.booksUpdated();
|
||||
}
|
||||
|
||||
private boolean bookContainsText(Book b, String text) {
|
||||
text = text.toLowerCase();
|
||||
BookElement elems[] = {BookElement.fullTitle, BookElement.author, BookElement.narratedBy, BookElement.shortTitle};
|
||||
|
||||
for (BookElement e : elems) {
|
||||
if (b.has(e) && b.get(e).toLowerCase().contains(text))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void parseAAX() {
|
||||
ProgressTask task = new ProgressTask("Parse AAX File") {
|
||||
|
@ -1225,10 +1249,11 @@ public class AudibleGUI implements BookListener, ConnectionListener {
|
|||
@Override
|
||||
public void loginFailed(String url, String html) {
|
||||
|
||||
SWTAsync.slow(new SWTAsync("Login problem...") {
|
||||
SWTAsync.slow(new SWTAsync("Need to open browser...") {
|
||||
public void task() {
|
||||
String message = "There was a problem logging in... Try to view the page in the OpenAudible Browser?";
|
||||
boolean ok = MessageBoxFactory.showGeneralYesNo(null, "Trouble logging in", message);
|
||||
String message = "Unable to automatically log in... \n\nPlease use the OpenAudible web browser to log onto your audible account and navigate to your library (list of books) and try to connect again." +
|
||||
"\n\nOpen OpenAudible Browser Now?";
|
||||
boolean ok = MessageBoxFactory.showGeneralYesNo(null, "Log in to your audible account", message);
|
||||
if (ok)
|
||||
browse(url);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package org.openaudible.desktop.swt.manager;
|
|||
public interface Version {
|
||||
|
||||
String appName = "OpenAudible";
|
||||
String appVersion = "1.1.5";
|
||||
String appVersion = "1.1.6";
|
||||
boolean appDebug = false;
|
||||
String appLink = "http://openaudible.org";
|
||||
String versionLink = "http://openaudible.org/swt_version.json";
|
||||
|
|
|
@ -31,7 +31,7 @@ public class AppMenu implements ITranslatable, SelectionListener {
|
|||
private Menu aboutMenu;
|
||||
|
||||
private final Command[] actionCommands = {Command.ViewInAudible, Command.Show_MP3, Command.Play, Command.Download,
|
||||
Command.Convert, Command.Refresh_Book_Info};
|
||||
Command.Convert, Command.Refresh_Book_Info, Command.Ignore_Book};
|
||||
private final Command[] controlCommands = {Command.Connect, Command.Quick_Refresh, Command.Rescan_Library, Command.Download_All, Command.Convert_All,
|
||||
Command.MenuSeparator, Command.Browser, Command.Logout}; // , Command.MenuSeparator, Command.Logout};
|
||||
|
||||
|
|
|
@ -31,7 +31,8 @@ public enum Command {
|
|||
AppWebPage,
|
||||
Logout,
|
||||
Test1,
|
||||
MenuSeparator;
|
||||
MenuSeparator,
|
||||
Ignore_Book;
|
||||
|
||||
|
||||
public char getKeyEquiv() {
|
||||
|
|
|
@ -262,6 +262,9 @@ public class CommandCenter {
|
|||
case Logout:
|
||||
AudibleGUI.instance.logout();
|
||||
break;
|
||||
case Ignore_Book:
|
||||
AudibleGUI.instance.ignoreSelected();
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.info("Unknown cmd: " + c);
|
||||
|
@ -314,7 +317,6 @@ public class CommandCenter {
|
|||
return AudibleGUI.instance.canConvertAll();
|
||||
case Logout:
|
||||
return ConnectionNotifier.getInstance().isConnected();
|
||||
|
||||
case Preferences:
|
||||
case Quit:
|
||||
case About:
|
||||
|
@ -327,6 +329,7 @@ public class CommandCenter {
|
|||
case Cut:
|
||||
case Paste:
|
||||
return false;
|
||||
case Ignore_Book:
|
||||
case Refresh_Book_Info:
|
||||
return AudibleGUI.instance.getSelected().size() > 0;
|
||||
case Rescan_Library:
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.openaudible.util.Platform;
|
|||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
|
@ -33,7 +34,6 @@ public class AudibleBrowser {
|
|||
boolean title = false;
|
||||
Composite parent;
|
||||
Text locationBar;
|
||||
private Browser browser;
|
||||
ToolBar toolbar;
|
||||
Canvas canvas;
|
||||
ToolItem itemBack, itemForward;
|
||||
|
@ -42,6 +42,7 @@ public class AudibleBrowser {
|
|||
SWTError error = null;
|
||||
Collection<Cookie> cookies;
|
||||
String customHeader[];
|
||||
private Browser browser;
|
||||
|
||||
public AudibleBrowser(Composite parent, String url) {
|
||||
this.parent = parent;
|
||||
|
@ -71,23 +72,6 @@ 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.
|
||||
*/
|
||||
|
@ -139,16 +123,40 @@ public class AudibleBrowser {
|
|||
display.dispose();
|
||||
}
|
||||
|
||||
public static void showHelp(Display display) {
|
||||
File dir = Directories.getHelpDirectory();
|
||||
File index = new File(dir, "index.html");
|
||||
if (index.exists()) {
|
||||
URI uri = index.toURI();
|
||||
String u = uri.toString();
|
||||
newBrowserWindow(display, u);
|
||||
} else {
|
||||
MessageBoxFactory.showError(null, "Unable to open help. Expected at:" + index.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of all resources associated with a particular instance of the BrowserApplication.
|
||||
*/
|
||||
public void dispose() {
|
||||
freeResources();
|
||||
}
|
||||
|
||||
public SWTError getError() {
|
||||
return error;
|
||||
}
|
||||
/*
|
||||
|
||||
public Browser getBrowser() {
|
||||
|
@ -156,6 +164,10 @@ public class AudibleBrowser {
|
|||
}
|
||||
*/
|
||||
|
||||
public SWTError getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setShellDecoration(Image icon, boolean title) {
|
||||
this.icon = icon;
|
||||
this.title = title;
|
||||
|
@ -259,7 +271,25 @@ public class AudibleBrowser {
|
|||
|
||||
@Override
|
||||
public void changed(LocationEvent locationEvent) {
|
||||
ConnectionNotifier.instance.setLastURL(locationEvent.location);
|
||||
|
||||
try {
|
||||
String u = locationEvent.location;
|
||||
if (u.startsWith("http")) {
|
||||
|
||||
URL url = new URL(u);
|
||||
String h = url.getHost();
|
||||
if (h.contains("audible")) {
|
||||
ConnectionNotifier.instance.setLastURL(locationEvent.location);
|
||||
} else
|
||||
{
|
||||
System.err.println("ignore non-audible..."+h);
|
||||
}
|
||||
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
th.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -437,18 +467,6 @@ public class AudibleBrowser {
|
|||
return browser == null || browser.isDisposed();
|
||||
}
|
||||
|
||||
public static void showHelp(Display display) {
|
||||
File dir = Directories.getHelpDirectory();
|
||||
File index = new File(dir, "index.html");
|
||||
if (index.exists()) {
|
||||
URI uri = index.toURI();
|
||||
String u = uri.toString();
|
||||
newBrowserWindow(display, u);
|
||||
} else {
|
||||
MessageBoxFactory.showError(null, "Unable to open help. Expected at:" + index.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (!isDisposed()) browser.close();
|
||||
browser = null;
|
||||
|
|
|
@ -122,7 +122,13 @@ public class WebPage {
|
|||
String mp3Name = fileName + ".mp3";
|
||||
File mp3File = new File(mp3Dir, mp3Name);
|
||||
progress.setTask("Copying book " + count + " of " + toCopy.size() + " to " + mp3File.getAbsolutePath());
|
||||
CopyWithProgress.copyWithProgress(progress, mp3, mp3File);
|
||||
|
||||
try {
|
||||
CopyWithProgress.copyWithProgress(progress, mp3, mp3File);
|
||||
} catch(Throwable th)
|
||||
{
|
||||
LOG.error("error copying mp3:"+mp3.getAbsolutePath()+" to "+mp3File.getAbsolutePath()+" for book "+b);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue