info.icefilms.icestream.browse.Location.java Source code

Java tutorial

Introduction

Here is the source code for info.icefilms.icestream.browse.Location.java

Source

/*
 * IceStream - The Official IceFilms Android Application
 * Copyright (C) 2011-2013 Stonyx
 *
 * The IceStream application (and any "covered work" as defined by the GNU General Public
 * License Version 3) is licensed under the GNU General Public License Version 3 (henceforth
 * referred to as "GNU GPL V3") with the following amendments that supersede any relevant wording
 * in the GNU GPL V3:
 *
 * 1. The IceStream application (and any "covered work" as defined by the GNU GPL V3) can be
 *    statically and/or dynamically linked to any source code, library, or application developed
 *    or released by Stonyx (the original authors of the IceStream application), regardless of the
 *    type of license that such source code, library, or application is licensed under.
 *
 * 2. The IceStream application (and any "covered work" as defined by the GNU GPL V3) can not be
 *    distributed for a fee without the prior written consent provided by Stonyx (the original
 *    authors of the IceStream application).
 *
 * The preceding amendments make up part of the license that the IceStream application is licensed
 * under.  They apply to and need to be included (along with the GNU GPL V3) with any derivative
 * work as outlined in the GNU GPL V3.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU GPL V3 for more details.
 *
 * You should have received a copy of the GNU GPL V3 along with this program.  If not, see
 * http://www.gnu.org/licenses/.
 */

package info.icefilms.icestream.browse;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.util.Log;
import info.icefilms.icestream.R;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.http.util.ByteArrayBuffer;

public abstract class Location implements Parcelable {
    // Class used to pass callback methods
    public static class Callback {
        // Variable
        private int mConnectionTimeout;
        private boolean mError;
        private int mErrorStringID;
        private boolean mWarning;
        private int mWarningStringID;
        private AsyncTask<?, ?, ?> mTask;

        // Constructor
        public Callback(Activity activity, AsyncTask<?, ?, ?> task) {
            try {
                mConnectionTimeout = Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(activity)
                        .getString("ConnectionTimeout", "20")) * 1000;
            } catch (NumberFormatException exception) {
                mConnectionTimeout = 20000;
            }
            mError = false;
            mErrorStringID = 0;
            mWarning = false;
            mWarningStringID = 0;
            mTask = task;
        }

        // Setter and getter methods
        public int GetConnectionTimeout() {
            return mConnectionTimeout;
        }

        public void SetErrorBoolean(boolean error) {
            mError = error;
        }

        public boolean GetErrorBoolean() {
            return mError;
        }

        public void SetErrorStringID(int id) {
            mErrorStringID = id;
        }

        public int GetErrorStringID() {
            return mErrorStringID;
        }

        public void SetWarningBoolean(boolean warning) {
            mWarning = warning;
        }

        public boolean GetWarningBoolean() {
            return mWarning;
        }

        public void SetWarningStringID(int id) {
            mWarningStringID = id;
        }

        public int GetWarningStringID() {
            return mWarningStringID;
        }

        // Method called to see if the async task associated with this callback class
        //    has been cancelled
        public boolean IsCancelled() {
            return mTask.isCancelled();
        }
    }

    // Variables
    private static URL mIceFilmsURL;
    private URL mURL;

    // Static method called to create a location for a passed item
    public static Location CreateLocationForItem(Item item) {
        Location location = null;
        if (item instanceof HeadingItem)
            location = new HeadingLocation(item.GetURL());
        else if (item instanceof SeriesItem)
            location = new SeriesLocation(item.GetURL());
        else if (item instanceof VideoItem)
            location = new VideoLocation(item.GetURL());
        else if (item instanceof SourceItem)
            location = new SourceLocation(((SourceItem) item).GetCookie(), ((SourceItem) item).GetSecret(),
                    ((SourceItem) item).GetToken(), ((SourceItem) item).GetIdent());

        return location;
    }

    // Constructor
    protected Location(URL url) {
        // Save the URL
        mURL = url;

        // Make sure we have an IceFilms URL
        if (mIceFilmsURL == null) {
            try {
                mIceFilmsURL = new URL(mURL.getProtocol(), mURL.getHost(), mURL.getPort(), "");
            } catch (MalformedURLException exception) {
            }
        }
    }

    // Constructor used by the parcelable functionality of this class
    protected Location(Parcel in) {
        mIceFilmsURL = (URL) in.readValue(null);
        mURL = (URL) in.readValue(null);
    }

    // Method used by the parcelable functionality of this class
    public int describeContents() {
        return 0;
    }

    // Method used by the parcelable functionality of this class
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeValue(mIceFilmsURL);
        dest.writeValue(mURL);
    }

    // Getter methods
    public URL GetIceFilmsURL() {
        return mIceFilmsURL;
    }

    public URL GetURL() {
        return mURL;
    }

    // Method called to get a list of heading items
    // Returns null if the async task calling this method got cancelled or if an
    //    error occurred otherwise returns an array of lists of heading items
    @SuppressWarnings("unchecked")
    public ArrayList<Item>[] GetHeadingItems(Callback callback) {
        // Download the page
        String page = DownloadPage(mURL, callback);
        if (page == null) {
            return null;
        } else if (page.length() == 0) {
            if (callback.GetErrorBoolean() == false) {
                callback.SetErrorBoolean(true);
                callback.SetErrorStringID(R.string.browse_page_download_error);
            }
            return null;
        }

        // Create the matcher
        Matcher matcher = Pattern.compile("<div class='menu ((indent)|(submenu.*?))'>" + "(.+?)</div>")
                .matcher(page);

        // Get all the heading strings
        Vector<String> headings = new Vector<String>();
        while (matcher.find())
            headings.add(matcher.group(4));

        // Create the array of heading items lists
        ArrayList<Item>[] headingItems = new ArrayList[headings.size()];
        for (int i = 0; i < headingItems.length; ++i)
            headingItems[i] = new ArrayList<Item>();

        // Loop thru the heading strings
        int size = headings.size();
        for (int i = 0; i < size; ++i) {
            // Redefine the matcher
            matcher = Pattern.compile("(<a href=(.+?)>(.+?)</a>)|(<b>(.+?)</b>)").matcher(headings.get(i));

            // Loop thru all our matches
            while (matcher.find() == true) {
                // Check if we got cancelled
                if (callback.IsCancelled())
                    return null;

                // Safely get all the info
                String name;
                URL url;
                boolean selected;
                if (matcher.group(1) != null) {
                    name = matcher.group(3);
                    try {
                        url = new URL(mIceFilmsURL, matcher.group(2));
                    } catch (MalformedURLException exception) {
                        continue;
                    }
                    selected = false;
                } else if (matcher.group(4) != null) {
                    name = matcher.group(5);
                    url = null;
                    selected = true;
                } else {
                    continue;
                }

                // Filter out the forum, donate, and random items
                if (name.contains("Forum") || name.contains("Donate") || name.contains("Random"))
                    continue;

                // Clean the string
                name = CleanString(name);

                // Add a heading item to the list
                headingItems[i].add(new HeadingItem(name, url, selected));
            }
        }

        // Add a link to home to the first heading
        if (headingItems.length > 0 && headingItems[0].isEmpty() == false)
            headingItems[0].add(0, new HeadingItem("Home", mIceFilmsURL,
                    mURL.getPath().length() == 0 || mURL.getPath().equals("/")));

        // Check for any empty lists
        for (int i = 0; i < headingItems.length; ++i) {
            if (headingItems[i].isEmpty()) {
                if (callback.GetErrorBoolean() == false) {
                    callback.SetErrorBoolean(true);
                    callback.SetErrorStringID(R.string.browse_parse_error);
                }
                return null;
            }
        }

        return headingItems;
    }

    // Method called to get a list of list items
    // Returns null if the async task calling this method got cancelled or if an
    //    error occurred otherwise returns a list of list items
    public ArrayList<Item> GetListItems(Callback callback) {
        // Download the page
        String page = DownloadPage(mURL, callback);
        if (page == null) {
            return null;
        } else if (page.length() == 0) {
            if (callback.GetErrorBoolean() == false) {
                callback.SetErrorBoolean(true);
                callback.SetErrorStringID(R.string.browse_page_download_error);
            }
            return null;
        }

        // Get the list of items
        ArrayList<Item> listItems = OnGetListItems(page, callback);

        // Check for any errors
        if (listItems == null) {
            return null;
        } else if (listItems.isEmpty()) {
            if (callback.GetErrorBoolean() == false) {
                callback.SetErrorBoolean(true);
                callback.SetErrorStringID(R.string.browse_parse_error);
            }
            return null;
        }

        return listItems;
    }

    // Method called by the GetListItems method to get a list of list items
    //    from the passed page
    // Returns null if the Callback IsCancelled method returns true or
    //    returns an empty list if no items were found otherwise
    //    returns a list of list items
    protected abstract ArrayList<Item> OnGetListItems(String page, Callback callback);

    // Methods called by this and derived classes to download a website page
    // Return null if it was cancelled or if an error occurred otherwise
    //    return a string containing the website page
    protected static String DownloadPage(URL url, Callback callback) {
        return DownloadPage(url, null, null, null, callback);
    }

    protected static String DownloadPage(URL url, String sendCookie, String sendData, StringBuilder getCookie,
            Callback callback) {
        // Declare our buffer
        ByteArrayBuffer byteArrayBuffer;

        try {
            // Setup the connection
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestProperty("User-Agent",
                    "Mozilla/5.0 (X11; Linux i686; rv:2.0) Gecko/20100101 Firefox/4.0");
            urlConnection.setRequestProperty("Referer", mIceFilmsURL.toString());
            if (sendCookie != null)
                urlConnection.setRequestProperty("Cookie", sendCookie);
            if (sendData != null) {
                urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                urlConnection.setDoOutput(true);
            }
            urlConnection.setConnectTimeout(callback.GetConnectionTimeout());
            urlConnection.setReadTimeout(callback.GetConnectionTimeout());
            urlConnection.connect();

            // Get the output stream and send the data
            if (sendData != null) {
                OutputStream outputStream = urlConnection.getOutputStream();
                outputStream.write(sendData.getBytes());
                outputStream.flush();
                outputStream.close();
            }

            // Get the cookie
            if (getCookie != null) {
                // Clear the string builder
                getCookie.delete(0, getCookie.length());

                // Loop thru the header fields
                String headerName;
                String cookie;
                for (int i = 1; (headerName = urlConnection.getHeaderFieldKey(i)) != null; ++i) {
                    if (headerName.equalsIgnoreCase("Set-Cookie")) {
                        // Get the cookie
                        cookie = GetGroup("([^=]+=[^=;]+)", urlConnection.getHeaderField(i));

                        // Add it to the string builder
                        if (cookie != null)
                            getCookie.append(cookie);

                        break;
                    }
                }
            }

            // Get the input stream
            InputStream inputStream = urlConnection.getInputStream();

            // For some reason we can actually get a null InputStream instead of an exception
            if (inputStream == null) {
                Log.e("Ice Stream", "Page download failed. Unable to create Input Stream.");
                if (callback.GetErrorBoolean() == false) {
                    callback.SetErrorBoolean(true);
                    callback.SetErrorStringID(R.string.browse_page_download_error);
                }
                urlConnection.disconnect();
                return null;
            }

            // Get the file size
            final int fileSize = urlConnection.getContentLength();

            // Create our buffers
            byte[] byteBuffer = new byte[2048];
            byteArrayBuffer = new ByteArrayBuffer(2048);

            // Download the page
            int amountDownloaded = 0;
            int count;
            while ((count = inputStream.read(byteBuffer, 0, 2048)) != -1) {
                // Check if we got canceled
                if (callback.IsCancelled()) {
                    inputStream.close();
                    urlConnection.disconnect();
                    return null;
                }

                // Add data to the buffer
                byteArrayBuffer.append(byteBuffer, 0, count);

                // Update the downloaded amount
                amountDownloaded += count;
            }

            // Close the connection
            inputStream.close();
            urlConnection.disconnect();

            // Check for amount downloaded calculation error
            if (fileSize != -1 && amountDownloaded != fileSize) {
                Log.w("Ice Stream", "Total amount downloaded (" + amountDownloaded + " bytes) does not "
                        + "match reported content length (" + fileSize + " bytes).");
            }
        } catch (SocketTimeoutException exception) {
            Log.e("Ice Stream", "Page download failed.", exception);
            if (callback.GetErrorBoolean() == false) {
                callback.SetErrorBoolean(true);
                callback.SetErrorStringID(R.string.browse_page_timeout_error);
            }
            return null;
        } catch (IOException exception) {
            Log.e("Ice Stream", "Page download failed.", exception);
            if (callback.GetErrorBoolean() == false) {
                callback.SetErrorBoolean(true);
                callback.SetErrorStringID(R.string.browse_page_download_error);
            }
            return null;
        }

        // Convert things to a string
        return new String(byteArrayBuffer.toByteArray());
    }

    // Method called by this and derived classes to find an image and description
    //    and create an info item from them
    // Returns null if neither was found otherwise returns an info item
    protected static InfoItem FindImageAndDescriptionInfo(String page, Callback callback) {
        // See if there's an image
        Bitmap image = null;
        String imageURL = GetGroup(
                new String[] { "<a class=img target=_blank href=(.+?)>", "<iframe src=/noref\\.php\\?url=(.+?) " },
                page);
        if (imageURL != null) {
            // Create the image URL
            URL url;
            try {
                url = new URL(mIceFilmsURL, imageURL);
            } catch (MalformedURLException exception) {
                url = null;
            }

            // Get the image
            if (url != null) {
                image = DownloadImage(url, callback);

                // Getting the image is optional so change any errors into warnings
                if (callback.GetErrorBoolean() == true) {
                    callback.SetWarningBoolean(callback.GetErrorBoolean());
                    callback.SetWarningStringID(callback.GetErrorStringID());
                    callback.SetErrorBoolean(false);
                    callback.SetErrorStringID(0);
                }
            }
        }

        // See if there's a description
        String description = GetGroup(new String[] { "<p>.*?</p>\\s*?<p>.*?</p>\\s*?<p>(.*?)</p>",
                "<th>Description:</th><td>(.+?) {0,1}<" }, page);
        if (description != null)
            description = CleanString(description);

        // Create and return the info item
        if (image != null || description != null)
            return new InfoItem(description, image);
        else
            return null;
    }

    // Method called by the FindImageAndDescriptionInfo method to download an image
    // Returns null if an error occurred otherwise returns a Bitmap object
    private static Bitmap DownloadImage(URL url, Callback callback) {
        try {
            // Open the connection
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestProperty("User-Agent",
                    "Mozilla/5.0 (X11; Linux i686; rv:2.0) Gecko/20100101 Firefox/4.0");
            urlConnection.setConnectTimeout(callback.GetConnectionTimeout());
            urlConnection.setReadTimeout(callback.GetConnectionTimeout());
            urlConnection.connect();

            // Get the input stream
            InputStream inputStream = urlConnection.getInputStream();

            // For some reason we can actually get a null InputStream instead of an exception
            if (inputStream == null) {
                Log.e("Ice Stream", "Image download failed. Unable to create Input Stream.");
                if (callback.GetErrorBoolean() == false) {
                    callback.SetErrorBoolean(true);
                    callback.SetErrorStringID(R.string.browse_image_download_error);
                }
                urlConnection.disconnect();
                return null;
            }

            // Download the image and create the bitmap
            Bitmap bitmap = BitmapFactory.decodeStream(inputStream);

            // Close the connection
            inputStream.close();
            urlConnection.disconnect();

            // Check for errors
            if (bitmap == null) {
                Log.e("Ice Stream", "Image data decoding failed.");
                if (callback.GetErrorBoolean() == false) {
                    callback.SetErrorBoolean(true);
                    callback.SetErrorStringID(R.string.browse_image_decode_error);
                }
                return null;
            }

            return bitmap;
        } catch (SocketTimeoutException exception) {
            Log.e("Ice Stream", "Image download failed.", exception);
            if (callback.GetErrorBoolean() == false) {
                callback.SetErrorBoolean(true);
                callback.SetErrorStringID(R.string.browse_image_timeout_error);
            }
            return null;
        } catch (IOException exception) {
            Log.e("Ice Stream", "Image download failed.", exception);
            if (callback.GetErrorBoolean() == false) {
                callback.SetErrorBoolean(true);
                callback.SetErrorStringID(R.string.browse_image_download_error);
            }
            return null;
        }
    }

    // Methods called by this and derived classes to get a single group using
    //    a single or multiple regular expressions
    // Return null if a match wasn't found otherwise return the group match
    protected static String GetGroup(String regex, String source) {
        return GetGroup(new String[] { regex }, -1, source);
    }

    protected static String GetGroup(String regex, int flags, String source) {
        return GetGroup(new String[] { regex }, flags, source);
    }

    protected static String GetGroup(String[] regex, String source) {
        return GetGroup(regex, -1, source);
    }

    protected static String GetGroup(String[] regex, int flags, String source) {
        // Combine all the regular expressions
        String expression = "(" + regex[0] + ")";
        for (int i = 1; i < regex.length; ++i)
            expression += "|(" + regex[i] + ")";

        // Define the matcher
        Matcher matcher;
        if (flags == -1)
            matcher = Pattern.compile(expression).matcher(source);
        else
            matcher = Pattern.compile(expression, flags).matcher(source);

        // Check if we found anything
        if (matcher.find() == false) {
            return null;
        } else {
            // Return the group we found
            for (int i = 2; i <= matcher.groupCount(); i += 2)
                if (matcher.group(i) != null)
                    return matcher.group(i);

            return null;
        }
    }

    // Method called by this and derived classes to remove HTML codes and
    //    tags from a string
    protected static String CleanString(String string) {
        // Remove any html tags
        string = Pattern.compile("<.*?>", Pattern.DOTALL).matcher(string).replaceAll("");

        // Replace any special HTML characters
        string = StringEscapeUtils.unescapeHtml4(string);

        return string;
    }
}