monakhv.samlib.http.HttpClientController.java Source code

Java tutorial

Introduction

Here is the source code for monakhv.samlib.http.HttpClientController.java

Source

/*
 * Copyright 2013 Dmitry Monakhov.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package monakhv.samlib.http;

import android.content.Context;
import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import monakhv.android.samlib.exception.SamlibParseException;
import monakhv.android.samlib.exception.BookParseException;
import monakhv.android.samlib.exception.SamLibIsBusyException;
import monakhv.android.samlib.exception.SamLibNullAuthorException;
import monakhv.android.samlib.sql.entity.Author;
import monakhv.android.samlib.sql.entity.AuthorCard;
import monakhv.android.samlib.sql.entity.Book;
import monakhv.android.samlib.sql.entity.SamLibConfig;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;

/**
 *
 * @author Dmitry Monakhov
 *
 * The Class make all internet connection for SamLib Info project. Must be call
 * from Async tasks or Services only! Have 3 main method
 *
 * - addAuthor to add new Author to data base. The method is used by AddAuthor
 * task - getAuthorByURL get Author object using http connection.The method is
 * used by Update service - downloadBook to download book content to file in
 * HTML from. It is used by DownloadBook service
 */
public class HttpClientController {
    public interface PageReader {
        public String doReadPage(InputStream in) throws IOException;
    }

    public static final int RETRY_LIMIT = 5;
    public static final int CONNECTION_TIMEOUT = 10000;
    public static final int READ_TIMEOUT = 10000;
    public static final String ENCODING = "windows-1251";
    protected static final String USER_AGENT = "Android reader";
    private static final String DEBUG_TAG = "HttpClientController";
    private static HttpHost proxy = null;
    private static AuthScope scope = null;
    private static UsernamePasswordCredentials pwd = null;
    private static HttpClientController instance = null;
    private final SamLibConfig slc;

    public static HttpClientController getInstance(Context context) {
        if (instance == null) {
            instance = new HttpClientController(context);
        }

        return instance;
    }

    private HttpClientController(Context context) {
        slc = SamLibConfig.getInstance(context);
    }

    /**
     * Construct Author object using reduced.
     * URL Internet connection is made using set of mirrors
     *
     * This is the method for update service
     *
     * @param link reduced URL
     * @return Author object
     * @throws java.io.IOException
     * @throws monakhv.android.samlib.exception.SamlibParseException
     */
    public Author getAuthorByURL(String link) throws IOException, SamlibParseException {
        Author a = new Author();
        a.setUrl(link);
        String str = getURL(slc.getAuthorRequestURL(a), new StringReader());

        parseAuthorData(a, str);
        return a;
    }

    /**
     * Create Author object using internet data and reduced url string. 
     * The same as getAuthorByURL but calculate author name for use in addAuthor task
     * Internet connection is made using set of mirrors. This is the method for
     * AddAuthor task
     *
     * @param link reduced url
     * @return Author object
     * @throws IOException
     * @throws SamlibParseException
     */
    public Author addAuthor(String link) throws IOException, SamlibParseException {
        Author a = getAuthorByURL(link);
        a.extractName();
        return a;
    }

    /**
     * Save book to appropriate file and make file transformation to make it
     * readable by android applications like ALRead and CoolReader.
     * Internet connection is made using set of mirrors.
     *
     * This is the method for DownloadBook service
     *
     * @param book the book to download
     * @throws IOException connection problem occurred
     * @throws SamlibParseException remote host return status other then 200
     */
    public void downloadBook(Book book) throws IOException, SamlibParseException {
        File f = book.getFile();
        PageReader reader;
        switch (book.getFileType()) {
        case HTML:
            reader = new TextFileReader(book.getFile());
            getURL(slc.getBookUrl(book), reader);
            SamLibConfig.transformBook(f);
            break;
        case FB2:
            reader = new Fb2ZipReader(book.getFile());
            getURL(slc.getBookUrl(book), reader);
            break;
        default:
            throw new IOException();
        }

    }

    /**
     * Making author search
     * @param pattern author name pattern to search
     * @param page number of page
     * @return Search Result
     * @throws IOException 
     * @throws monakhv.android.samlib.exception.SamlibParseException 
     */
    public HashMap<String, ArrayList<AuthorCard>> searchAuthors(String pattern, int page)
            throws IOException, SamlibParseException {
        String str;
        try {
            str = getURL(slc.getSearchAuthorURL(pattern, page), new StringReader());
        } catch (NullPointerException ex) {
            throw new SamlibParseException("Pattern: " + pattern);
        }

        return parseSearchAuthorData(str);
    }

    /**
     * Make http connection and begin download data using list of mirrors URL
     *
     * @param urls list of mirrors URL
     * @param reader  file to download data to can be null
     * @return downloaded data in case file is null
     * @throws IOException connection problem
     * @throws SamlibParseException remote host return status other then 200
     */
    private String getURL(List<String> urls, PageReader reader) throws IOException, SamlibParseException {
        String res = null;
        IOException exio = null;
        SamlibParseException exparse = null;
        for (String surl : urls) {
            Log.i(DEBUG_TAG, "using urls: " + surl);
            exio = null;
            exparse = null;
            try {
                URL url = new URL(surl);
                res = _getURL(url, reader);
            } catch (IOException e) {
                slc.flipOrder();
                exio = e;
                Log.e(DEBUG_TAG, "IOException: " + surl, e);
            } catch (SamlibParseException e) {
                slc.flipOrder();
                exparse = e;
                Log.e(DEBUG_TAG, "AuthorParseException: " + surl, e);
            }

            if (exio == null && exparse == null) {
                return res;
            }
        }
        if (exio != null) {
            throw exio;
        } else {
            throw exparse;
        }
    }

    /**
     * Row method to make http connection and begin download data Take into
     * account 503 return status make retry after one (1) second of sleep. Call
     * only by _getURL. Make internal call of __getURL
     *
     * @param url URL to download from
     * @param reader File to download to, can be null
     * @return Download data if "f" is null
     * @throws IOException connection problem
     * @throws SamlibParseException remote host return status other then 200 ad
     * 503
     */
    private String _getURL(URL url, PageReader reader) throws IOException, SamlibParseException {
        String res = null;
        boolean retry = true;
        int loopCount = 0;
        while (retry) {
            try {
                res = __getURL(url, reader);
                retry = false;
            } catch (SamLibIsBusyException ex) {
                loopCount++;
                Log.w(DEBUG_TAG, "Retry number: " + loopCount + "  sleep 1 second");
                try {
                    TimeUnit.SECONDS.sleep(loopCount);
                } catch (InterruptedException ex1) {
                    Log.e(DEBUG_TAG, "Sleep interapted: ", ex);
                }
                if (loopCount >= RETRY_LIMIT) {
                    // retry = false;
                    throw new IOException("Retry Limit exeeded");
                }
            }
        }
        return res;
    }

    /**
     * Very row method to make http connection and begin download data Call only
     * by _getURL
     *
     * @param url URL to download
     * @param reader File to download to can be null
     * @return Download data if "f" is null
     * @throws IOException connection problem
     * @throws SamLibIsBusyException host return 503 status
     * @throws SamlibParseException host return status other then 200 and 503
     */
    private String __getURL(URL url, PageReader reader)
            throws IOException, SamLibIsBusyException, SamlibParseException {

        HttpGet method = new HttpGet(url.toString());

        HttpParams httpParams = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(httpParams, CONNECTION_TIMEOUT);
        HttpConnectionParams.setSoTimeout(httpParams, READ_TIMEOUT);

        DefaultHttpClient httpclient = new DefaultHttpClient(httpParams);

        if (pwd != null && scope != null) {
            httpclient.getCredentialsProvider().setCredentials(scope, pwd);
        }

        if (proxy != null) {
            httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
        }

        method.setHeader("User-Agent", USER_AGENT);
        method.setHeader("Accept-Charset", ENCODING);
        HttpResponse response;
        try {
            response = httpclient.execute(method);
            Log.d(DEBUG_TAG, "Status Response: " + response.getStatusLine().toString());
        } catch (NullPointerException ex) {
            Log.e(DEBUG_TAG, "Connection Error", ex);
            throw new IOException("Connection error: " + url.toString());
        }
        int status = response.getStatusLine().getStatusCode();

        if (status == 503) {
            httpclient.getConnectionManager().shutdown();
            throw new SamLibIsBusyException("Need to retryException ");
        }
        if (status != 200) {
            httpclient.getConnectionManager().shutdown();
            throw new SamlibParseException("Status code: " + status);
        }

        String result = reader.doReadPage(response.getEntity().getContent());
        httpclient.getConnectionManager().shutdown();
        return result;

    }

    public static void setProxy(String host, int port, String user, String password) {
        proxy = new HttpHost(host, port);
        scope = new AuthScope(host, port);
        pwd = new UsernamePasswordCredentials(user, password);

    }

    public static void cleanProxy() {
        proxy = null;
        pwd = null;
        scope = null;
    }

    /**
     * Parse String data to load Author object 
     * @param a Author object to load data to
     * @param text String data to parse
     * 
     * @throws SamlibParseException Error parsing
     */
    private static void parseAuthorData(Author a, String text) throws SamlibParseException {
        String[] lines = text.split("\n");

        for (String line : lines) {

            if (SamLibConfig.testSplit(line) < 9) {
                Log.e(DEBUG_TAG, "Line Book parse Error:  length=" + SamLibConfig.testSplit(line) + "   line: "
                        + line + " lines: " + lines.length);
                throw new SamlibParseException("Line Book parse Error:  length=" + SamLibConfig.testSplit(line)
                        + "   line: " + line + " lines: " + lines.length);
            }
            try {
                Book b = new Book(line);
                b.setAuthorId(a.getId());
                a.getBooks().add(b);
            } catch (BookParseException ex) {//parsing book update date handling
                //TODO: new put it to Book constructor
                Log.e(DEBUG_TAG, "Error parsing book: " + line + "  skip it.", ex);
            }
        }

    }

    private HashMap<String, ArrayList<AuthorCard>> parseSearchAuthorData(String text) throws SamlibParseException {
        String[] lines = text.split("\n");
        HashMap<String, ArrayList<AuthorCard>> res = new HashMap<String, ArrayList<AuthorCard>>();
        for (String line : lines) {
            if (SamLibConfig.testSplit(line) < 7) {
                Log.e(DEBUG_TAG, "Line Search parse Error:  length=" + SamLibConfig.testSplit(line) + "\nline: "
                        + line + "\nlines: " + lines.length);
                throw new SamlibParseException("Parse Search Author error\nline: " + line);
            }
            try {
                AuthorCard card = new AuthorCard(line);
                String name = card.getName();

                if (res.containsKey(name)) {
                    res.get(name).add(card);

                } else {
                    ArrayList<AuthorCard> aa = new ArrayList<AuthorCard>();
                    aa.add(card);
                    res.put(name, aa);

                }
            } catch (SamLibNullAuthorException ex) {
                //Log.i(DEBUG_TAG,"Skip author with no book");
            }

        }
        if (res.isEmpty()) {
            return null;
        }
        return res;

    }
}