package com.blogspot.applications4android.comicreader.core; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import org.apache.http.client.ClientProtocolException; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.util.Log; import com.blogspot.applications4android.comicreader.exceptions.ComicException; import com.blogspot.applications4android.comicreader.exceptions.ComicSDCardFull; /** * Base class for all comic series */ // TODO: add unit-tests public abstract class Comic extends ComicParser { /** properties folder name */ public static final String PROPS = "props"; /** previous session */ public static final int TYPE_PREV_SESSION = 1; /** latest comics */ public static final int TYPE_LATEST = 2; /** favorite comics */ public static final int TYPE_FAVORITE = 3; /** preview comics */ public static final int TYPE_PREVIEW = 4; /** background caching */ public static final int TYPE_CACHING = 5; /** navigate to latest strip */ public static final int NAV_LATEST = 11; /** navigate to latest strip forcefully */ public static final int NAV_LATEST_FORCE = 12; /** navigate to first strip */ public static final int NAV_FIRST = 13; /** navigate to next strip */ public static final int NAV_NEXT = 14; /** navigate to previous strip */ public static final int NAV_PREVIOUS = 15; /** navigate to random strip */ public static final int NAV_RANDOM = 16; /** navigate to current strip */ public static final int NAV_CURRENT = 17; /** navigate to previous session */ public static final int NAV_PREV_SESSION = 18; /** use date picker dialog */ public static final int DIALOG_DATE = 51; /** use number picker dialog */ public static final int DIALOG_NUMBER = 52; /** for logging purposes only */ private static final String TAG = "Comic"; /** uid for latest comic */ protected String mLatestUid; /** uid for first comic */ protected String mFirstUid; /** uid for the previous session comic */ protected String mPrevSessionUid; /** name of this comic */ private String mName; /** english name of this comic */ private String mComicName; /** list of all strips */ private HashMap mStrips; /** cache manager for this comic */ private Cache mCache; /** default value of zoom */ private float mDefZoom; /** current strip */ private Strip mCurrent; /** launch type */ private int mType; /** favorites array */ private ArrayList mFavs; /** for traversing on favorites array */ private int mFavIdx; /** background caching enabled */ private boolean mCacheEnabled; /** * Constructor */ public Comic() { mName = getClass().getSimpleName(); mComicName = null; mCache = new Cache(mName); mLatestUid = null; mFirstUid = null; mPrevSessionUid = null; mCurrent = null; mDefZoom = 1.0f; mStrips = new HashMap(); mType = TYPE_LATEST; mFavs = new ArrayList(); mFavIdx = 0; mCacheEnabled = true; } /** * Tell to this comic whether caching is enabled or not. This'll be used to * decide whether to forcefully download the latest comic or not. * * @param cache * true if it is, else false */ public void setCacheEnabled(boolean cache) { mCacheEnabled = cache; } /** * Set the launch type for this comic * * @param type * launch type */ public void setLaunchType(int type) { mType = type; } /** * get the launch type * * @return launch type */ public int getLaunchType() { return mType; } /** * Get the comic name * * @return name */ public String getName() { return mName; } /** * Gets the current strip * * @return current strip */ public Strip getCurrentStrip() { return mCurrent; } /** * Get the english name for this comic * * @return name */ public String getComicName() { return mComicName; } /** * Set the english name for this comic * * @param name * name to be set */ public void setComicName(String name) { mComicName = name; } /** * Reads unread strips count for the current comic * * @return unread strips count * @throws FileNotFoundException * @throws IOException * @throws JSONException */ public int readOnlyUnread() throws FileNotFoundException, IOException, JSONException { File json = _getJsonFile(); if (!json.exists()) { return 0; } JSONObject root = JsonUtils.jsonRoot(new FileInputStream(json)); if (root.has("mUnread")) { return root.getInt("mUnread"); } return 0; } /** * Helper function to read the properties of this comic from its json file * * @throws FileNotFoundException * @throws IOException * @throws JSONException */ public void readProperties() throws FileNotFoundException, IOException, JSONException { File json = _getJsonFile(); if (!json.exists()) { _readOldFavs(); _readOldPrevSession(); return; } Log.d(TAG, "Reading comic properties from " + json.getPath()); JSONObject root = JsonUtils.jsonRoot(new FileInputStream(json)); if (root.has("mLatestUid")) { mLatestUid = root.getString("mLatestUid"); } if (root.has("mFirstUid")) { mFirstUid = root.getString("mFirstUid"); } if (root.has("mPrevSessionUid")) { mPrevSessionUid = root.getString("mPrevSessionUid"); } if (root.has("mDefZoom")) { mDefZoom = (float) root.getDouble("mDefZoom"); } if (root.has("mStrips")) { JSONArray arr = root.getJSONArray("mStrips"); int len = arr.length(); for (int i = 0; i < len; ++i) { Strip s = Strip.readFromJsonObject(arr.getJSONObject(i)); mStrips.put(s.uid(), s); if (s.isFavorite()) { mFavs.add(s.uid()); } } } Log.d(TAG, "Successfully read comic properties ..."); } /** * For backwards compatibility * * @throws IOException */ private void _readOldFavs() throws IOException { File old = new File(FileUtils.getComicRoot(), mName); if (!old.exists()) { return; } Log.d(TAG, "Getting old favorites from " + old.getPath()); BufferedReader br = new BufferedReader(new FileReader(old)); String line = br.readLine(); mDefZoom = Float.parseFloat(line); while ((line = br.readLine()) != null) { Strip s = new Strip(line, mCache.cachePath()); s.setAsFavorite(true); Log.d(TAG, line); mStrips.put(line, s); line = br.readLine(); // no more required } br.close(); old.delete(); // this file is no longer required! } /** * For backwards compatibility * * @throws IOException */ private void _readOldPrevSession() throws IOException { File old = new File(FileUtils.getComicRoot(), mName + "_lsu"); if (!old.exists()) { return; } Log.d(TAG, "Getting old previous-session from " + old.getPath()); BufferedReader br = new BufferedReader(new FileReader(old)); String line = br.readLine(); if (line != null) { mPrevSessionUid = line; } br.close(); old.delete(); // this file is no longer required! } /** * Helper function to write the properties of this comic into its json file * * @throws IOException * @throws ComicSDCardFull */ public void writeProperties() throws IOException, ComicSDCardFull { if (mType == TYPE_PREVIEW) { return; } StringBuilder sb = new StringBuilder(); sb.append("{\n"); { int unread = 0; Iterator> itr1 = mStrips.entrySet().iterator(); while (itr1.hasNext()) { Map.Entry e = itr1.next(); if (!e.getValue().isRead()) { unread++; } } sb.append("\"mUnread\":" + unread + ",\n"); if (mLatestUid != null) { sb.append("\"mLatestUid\":\"" + mLatestUid + "\",\n"); } if (mFirstUid != null) { sb.append("\"mFirstUid\":\"" + mFirstUid + "\",\n"); } if (mPrevSessionUid != null) { sb.append("\"mPrevSessionUid\":\"" + mPrevSessionUid + "\",\n"); } sb.append("\"mDefZoom\":\"" + mDefZoom + "\",\n"); sb.append("\"mStrips\": [\n"); Iterator> itr = mStrips.entrySet().iterator(); while (itr.hasNext()) { Map.Entry e = itr.next(); e.getValue().toJsonString(sb); if (itr.hasNext()) { sb.append(","); } sb.append("\n"); } sb.append("]\n"); } sb.append("}\n"); FileUtils.storeString(sb.toString(), _getJsonFile()); Log.d(TAG, "Stored comic properties to the file '" + _getJsonFile().getPath() + "'"); } /** * Returns the default value of zoom * * @return zoom value */ public float getDefaultZoom() { return mDefZoom; } /** * Set the default value of zoom * * @param zoom * zoom value */ public void setDefaultZoom(float zoom) { Log.d(TAG, "Setting default zoom to " + zoom); mDefZoom = zoom; } /** * Tells whether the comic has any previous sessions or not * * @return true if it has, else false */ public boolean hasPreviousSession() { return (mPrevSessionUid != null); } /** * Sets the current strip as favorite or not * * @param val * true if it is to be set as favorite */ public void setCurrentAsFavorite(boolean val) { if (mCurrent != null) { mCurrent.setAsFavorite(val); if (val) { mFavs.add(mCurrent.uid()); } else { mFavs.remove(mCurrent.uid()); } } } /** * Whether the current comic is favorite or not * * @return true if it is */ public boolean isCurrentFavorite() { if (mCurrent == null) { return false; } return mCurrent.isFavorite(); } /** * Whether the current comic has image text or not * * @return true if it is */ public boolean currentHasImageText() { if (mCurrent == null) { return false; } return mCurrent.hasText(); } public boolean containsFavs(){ return mFavs.size() > 0 ? true : false; } /** * Creates a valid file name out of the strip's title * * @return valid file name */ public String currentTitleAsValidFilename() { if (mCurrent == null) { return null; } return mCurrent.currentTitleAsValidFilename(); } /** * Helper function to clear the cache */ public void clearCache() { mCache.clearCache(); } /** * Clears all the history associated with this comic */ public void clearHistory() { mStrips.clear(); mLatestUid = null; } // //// Strip navigation ////// /** * Helper function to provide unified interface for navigation among the * strips * * @param type * navigation type * @return strip * @throws ComicException */ public Strip navigateStrip(int type) throws ComicException { mCache.makeSpace(); if (mType == TYPE_FAVORITE) { switch (type) { case NAV_LATEST: mFavIdx = mFavs.size() - 1; return _getFavoriteStrip(); case NAV_FIRST: mFavIdx = 0; return _getFavoriteStrip(); case NAV_NEXT: ++mFavIdx; mFavIdx = (mFavIdx >= mFavs.size()) ? 0 : mFavIdx; return _getFavoriteStrip(); case NAV_PREVIOUS: --mFavIdx; mFavIdx = (mFavIdx < 0) ? mFavs.size() - 1 : mFavIdx; return _getFavoriteStrip(); case NAV_RANDOM: mFavIdx = RandUtils.getPositiveInt(mFavs.size(), 0); return _getFavoriteStrip(); case NAV_CURRENT: return mCurrent; default: ComicException ce = new ComicException("Bad navigation-type passed: " + type); throw ce; } } switch (type) { case NAV_LATEST: return getLatestStrip(); case NAV_LATEST_FORCE: return getLatestStripForcefully(); case NAV_FIRST: return getFirstStrip(); case NAV_NEXT: return getNextStrip(); case NAV_PREVIOUS: return getPreviousStrip(); case NAV_RANDOM: return getRandomStrip(); case NAV_CURRENT: return mCurrent; case NAV_PREV_SESSION: return getPreviousSessionStrip(); default: ComicException ce = new ComicException("Bad navigation-type passed: " + type); throw ce; } } /** * Gets latest strip * * @return latest strip */ public Strip getLatestStrip() { if ((mLatestUid == null) || !mCacheEnabled) { Log.d(TAG, "Latest uid is null (or bg-caching is disabled) , calling getlatestStripUrl..."); mLatestUid = getLatestStripUrl(); } return _querySetCurrentUid(mLatestUid); } /** * Gets latest strip forcefully (used during background caching) * * @return latest strip */ public Strip getLatestStripForcefully() { mLatestUid = getLatestStripUrl(); return _querySetCurrentUid(mLatestUid); } /** * Gets the first strip * * @return first strip */ public Strip getFirstStrip() { mFirstUid = getFirstStripUrl(); return _querySetCurrentUid(mFirstUid); } /** * Gets the random strip * * @return random strip */ public Strip getRandomStrip() { String r = getRandomStripUrl(); return _querySetCurrentUid(r); } /** * Gets the next strip * * @return next strip */ public Strip getNextStrip() { if (isCurrentLatestStrip()) { return mCurrent; } if (!mCurrent.hasNext()) { mCurrent.setNext(getNextStripUrl()); } String uid = mCurrent.uid(); Strip s = _querySetCurrentUid(mCurrent.getNext()); s.setPrevious(uid); return s; } /** * Gets the previous strip * * @return previous strip */ public Strip getPreviousStrip() { if (isCurrentFirstStrip()) { return mCurrent; } if (!mCurrent.hasPrevious()) { mCurrent.setPrevious(getPreviousStripUrl()); } String uid = mCurrent.uid(); Strip s = _querySetCurrentUid(mCurrent.getPrevious()); s.setNext(uid); return s; } /** * Gets the previous session strip * * @return previous session strip */ public Strip getPreviousSessionStrip() { if (mPrevSessionUid == null) { return null; } return _querySetCurrentUid(mPrevSessionUid); } /** * Gets the strip from the given url * * @param url * url * @return strip * @throws ComicSDCardFull * @throws IOException * @throws URISyntaxException * @throws ClientProtocolException */ public Strip getStripFromUrl(String url) throws ClientProtocolException, URISyntaxException, IOException, ComicSDCardFull { Strip s = _querySetCurrentUid(url); s.downloadImage(this); return s; } /** * Download the current strip * * @throws ClientProtocolException * @throws URISyntaxException * @throws IOException * @throws ComicSDCardFull */ public void downloadCurrentStrip() throws ClientProtocolException, URISyntaxException, IOException, ComicSDCardFull { if (mCurrent == null) { return; } mCurrent.downloadImage(this); } // //// Strip navigation ////// // //// List of all abstract methods of this class ////// /** * Get the main page url for the current comic series * * @return desired url */ public abstract String getComicWebPageUrl(); /** * Returns the bound for the current comic * * @return bound */ public abstract Bound getBound(); /** * Type of dialog to be used for choosing comics * * @return int */ public abstract int dialogType(); /** * Gets the url for the latest strip * * @return url */ protected abstract String getLatestStripUrl(); /** * Gets the url for the first strip * * @return url */ protected abstract String getFirstStripUrl(); /** * Gets the url for the next strip * * @return url */ protected abstract String getNextStripUrl(); /** * Gets the url for the previous strip * * @return url */ protected abstract String getPreviousStripUrl(); /** * Gets the url for the previous strip * * @return url */ protected abstract String getRandomStripUrl(); /** * Gives out the list of urls (can be regexes) which are not supposed to be * cached using 'mStrips' * * @return list */ protected abstract String[] urlsNotForCaching(); // //// List of all abstract methods of this class ////// // //// protected methods ////// /** * Checks whether the current strip is latest strip or not * * @return true if it is */ protected boolean isCurrentLatestStrip() { if (mLatestUid == null) { mLatestUid = getLatestStripUrl(); } return mLatestUid.equals(mCurrent.uid()); } /** * Checks whether the current strip is first strip or not * * @return true if it is */ protected boolean isCurrentFirstStrip() { if (mFirstUid == null) { mFirstUid = getFirstStripUrl(); } return mFirstUid.equals(mCurrent.uid()); } /** * Query whether the uid already exists in the history * * @param uid * uid * @return true if it is, else false */ protected boolean hasUid(String uid) { return mStrips.containsKey(uid); } /** * Helper function to query for a strip and create if it doesn't exist * * @param uid * key to be queried * @return strip */ protected Strip queryUid(String uid) { if (mStrips.containsKey(uid)) { return mStrips.get(uid); } if (uid == null) { } Strip s = new Strip(uid, mCache.cachePath()); String[] urls = urlsNotForCaching(); if (urls != null) { for (String url : urls) { if (uid.matches(url) || uid.equals(url)) { Log.d(TAG, "UID=" + uid + " matches with the regex/url=" + url + ". So not caching this strip"); File f = new File(s.getImagePath()); if (f.exists()) { f.delete(); } return s; } } } if (uid != null) { mStrips.put(uid, s); } return s; } // //// protected methods ////// // //// private methods ////// /** * Helper function to query for a strip and set it as current * * @param uid * key to be queried * @return strip */ private Strip _querySetCurrentUid(String uid) { mCurrent = queryUid(uid); if (mType != TYPE_CACHING) { mPrevSessionUid = uid; } Log.d(TAG, "Current UID = " + uid); return mCurrent; } /** * Returns the current favorite strip * * @return strip */ private Strip _getFavoriteStrip() { String uid = mFavs.get(mFavIdx); return _querySetCurrentUid(uid); } /** * Helper function to return the properties json file associated with this * comic * * @return json file */ private File _getJsonFile() { File f = new File(FileUtils.getComicRoot(), PROPS); f.mkdirs(); return new File(f, mName + ".json"); } // //// private methods ////// }