/* * [DStore.java] * * Summary: Facts about various DVDstores that sell with UPC product numbers. * * Copyright: (c) 2011-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 2011-10-04 initial version */ package com.mindprod.stores; import com.mindprod.aws.jax.AWSECommerceService; import com.mindprod.aws.jax.AWSECommerceServicePortType; import com.mindprod.common18.Misc; import com.mindprod.common18.ST; import com.mindprod.entities.DeEntifyStrings; import com.mindprod.fastcat.FastCat; import com.mindprod.htmlmacros.macro.CountryFlag; import com.mindprod.http.Get; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Arrays; import java.util.EnumSet; import java.util.HashSet; import java.util.Set; import static com.mindprod.stores.DStoreConstants.CONFIGURATION; import static com.mindprod.stores.DStoreConstants.DOLLAR_PRICE; import static com.mindprod.stores.DStoreConstants.INTERPROBE_DELAY_FOR_AMAZON_GENERIC; import static com.mindprod.stores.DStoreConstants.NO_DELAY; import static com.mindprod.stores.Hint.IN; import static com.mindprod.stores.Hint.OUT; import static com.mindprod.stores.Hint.OUT2; import static com.mindprod.stores.Hint.UNLISTED; import static java.lang.System.*; /** * Facts about various DVDstores that sell with UPC product numbers. * * @author Roedy Green, Canadian Mind Products * @version 1.0 2011-10-04 initial version * @see DVD * @see Stock#reportUnusedClues * @since 2011-09-28 */ public enum DStore implements OnlineStore { AMAZONCA( true, "amazon.ca", "Amazon Canada", "https://", true, "www.amazon.ca", false, CountryFlag.Canada, CONFIGURATION.getAccountForAmazonCa(), INTERPROBE_DELAY_FOR_AMAZON_GENERIC, null, 0, 15121, 330641 ) { public AWSECommerceServicePortType getPort( AWSECommerceService service ) { return service.getAWSECommerceServicePortCA(); } public String buildProductLink( final String asin ) { return buildAmazonProductLink( asin ); } public String buildQueryLink( final String title, final String author ) { return buildAmazonQueryLink( title, author ); } public StockStatus probe( final String asin ) { return BulkProber.awsProbe( this, asin ); } }, AMAZONCOM( true, "amazon.com", "Amazon USA", "https://", true, "www.amazon.com", false, CountryFlag.USA, CONFIGURATION.getAccountForAmazonCom(), INTERPROBE_DELAY_FOR_AMAZON_GENERIC, null, 0, 1789, 9325 ) { public AWSECommerceServicePortType getPort( AWSECommerceService service ) { return service.getAWSECommerceServicePort(); } public String buildProductLink( final String asin ) { return buildAmazonProductLink( asin ); } public String buildQueryLink( final String title, final String author ) { return buildAmazonQueryLink( title, author ); } public StockStatus probe( final String asin ) { return BulkProber.awsProbe( this, asin ); } }, AMAZONDE( true, "amazon.de", "Amazon Germany", "https://", true, "www.amazon.de", false, CountryFlag.Germany, CONFIGURATION.getAccountForAmazonDe(), INTERPROBE_DELAY_FOR_AMAZON_GENERIC, null, 0, 1638, 6742 ) { public AWSECommerceServicePortType getPort( AWSECommerceService service ) { return service.getAWSECommerceServicePortDE(); } public String buildProductLink( final String asin ) { return buildAmazonProductLink( asin ); } public String buildQueryLink( final String title, final String author ) { return buildAmazonQueryLink( title, author ); } public StockStatus probe( final String asin ) { return BulkProber.awsProbe( this, asin ); } }, AMAZONES( true, "amazon.es", "Amazon Spain", "https://", true, "www.amazon.es", false, CountryFlag.Spain, CONFIGURATION.getAccountForAmazonEs(), INTERPROBE_DELAY_FOR_AMAZON_GENERIC, null, 0, 624, 6746 ) { public AWSECommerceServicePortType getPort( AWSECommerceService service ) { return service.getAWSECommerceServicePortES(); } public String buildProductLink( final String asin ) { return buildAmazonProductLink( asin ); } public String buildQueryLink( final String title, final String author ) { return buildAmazonQueryLink( title, author ); } public StockStatus probe( final String asin ) { return BulkProber.awsProbe( this, asin ); } }, AMAZONFR( true, "amazon.fr", "Amazon France", "https://", true, "www.amazon.fr", false, CountryFlag.France, CONFIGURATION.getAccountForAmazonFr(), INTERPROBE_DELAY_FOR_AMAZON_GENERIC, null, 0, 1642, 6746 ) { public AWSECommerceServicePortType getPort( AWSECommerceService service ) { return service.getAWSECommerceServicePortFR(); } public String buildProductLink( final String asin ) { return buildAmazonProductLink( asin ); } public String buildQueryLink( final String title, final String author ) { return buildAmazonQueryLink( title, author ); } public StockStatus probe( final String asin ) { return BulkProber.awsProbe( this, asin ); } }, AMAZONIT( true, "amazon.it", "Amazon Italy", "https://", true, "www.amazon.it", false, CountryFlag.Italy, CONFIGURATION.getAccountForAmazonIt(), INTERPROBE_DELAY_FOR_AMAZON_GENERIC, null, 0, 3370, 23322 ) { public AWSECommerceServicePortType getPort( AWSECommerceService service ) { return service.getAWSECommerceServicePortIT(); } public String buildProductLink( final String asin ) { return buildAmazonProductLink( asin ); } public String buildQueryLink( final String title, final String author ) { return buildAmazonQueryLink( title, author ); } public StockStatus probe( final String asin ) { return BulkProber.awsProbe( this, asin ); } }, AMAZONUK( true, "amazon.co.uk", "Amazon UK", "https://", true, "www.amazon.co.uk", false, CountryFlag.UK, CONFIGURATION.getAccountForAmazonUk(), INTERPROBE_DELAY_FOR_AMAZON_GENERIC, null, 0, 1634, 6738 ) { public AWSECommerceServicePortType getPort( AWSECommerceService service ) { return service.getAWSECommerceServicePortUK(); } public String buildProductLink( final String asin ) { return buildAmazonProductLink( asin ); } public String buildQueryLink( final String title, final String author ) { return buildAmazonQueryLink( title, author ); } public StockStatus probe( final String asin ) { return BulkProber.awsProbe( this, asin ); } }, JUNGLEE( /* amazon india, no affiliate program */ true, "junglee.com", "Junglee (Amazon in India)", "http://", false, "www.junglee.com", false, CountryFlag.India, CONFIGURATION.getAccountForJungleeCom(), INTERPROBE_DELAY_FOR_AMAZON_GENERIC, new Clue[] { // no AWS support, works with ASINs. new Clue( OUT, "This product is not available from any of our online sellers" ), new Clue( UNLISTED, "We're sorry. We cannot find any matches for your search" ), new Clue( IN, "regex:[0-9,]+" ), new Clue( IN, "Price Starts at" ), }, 55_000, 0, 0 ) { public String buildProbeLink( final String asin ) { return buildAmazonManualProbeLink( asin ); } public AWSECommerceServicePortType getPort( AWSECommerceService service ) { return service.getAWSECommerceServicePortIN(); } public String buildProductLink( final String asin ) { return buildAmazonProductLink( asin ); } public String buildQueryLink( final String title, final String author ) { return buildAmazonQueryLink( title, author ); } /** * Probe store to see if product in stock, don't use AWS * * @param asin of product * @return does store carry this book 0=no 1=yes 2=don't know */ public StockStatus probe( final String asin ) { validateAsin( asin ); return BulkProber.screenScrapeProbe( this, asin, buildProbeLink( asin ) ); } }, // Amazon DVDs are handled via EStore, though markup is generated by DVD BN( true, "Barnes & Noble", "Barnes and Noble", "https://", true, "www.barnesandnoble.com", true, CountryFlag.USA, CONFIGURATION.getAccountForBarnesAndNoble(), NO_DELAY, new Clue[] { new Clue( IN, "" + DOLLAR_PRICE + "" ), new Clue( IN, "regex:itemprop=\"offers\" itemscope itemtype=\"http://schema.org/Offer\">\\s*" + DOLLAR_PRICE + "" ), new Clue( OUT2, ">Item is available through our marketplace sellers and in stores.<" ), new Clue( OUT2, ">Item is available through our marketplace sellers.<" ), new Clue( UNLISTED, "We found 0 results for" ), }, 93_000, 0, 0 ) { public String buildProductLink( final String upc ) { return buildProbeLink( upc ); } public String buildProbeLink( final String upc ) { validateUPC( upc ); final FastCat sb = new FastCat( 4 ); // http://www.barnesandnoble.com/w?ean=0054961241590 sb.append( getProtocol(), getHost(), "/w?ean=", upc ); return sb.toString(); } public String buildQueryLink( final String title, final String author ) { // http://www.barnesandnoble.com/s?keyword=the%20meaning%20of%20life&store=All Products final FastCat sb = new FastCat( 5 ); sb.append( getProtocol(), getHost() ); sb.append( "/s?keyword=" ); sb.append( prepareQueryField( title + " by " + author ) ); sb.append( "&store=All+Products" ); return sb.toString(); } /** Probe BN DVD store to see if product in stock * * @param upc of product * * @return does store carry this dvd 0=no 1=yes 2=don't know */ public StockStatus probe( final String upc ) { validateUPC( upc ); return BulkProber.screenScrapeProbe( this, upc, buildProbeLink( upc ) ); } }, POWELLS( true, "powells.com", "Powells", "http://", false, "www.powells.com", true, CountryFlag.USA, CONFIGURATION.getAccountForPowells(), NO_DELAY, new Clue[] { new Clue( OUT2, "http://www.powells.com/Portals/0/Images/powells_images/soldout_160.png" ), }, 149_000, 0, 0 ) { // works with both http: and https:, but http: gets more hits. /** * get link for probing * @param powellsIsbn 13 or 14 digits * @return URL */ public String buildProbeLink( final String powellsIsbn ) { // THis may be an ISBN or a Powells extension 2 or 3 zeroes followed by UPC or ?? // sample in stock http://www.powells.com/book/-9780765342409 new format // Do not validate isbn final FastCat sb = new FastCat( 5 ); // http://www.powells.com/book/-767685975633 sb.append( getProtocol(), getHost(), "/book/-", powellsIsbn ); return sb.toString(); } /** * Get link for affiiate * @param powellsIsbn 13 or 14 digits * @return URL */ public String buildProductLink( final String powellsIsbn ) { // powells ISBN extended. No not validate final FastCat sb = new FastCat( 7 ); // http://www.powells.com/book/-9781583672129/?p_isbn&partnerid=28995 sb.append( getProtocol(), getHost() ); sb.append( "/book/-", powellsIsbn, "?p_isbn" ); sb.append( "&partnerid=", getAccount() ); return sb.toString(); } /** * query link, for general query for Powells * @param title title of dvd * * @return HTML for URL to link. */ public String buildQueryLink( String title, final String author ) { // Powells chokes on entities, HTML and ’ title = DeEntifyStrings.flattenHTML( title, ' ' ); title = title.replace( '\u2019' /* ’ */, '\'' ); final FastCat sb = new FastCat( 10 ); // split author into its own field // http://www.powells.com/SearchResults?kw=any_words:The+End+of+the+line&au=Joe+Smith&bi=Dvd sb.append( getProtocol(), this.getHost() ); sb.append( "/PartnerSearchResult.aspx?p_kw" ); sb.append( "&partnerID=", this.getAccount(), "&kw=any_words:" ); sb.append( prepareQueryField( title ) ); sb.append( "&au=" ); sb.append( prepareQueryField( author ) ); sb.append( "&bi=Dvd" ); return sb.toString(); } /** Probe Kobo store to see if product in stock * * @param isbn13 of product * * @return does dvdstore carry this dvd 0=no 1=yes 2=don't know */ public StockStatus probe( final String isbn13 ) { // do not validate return BulkProber.screenScrapeProbe( this, isbn13, buildProbeLink( isbn13 ) ); } }; // declarations /** * all the amazon stores including Junglee, and Amazon.cn * Must be here after enums defined. */ static final EnumSet AMAZON_DVDSTORES = EnumSet.range( DStore.AMAZONCA, DStore.JUNGLEE ); /** * max number of refused probes in a row from a store before we give up on it for this session */ private static final int MAX_ALLOWABLE_PROBES_REFUSED = 20; /** * max number stalls for a store before we give up restarting it. */ private static final int MAX_ALLOWABLE_STALLS = 9; /** * only applies to amazon stores. Just a field needed. Who knows why. */ private final int camp; /** * only applies to amazon stores. Just a field needed. Who knows why. */ private final int creative; /** * how much time we must allow between probes to this dvdstore, in ms */ private final long interProbeTime; /** * our account id with this dvdstore used to mark our sales */ private final String account; /** * dvdstore name with entities */ private final String decoratedDStoreName; /** * dvdstore name without entities */ private final String undecoratedDStoreName; /** * domain to go to with http:// */ private final String website; /** * text appearing on a page that carries this dvd for this dvdstore */ private final Clue[] clues; /** * flat to display beside dvdstore */ private final CountryFlag flag; /** * true if store takes PayPal */ private final boolean takesPayPal; /** * upcs we will probe for this store to see if they are instock */ private final HashSet productsYetToProbe = new HashSet<>( 100 ); /** * protocol for this store: http:// or https://. Defined in constructor */ private final String protocol; private final int minimumResponseLength; private final boolean isAlive; private final boolean sni; private int productsAdded; private int productsDropped; /** * count of refused probes */ private volatile int refused = 0; /** * 1=should check this store, 0=should not check, -1= don't know yet */ private int shouldCheck = -1; // /declarations /** * number 1..31 assigned by stock to indicate this store. */ private int stockOrdinal; /** * time stamp of when last probe completed. */ private long lastProbeCompleted; /** * which stock product/numbering-inventory to use */ private Stock stock; /** * Get connection in progress, null if none */ private Get getInProgress; /** * countdown of how many stalls/restarts. When hit 0, we give up */ private int stallsToGo = MAX_ALLOWABLE_STALLS; /** * last product probed, not necessary completed. */ private String lastProductProbed; /** * track how complete probe is 0 .. 7 */ private int probeCompletionStage = 0; /** * constructor * * @param isAlive true if store is functioning * @param decoratedDStoreName store name with entities * @param undecoratedDStoreName store name without entities * @param protocol either http: https: * @param sni sni server name indication true/false * @param website URL of website we link to * @param takesPayPal true if store takes paypal * @param flag which flag tco display * @param account account used to track sales. * @param interProbeTime minimum time between hitting same store in ms. * @param clues text on page indicating it carries or does not carry dvd * @param minimumResponseLength minumum acceptable response before we pars * @param camp mysterious Amazon link code * @param creative mysterious Amazon link code */ DStore( final boolean isAlive, final String decoratedDStoreName, final String undecoratedDStoreName, final String protocol, final boolean sni, final String website, final boolean takesPayPal, final CountryFlag flag, final String account, final long interProbeTime, final Clue[] clues, final int minimumResponseLength, final int camp, final int creative ) { this.isAlive = isAlive; this.decoratedDStoreName = decoratedDStoreName; assert decoratedDStoreName != null : "null decoratedDStoreName "; this.undecoratedDStoreName = undecoratedDStoreName; this.protocol = protocol; this.sni = sni; this.website = website; this.takesPayPal = takesPayPal; this.flag = flag; this.account = account; this.interProbeTime = interProbeTime; this.clues = clues == null ? new Clue[ 0 ] : clues; this.minimumResponseLength = minimumResponseLength; this.camp = camp; this.creative = creative; } /** * prepare title, author, description for use in a general query * * @param description title or author of the dvd * * @return endocoded field */ private static String prepareQueryField( final String description ) { try { return URLEncoder.encode( DeEntifyStrings.deEntifyHTML( description, ' ' ), "UTF-8" ); } catch ( UnsupportedEncodingException e ) { err.println( "UTF-8 not supported" ); System.exit( 2 ); return null; } } /** * print count of how many dvds each store has in stock. This helps detect faulty hints */ private static void printCountsOfItemsInStock() { if ( !Misc.parseBoolean( System.getenv( "showcountsofdvdsinstock" ), false ) ) { return; } out.println( "D V D S I N S T O C K" ); Stock.printCountsOfItemsInStock( DStore.values() ); }// /method /** * Report unused clues. This helps detect faulty hints() */ private static void reportUnusedClues() { if ( Misc.parseBoolean( System.getenv( "reportunusedclues" ), false ) || Misc.parseBoolean( System.getenv( "checkstores" ), false ) || Misc.parseBoolean( System.getenv( "checkdvdstores" ), false ) ) { out.println( "U N U S E D D V D C L U E S" ); Stock.reportUnusedClues( DStore.values() ); } }// /method /** * check that a string is a asin * * @param asin number */ private static void validateAsin( String asin ) { // dvd asin usually start with B00, but 1604866888 is also valid assert !ST.isEmpty( asin ) && asin.length() == 10 : "bad asin [" + asin + "]"; }// /method /** * check that a string is a UPC * * @param upc upc number */ private static void validateUPC( String upc ) { assert !ST.isEmpty( upc ) && upc.length() == 12 && ST.isNumeric( upc ) : "bad upc [" + upc + "]"; }// /method /** * flag link for a referral to Amazon store, manual probe * * @param asin asin,. * * @return code for link without or flag. */ String buildAmazonManualProbeLink( final String asin ) { validateAsin( asin ); final FastCat sb = new FastCat( 4 ); sb.append( getProtocol(), getHost() ); sb.append( "/dp/", asin ); return sb.toString(); }// /method /** * flag link for a referral to Amazon store, default. * * @param asin asin,. * * @return code for link without or flag. */ String buildAmazonProductLink( final String asin ) { validateAsin( asin ); // we link using asin, if ( asin == null ) { return "."; // dummy link } final String account = this.getAccount(); if ( account == null ) { final FastCat sb = new FastCat( 4 ); sb.append( getProtocol(), getHost() ); sb.append( "/dp/", asin ); return sb.toString(); } else { final int camp = this.getCamp(); final int creative = this.getCreative(); final FastCat sb = new FastCat( 14 ); sb.append( getProtocol(), getHost() ); sb.append( "/gp/product/", asin ); sb.append( "/ref=as_li_qf_br_asin_tl?ie=UTF8" ); sb.append( "&tag=", account ); sb.append( "&linkCode=as2" ); sb.append( "&camp=", camp ); sb.append( "&creative=", creative ); sb.append( "&creativeASIN=", asin ); return sb.toString(); } }// /method /** * general query to an Amazon EStore. Used when product not is stock. * * @param title product description. Cannot be private. * * @return general search link. */ String buildAmazonQueryLink( final String title, final String author ) { final FastCat sb = new FastCat( 6 ); // http://www.amazon.ca/s/?field-keywords=the+God+Delusion+by+richard+Dawkins&tag=canadianmin07-20 sb.append( getProtocol(), getHost() ); sb.append( "/s?field-keywords=" ); sb.append( prepareQueryField( title + " by " + author ) ); if ( account != null ) { sb.append( "&tag=", account ); } return sb.toString(); }// /method /** * getter of Amazon camp number * * @return camp */ int getCamp() { return camp; }// /method /** * getter for Amazon creative magic number * * @return creative number. */ int getCreative() { return creative; }// /method /** * summary reports */ public static void summary() { printCountsOfItemsInStock(); reportUnusedClues(); }// /method /** * clear counts of products added and dropped */ public void clearStats() { productsAdded = 0; productsDropped = 0; }// /method /** * configure this dvdstore to use a particular stock (numbering system, inventory) * * @param stock Stack that tracks products for this store. * @param ordinal number 0..60 assigned by Stack to indicate this store. */ public void configureStock( Stock stock, int ordinal ) { this.stock = stock; this.stockOrdinal = ordinal; }// /method /** * enque a probe for a given dvd for this store * * @param product upc */ public void enqueueProductToProbe( final String product ) { synchronized ( productsYetToProbe ) { productsYetToProbe.add( product ); } }// /method /** * forgive past string of refusals because we had a success */ public void forgiveRefusals() { refused = 0; } public String getAccount() { return account; }// /method /** * get all products this store potentially has in stock. * * @return Set of all products */ public String[] getAllSortedProducts() { // returns same set for all stores in the stock. final Set allProducts = stock.allProducts(); final String[] products = allProducts.toArray( new String[ allProducts.size() ] ); Arrays.sort( products ); return products; }// /method /** * get strings that indicate item is in or out of stock. * * @return clues array */ public Clue[] getClues() { return clues; }// /method /** * how many products we still have to probe */ public int getCountOfProductsYetToProbe() { synchronized ( productsYetToProbe ) { return productsYetToProbe.size(); } } public String getDecoratedStoreName() { return decoratedDStoreName; }// /method /** * make enum.name() visible. Don't change to name. * * @return enum name */ public String getEnumName() { return this.name(); }// /method public CountryFlag getFlag() { return flag; }// /method /** * get the Get connection in progress * * @return Get connection in progress, null if none */ public Get getGetInProgress() { return this.getInProgress; } public String getHost() { return website; }// /method /** * milliseconds min between probing the same store. */ public long getInterProbeTime() { return interProbeTime; }// /method /** * record last product probed for this store * * @return last product probed, not necessary completed. */ public String getLastProductProbed() { return this.lastProductProbed; } /** * record last product probed for this store * * @param product product id */ public void setLastProductProbed( String product ) { this.lastProductProbed = product; } /** * miminum length of response from store to parse */ public int getMinimumResponseLength() { return this.minimumResponseLength; } /** * get dvdstore ordinal */ public int getOrdinal() { return ordinal(); } /** * get the port for the server for that country * * @param service the soap service * * @return the port for that country. */ public AWSECommerceServicePortType getPort( AWSECommerceService service ) { return null; }// /method @Override /** * record whether probe completed * * @param completionStage 0..7 */ public int getProbeCompletionStage() { return probeCompletionStage; } @Override /** * record whether probe completed * * @param completionStage 0..7 */ public void setProbeCompletionStage( final int completionStage ) { this.probeCompletionStage = completionStage; } /** * is a given product in stock at this store? * * @param product product number * @param probe true if should probe to get status if one not on file. * @param isEBook true if this is an electronic book (out of stock treated as instock) * * @return StockStatus enum. */ public StockStatus getProductStatus( final String product, final boolean probe ) { // Override errors in product database final StockStatus override = CONFIGURATION.stockStatusOverride( this /* store */, product ); if ( override != null ) { return override; } synchronized ( stock ) { // normal case return stock.getProductStatus( product, stockOrdinal, probe ); } }// /method /** * get count of products recently added * * @return count of products recently added */ public int getProductsAddedCount() { return productsAdded; }// /method /** * get count of products recently dropped * * @return count of products recently dropped */ public int getProductsDroppedCount() { return productsDropped; }// /method /** * get protocol for this store: http:// or https:// */ public String getProtocol() { return this.protocol; } /** * get array of all the products we plan to probe for this store . */ public String[] getSortedProductsToProbe() { synchronized ( productsYetToProbe ) { final String[] products = productsYetToProbe.toArray( new String[ productsYetToProbe.size() ] ); Arrays.sort( products ); return products; } }// /method /** * get timestamp of when previous probe was completed * * @return timestamp in millis since 1970 */ public long getTimeLastProbeCompleted() { return lastProbeCompleted == 0 ? System.currentTimeMillis() : lastProbeCompleted; }// /method public String getUndecoratedStoreName() { return undecoratedDStoreName; }// /method /** * true if still probing this store, it has not refused too many probes */ public synchronized boolean isAcceptingProbes() { return refused <= MAX_ALLOWABLE_PROBES_REFUSED; }// /method /** * is this store functioning. * If false, show with under construction icon, without any links * * @return true if store working */ public boolean isAlive() { return isAlive; } /** * Should we give up reviving this stall store? * * @return true if this store has stalled many times and should not be revived, * false if store should be revived from stall yet again; */ public boolean isHopelesslyStalled() { return stallsToGo > 0; } /** * restart the counter of refused probes, if not already hit the limit * * @param product isbn, asin, upc just probed successfully. * @param straggler true if we are processing random stragglers. false if processing bulk sorted products. */ public void noteCompletedProbe( final String product, final boolean straggler ) { synchronized ( productsYetToProbe ) { productsYetToProbe.remove( product ); } lastProbeCompleted = System.currentTimeMillis(); if ( !straggler ) { stock.noteHighestProbe( product, stockOrdinal ); } }// /method /** * increment count of products added on this probe */ public void noteProductAdded() { productsAdded++; }// /method /** * increment count of products dropped on this probe */ public void noteProductDropped() { productsDropped++; }// /method /** * increment the count of refused probes */ public void noteRefusedProbe( final String product ) { synchronized ( productsYetToProbe ) { productsYetToProbe.remove( product ); } refused++; if ( refused < MAX_ALLOWABLE_PROBES_REFUSED ) { err.println( this + " refusing probes " + refused ); } else { err.println( this + " refusing probes " + refused + " giving up probing" ); } lastProbeCompleted = System.currentTimeMillis(); }// /method /** * record fact this store has stalled again */ public void noteStall() { stallsToGo--; } /** * what this store sells * * @return "DVDs" */ public String productCategory() { return "DVD"; }// /method /** * record the Get connection in progress * * @param get Get connection */ public void recordGetInProgress( Get getInProgress ) { this.getInProgress = getInProgress; } /** * set single stockItem status * * @param product product number * @param status 0=not in stock 1=in stock 2=unknown 3=store is refusing probes. */ public void setProductStockStatus( final String product, final StockStatus status ) { synchronized ( stock ) { stock.setProductStockStatus( product, stockOrdinal, status ); } }// /method /** * true if this store's inventory should be checked, according to env * * @return true if should check this store. */ public boolean shouldCheck() { // cache the result of probing the environment for set check=yes // 1=should check this store, 0=should not check, -1= don't know yet switch ( shouldCheck ) { case -1: if ( Misc.parseBoolean( System.getenv( "check" + this.name().toLowerCase() + "dvds" ), false ) ) { shouldCheck = 1; return true; } else { shouldCheck = 0; return false; } case 0: return false; case 1: return true; default: throw new IllegalArgumentException( "bug in shouldCheck case" ); } }// /method /** * do we need to suppress cookie errors * * @return true if need to suppress cookie errors */ public boolean suppressCookieErrors() { return false; }// /method public boolean takesPayPal() { return takesPayPal; }// /method /** * Was this item successfully probed on a recent run? * * @param product product number, may be null or empty. * * @return true if recently probed already. */ public boolean wasRecentlyProbed( String product ) { return stock.wasRecentlyProbed( product, stockOrdinal ); } }