/* * [ISBNToASIN.java] * * Summary: Convert ISBN to ASIN. Only for Amazon books, not kindle. * * Copyright: (c) 2014-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.0 2014-07-20 initial version * 1.1 2014-08-01 put all static init from env in static init to control order. * 1.2 2016-06-21 add countdownToSave intermediate saves. */ package com.mindprod.stores; import com.mindprod.aws.ProbeViaAWS; import com.mindprod.common18.EIO; import com.mindprod.common18.Misc; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import static com.mindprod.htmlmacros.macro.Global.configuration; import static java.lang.System.*; /** * Convert ISBN to ASIN. Only for Amazon books, not kindle. * * @author Roedy Green, Canadian Mind Products * @version 1.2 2016-06-21 add countdownToSave intermediate saves. * @see com.mindprod.stores.BStore * @see com.mindprod.stores.Book * @since 2014-07-20 */ public class ISBNToASIN { // declarations /** * true if want extra output and stop on first error. */ private static final boolean DEBUGGING = false; /** * true if should reprobe ISBN -> ASIN lookups that failed previously */ private static final boolean REFRESH_ASIN_LOOKUP = Misc.parseBoolean( System.getenv( "refreshasinlookup" ), false ); /** * how much extra space to give HashMap above size needed to hold elts */ private static final int BREATHING_ROOM_PERCENT = 30; /** * Defining a layout version for a class. */ private static final long serialVersionUID = 1L; /** * root directory of the local websites */ private static final File webrootDir = new File( configuration.getLocalWebrootWithSlashes() ); /** * how ofter we do intermediate saves */ private static final int SAVE_FREQUENCY = 500; // /declarations // methods /** * look up ASIN given ISBN. If no corresponding ASIN, has string "none". */ static HashMap lookupASINbyISBN; /** * We save after every 1000 changes to the stock to save work in case of stall or crash */ private static int countdownToSave = SAVE_FREQUENCY; /** * find deadwood isbns, but do not remove them */ private static void findASINDeadwood() { for ( Map.Entry entry : lookupASINbyISBN.entrySet() ) { final String isbn = ( String ) entry.getKey(); final String asin = ( String ) entry.getValue(); if ( !Stock.amazonBookStock.isProductDefined( asin ) ) { out.println( "ASIN deadwood found " + isbn + " " + asin ); } } } /** * load up binary disk file into HashMap */ private static void load() { final File file = new File( webrootDir, "embellishment/isbntoasin.dat" ); if ( !file.canRead() ) { err.println( EIO.getCanOrAbsPath( file ) + " missing. Starting from scratch." ); lookupASINbyISBN = new HashMap<>( 100 ); return; } try { final DataInputStream dis = EIO.getDataInputStream( file, 64 * 1024 ); final long expectedSerialVersionUID = dis.readLong(); if ( expectedSerialVersionUID != serialVersionUID ) { err.println( EIO.getCanOrAbsPath( file ) + " wrong version. Starting over." ); lookupASINbyISBN = new HashMap<>( 100 ); dis.close(); return; } final int size = dis.readInt(); lookupASINbyISBN = new HashMap<>( ( int ) ( size * ( 100d + BREATHING_ROOM_PERCENT ) / 100d ) ); for ( int i = 0; i < size; i++ ) { final String isbn = dis.readUTF(); final String asin = dis.readUTF(); lookupASINbyISBN.put( isbn, asin ); } dis.close(); } catch ( IOException e ) { err.println( EIO.getCanOrAbsPath( file ) + " trouble reading. Starting over." + e.getMessage() ); lookupASINbyISBN = new HashMap<>( 100 ); } }// /method /** * ISBNs that previously failed to lookup an ASIN may now succeed. */ private static void refreshASINLookup() { // redo the "nones" // to refresh everything, delete embellishment/isbntoasin.dat out.println( "Refreshing ASIN lookup of previous fails." ); for ( Map.Entry entry : lookupASINbyISBN.entrySet() ) { final String isbn13 = entry.getKey(); String asin = entry.getValue(); if ( asin == null || asin.equals( "none" ) ) { // lookup bypassing the cache. asin = ProbeViaAWS.isbnToASINviaAWS( isbn13 ); if ( asin != null ) { lookupASINbyISBN.put( isbn13, asin ); // isbnToAsinViaAWS has already crowed about success } } } } /** * remove deadwood isbns */ private static void removeASINDeadwood() { // this has the unfortunate side effect of removing entries pointing to none that occur, // but which truly have no isbn at Amazon. // The fortunate size effect is this forces re-lookup of ISBNs that might now have ASINS. for ( Iterator iter = lookupASINbyISBN.entrySet().iterator(); iter.hasNext(); ) { final Map.Entry entry = ( Map.Entry ) iter.next(); final String isbn = ( String ) entry.getKey(); final String asin = ( String ) entry.getValue(); if ( !Stock.amazonBookStock.isProductDefined( asin ) ) { out.println( "ASIN deadwood removed " + isbn + " " + asin ); iter.remove();// avoids ConcurrentModificationException } } } /** * persist isbn -> asin lookupASINbyISBN table */ static void save() { final File file = new File( webrootDir, "embellishment/isbntoasin.dat" ); try { final DataOutputStream dos = EIO.getDataOutputStream( file, 64 * 1024 ); dos.writeLong( serialVersionUID ); // different from bookstore.dats. dos.writeInt( lookupASINbyISBN.size() ); for ( Map.Entry entry : lookupASINbyISBN.entrySet() ) { final String isbn = entry.getKey(); dos.writeUTF( isbn ); final String asin = entry.getValue(); dos.writeUTF( asin ); } dos.close(); } catch ( IOException e ) { err.println( EIO.getCanOrAbsPath( file ) + " trouble writing. Starting over." + e.getMessage() ); file.delete(); } }// /method /** * invoke deadwood handling on shutdown */ private static void shutdownASINDeadwood() { switch ( Stock.DEADWOOD ) { case 0: /* no */ break; case 1: /* yes */ removeASINDeadwood(); break; case 2: /* find */ findASINDeadwood(); break; default: throw new IllegalArgumentException( "invalid switch value in ISBNToASIN.shutdownDeadwood" ); } } /** * what to do to initialise */ public static void fireup() { load(); if ( REFRESH_ASIN_LOOKUP ) { refreshASINLookup(); } }// /** * convert isbn13 to ASIN, using cache, and AWS if necessary. * * @param isbn13 book number * * @return ASIN, null means amazon.com has not assigned an ASIN to this ISBN. */ public static String isbnToAsin( String isbn13 ) { synchronized ( lookupASINbyISBN ) { String asin = lookupASINbyISBN.get( isbn13 ); if ( asin == null ) { // for "none" we leave it as is. // never seen it before, probe to find out asin = ProbeViaAWS.isbnToASINviaAWS( isbn13 ); if ( asin == null ) { // so we won't keep reprobing it which would happen if we left it out of the cache. lookupASINbyISBN.put( isbn13, "none" ); // only complain once. out.println( "no asin for " + isbn13 ); } else { lookupASINbyISBN.put( isbn13, asin ); countdownToSave--; if ( countdownToSave == 0 ) { save(); countdownToSave = SAVE_FREQUENCY; } } return asin; } else if ( asin.equals( "none" ) ) { return null; } else { // found it in the cache return asin; } } }// /method /** * shutdown save */ public static void shutdown() { shutdownASINDeadwood(); save(); }// /method // /methods }