Android Open Source - feedhive Contents Manager






From Project

Back to project page feedhive.

License

The source code is released under:

SOFTWARE LICENSE ---------------- Copyright (C) 2012, 2013, 2014 Younghyung Cho. <yhcting77@gmail.com> All rights reserved. This file is part of FeedHive This program is licensed under the FreeBSD l...

If you think the Android project feedhive listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/******************************************************************************
 * Copyright (C) 2012, 2013, 2014/* w  w w . j  a v  a  2  s.c  o  m*/
 * Younghyung Cho. <yhcting77@gmail.com>
 * All rights reserved.
 *
 * This file is part of FeedHive
 *
 * This program is licensed under the FreeBSD license
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation
 * are those of the authors and should not be interpreted as representing
 * official policies, either expressed or implied, of the FreeBSD Project.
 *****************************************************************************/

package free.yhc.feeder.model;

import static free.yhc.feeder.model.Utils.eAssert;

import java.io.File;
import java.util.Iterator;
import java.util.LinkedList;

import android.content.SharedPreferences;
import android.database.Cursor;
import android.preference.PreferenceManager;
import free.yhc.feeder.R;
import free.yhc.feeder.db.ColumnChannel;
import free.yhc.feeder.db.ColumnItem;
import free.yhc.feeder.db.DBPolicy;


//
// Application Contents Manager
//
public class ContentsManager implements
UnexpectedExceptionHandler.TrackedModule {
    private static final boolean DBG = false;
    private static final Utils.Logger P = new Utils.Logger(ContentsManager.class);

    // Current Contents Version
    private static final int CONTENTS_VERSION = 1;

    // Contents are classified by item and data.
    private static final long FLAG_CHAN_DATA = 0x1;
    private static final long FLAG_ITEM_DATA = 0x2;

    private static ContentsManager sInstance = null;

    private final ListenerManager mLm = new ListenerManager();

    // Only for bug reporting.
    private final LinkedList<String>  mWiredChannl = new LinkedList<String>();

    public interface OnContentsUpdatedListener {
        void onContentsUpdated(UpdateType type, Object arg0, Object arg1);
    }

    public enum UpdateType implements ListenerManager.Type {
        CHAN_DATA (FLAG_CHAN_DATA),// arg0 : channel id array
        ITEM_DATA (FLAG_ITEM_DATA);// arg0 : channel id array
        private final long _mFlag;

        private UpdateType(long flag) {
            _mFlag = flag;
        }

        @Override
        public long
        flag() {
            return _mFlag;
        }
    }

    private ContentsManager() {
        int ver = Utils.getPrefContentVersion();
        if (ver < CONTENTS_VERSION)
            upgrade(ver, CONTENTS_VERSION);
    }

    public static ContentsManager
    get() {
        if (null == sInstance)
            sInstance = new ContentsManager();
        return sInstance;
    }


    // ========================================================================
    //
    // UPGRADE
    //
    // ========================================================================
    private static void
    upgradeTo1() {
        File rt = new File(Environ.get().getAppRootDirectoryPath());
        for (File f : rt.listFiles()) {
            if (f.isDirectory()
                && f.getName().matches("^[0-9]+$")) {
                // directory and it's name is 'number' => channel directory.
                // NumberFormatException is NOT expected here!
                long cid = Long.parseLong(f.getName());
                String newDirPath = getChannelDirPath(cid);
                if (null == newDirPath)
                    // This directory doens't have matching channel.
                    // This is garbage.
                    // Remove it!
                    Utils.removeFileRecursive(f, true);
                else
                    // rename to human readable format.
                    f.renameTo(new File(newDirPath));
            }
        }
    }

    private static void
    upgrade(int oldVersion, int newVersion) {
        int dbv = oldVersion;
        while (dbv < newVersion) {
            switch (dbv) {
            case 0:
                upgradeTo1();
                break;

            default:
                // SHOULD NOT reach here.
                Utils.eAssert(false);
            }
            dbv++;
        }

        // update contents version string to shared preference
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(Environ.getAppContext());
        SharedPreferences.Editor prefEd = prefs.edit();
        prefEd.putInt(Utils.getResString(R.string.cscontent_version), CONTENTS_VERSION);
        prefEd.apply();
    }

    @Override
    public String
    dump(UnexpectedExceptionHandler.DumpLevel lv) {
        StringBuilder bldr = new StringBuilder("[ ContentManager ]\n");
        synchronized (mWiredChannl) {
            Iterator<String> itr = mWiredChannl.iterator();
            while (itr.hasNext())
                bldr.append("  Wired Channel : " + itr.next() + "\n");
        }
        return bldr.toString();
    }

    // ========================================================================
    //
    // Update Listener Handling
    //
    // ========================================================================
    private void
    notifyUpdated(final UpdateType type, final Object arg0, final Object arg1) {
        mLm.notifyIndirect(type, arg0, arg1);
    }

    private void
    notifyUpdated(final UpdateType type, final Object arg0) {
        notifyUpdated(type, arg0, null);
    }

    private void
    notifyUpdated(final UpdateType type) {
        notifyUpdated(type, null, null);
    }

    public void
    registerUpdatedListener(ListenerManager.Listener listener, long flag) {
        mLm.registerListener(listener, null, flag);
    }

    public void
    unregisterUpdatedListener(ListenerManager.Listener listener) {
        mLm.unregisterListener(listener);
    }


    // ========================================================================
    //
    // Contents(Directory/File) Management
    //
    // ========================================================================
    /**
     * ChannelDirLink is human-readable directory name.
     * This is for user to access to feed contents easily by using external tools.
     * @param cid
     * @return
     */
    private static String
    getChannelDirPath(long cid) {
        DBPolicy dbp = DBPolicy.get();
        String title = dbp.getChannelInfoString(cid, ColumnChannel.TITLE);
        if (!Utils.isValidValue(title))
            return null;
        String fname = Utils.convertToFilename(title) + "_" + cid;
        return Environ.get().getAppRootDirectoryPath() + fname + "/";
    }

    private static File
    getChannelDirFile(long cid) {
        String path = getChannelDirPath(cid);
        return (null == path)? null: new File(path);
    }

    /**
     * Create channel dir.
     * If @force == true and directory already exists, all files in it are deleted,
     *   and new empty directory will be generated.
     * @param cid
     * @param force
     * @return
     */
    public boolean
    makeChannelDir(long cid, boolean force) {
        File f = getChannelDirFile(cid);
        if (null == f)
            return false; // Channel information is NOT updated yet.
        if (f.exists() && force)
            Utils.removeFileRecursive(f, true);
        return f.mkdir();
    }

    /**
     * This deletes channel directory itself.
     * @param cid
     * @return
     */
    public boolean
    removeChannelDir(long cid) {
        File f = getChannelDirFile(cid);
        if (null == f)
            return false; // Channel information is NOT updated yet.
        boolean ret = Utils.removeFileRecursive(f, true);
        notifyUpdated(UpdateType.CHAN_DATA, new long[] { cid });
        return ret;
    }

    private boolean
    cleanChannelDir(long cid, boolean notify) {
        File f = getChannelDirFile(cid);
        // Channel is NOT successfully updated yet.
        // So, there is no channel directory.
        if (null == f)
            return true;
        boolean ret = Utils.removeFileRecursive(f, false);
        if (notify)
            notifyUpdated(UpdateType.CHAN_DATA, new long[] { cid });
        return ret;
    }

    public boolean
    cleanChannelDir(long cid) {
        return cleanChannelDir(cid, true);
    }

    public boolean
    cleanChannelDirs(long[] cids) {
        boolean ret = true;
        for (long cid : cids)
            cleanChannelDir(cid, false);
        notifyUpdated(UpdateType.CHAN_DATA, cids);
        return ret;
    }

    public void
    cleanAllChannelDirs() {
        Cursor c = DBPolicy.get().queryChannel(ColumnChannel.ID);
        if (!c.moveToFirst()) {
            c.close();
            return;
        }

        long[] cids = new long[c.getCount()];
        int i = 0;
        do {
            cids[i++] = c.getLong(0);
        } while (c.moveToNext());
        c.close();
        cleanChannelDirs(cids);
    }

    public LinkedList<File>
    getContentFiles(long cids[]) {
        LinkedList<File> l = new LinkedList<File>();
        for (long cid : cids)
            Utils.getFilesRecursive(l, getChannelDirFile(cid));
        return l;
    }

    public LinkedList<File>
    getContentFiles() {
        Cursor c = DBPolicy.get().queryChannel(ColumnChannel.ID);
        if (!c.moveToFirst()) {
            c.close();
            return new LinkedList<File>(); // return empty list.
        }

        long[] cids = new long[c.getCount()];
        int i = 0;
        do {
            cids[i++] = c.getLong(0);
        } while (c.moveToNext());
        c.close();
        return getContentFiles(cids);
    }

    /**
     * Get file which contains data for given feed item.
     * Usually, this file is downloaded from internet.
     * (Ex. downloaded web page / downloaded mp3 etc)
     * @param id
     * @return
     */
    public File
    getItemInfoDataFile(long id) {
        return getItemInfoDataFile(id, -1, null, null);
    }

    // NOTE
    // Why this parameter is given even if we can get from DB?
    // This is only for performance reason!
    // postfix : usually, extension;
    /**
     * NOTE
     * Why these parameters - title, url - are given even if we can get from DB?
     * This is only for performance reason!
     * @param id
     *   item id
     * @param cid
     *   channel id of this item. if '< 0', than value read from DB is used.
     * @param title
     *   title of this item. if '== null' or 'isEmpty()', than value read from DB is used.
     * @param url
     *   target url of this item. link or enclosure is possible.
     *   if '== null' or is 'isEmpty()', then value read from DB is used.
     * @return
     */
    public File
    getItemInfoDataFile(long id, long cid, String title, String url) {
        DBPolicy dbp = DBPolicy.get();
        if (cid < 0)
            cid = dbp.getItemInfoLong(id, ColumnItem.CHANNELID);

        String chanDirPath = getChannelDirPath(cid);
        if (null == chanDirPath)
            return null;

        if (!Utils.isValidValue(title))
            title = dbp.getItemInfoString(id, ColumnItem.TITLE);

        if (!Utils.isValidValue(url)) {
            long action = dbp.getChannelInfoLong(cid, ColumnChannel.ACTION);
            String link = dbp.getItemInfoString(id, ColumnItem.LINK);
            String enclosure = dbp.getItemInfoString(id, ColumnItem.ENCLOSURE_URL);
            url = FeedPolicy.getDynamicActionTargetUrl(action, link, enclosure);
        }

        // we don't need to create valid filename with empty url value.
        if (!Utils.isValidValue(url)) {
            synchronized (mWiredChannl) {
                mWiredChannl.add(dbp.getChannelInfoString(cid, ColumnChannel.URL));
            }
            return null;
        }

        String ext = Utils.getExtentionFromUrl(url);

        // Title may include character that is not allowed as file name
        // (ex. '/')
        // Item is id is preserved even after update.
        // So, item ID can be used as file name to match item and file.
        String fname = Utils.convertToFilename(title) + "_" + id;
        int endIndex = Utils.MAX_FILENAME_LENGTH - ext.length() - 1; // '- 1' for '.'
        if (endIndex > fname.length())
            endIndex = fname.length();

        fname = fname.substring(0, endIndex);
        fname = fname + '.' + ext;


        // NOTE
        //   In most UNIX file systems, only '/' and 'null' are reserved.
        //   So, we don't worry about "converting string to valid file name".
        return new File(chanDirPath + fname);
    }

    public long
    getIdFromContentFileName(String fname) {
        // Item data file format
        // <channel dir>/<title string>_<id>.<ext>
        int idot = fname.lastIndexOf('.');
        int iubar = fname.lastIndexOf('_');
        try {
            String idstr = fname.substring(iubar + 1, idot);
            return Long.parseLong(idstr);
        } catch (Exception e) {
            // NumberFormatException, IndexOutOfBoundsException
            return -1;
        }
    }

    public boolean
    addItemContent(File f, long id) {
        File itemContentFile = ContentsManager.get().getItemInfoDataFile(id);
        if (f.getAbsolutePath().equals(itemContentFile.getAbsolutePath())
            || f.renameTo(ContentsManager.get().getItemInfoDataFile(id))) {
            notifyUpdated(UpdateType.ITEM_DATA, new long[] { id });
            return true;
        } else
            return false;
    }

    public int
    deleteItemContents(long ids[]) {
        int failcnt = 0;
        LinkedList<Long> l = new LinkedList<Long>();
        for (long id : ids) {
            File f = getItemInfoDataFile(id);
            eAssert(null != f);
            if (null == f
                || !f.delete())
                ++failcnt;
            else
                l.add(id);
        }
        // Item ids that fails to delete it's content, are ignored intentionally.
        notifyUpdated(UpdateType.ITEM_DATA, Utils.convertArrayLongTolong(l.toArray(new Long[0])));
        return failcnt;
    }

    public boolean
    deleteItemContent(long id) {
        // NOTE
        // There is no use case that "null == f" here.
        File f = getItemInfoDataFile(id);
        eAssert(null != f);
        if (f.delete()) {
            notifyUpdated(UpdateType.ITEM_DATA, new long[] { id });
            return true;
        }
        return false;
    }
}




Java Source Code List

free.yhc.feeder.AppWidgetCategoryChooserActivity.java
free.yhc.feeder.AppWidgetMenuActivity.java
free.yhc.feeder.AppWidgetUpdateCategoryActivity.java
free.yhc.feeder.AsyncAdapter.java
free.yhc.feeder.AsyncCursorAdapter.java
free.yhc.feeder.AsyncCursorListAdapter.java
free.yhc.feeder.ChannelListActivity.java
free.yhc.feeder.ChannelListAdapter.java
free.yhc.feeder.ChannelListFragment.java
free.yhc.feeder.ChannelListPagerAdapter.java
free.yhc.feeder.ChannelSettingActivity.java
free.yhc.feeder.DBManagerActivity.java
free.yhc.feeder.DiagAsyncTask.java
free.yhc.feeder.FeederActivity.java
free.yhc.feeder.FeederApp.java
free.yhc.feeder.FeederPreferenceActivity.java
free.yhc.feeder.FragmentPagerAdapterEx.java
free.yhc.feeder.ItemListActivity.java
free.yhc.feeder.ItemListAdapter.java
free.yhc.feeder.ItemViewActivity.java
free.yhc.feeder.LifeSupportService.java
free.yhc.feeder.NotiManager.java
free.yhc.feeder.PredefinedChannelActivity.java
free.yhc.feeder.ScheduledUpdateService.java
free.yhc.feeder.UiHelper.java
free.yhc.feeder.appwidget.AppWidgetUtils.java
free.yhc.feeder.appwidget.Provider.java
free.yhc.feeder.appwidget.UpdateService.java
free.yhc.feeder.appwidget.ViewsFactory.java
free.yhc.feeder.appwidget.ViewsService.java
free.yhc.feeder.db.ColumnCategory.java
free.yhc.feeder.db.ColumnChannel.java
free.yhc.feeder.db.ColumnItem.java
free.yhc.feeder.db.DBPolicy.java
free.yhc.feeder.db.DB.java
free.yhc.feeder.model.AssetSQLiteHelper.java
free.yhc.feeder.model.AtomParser.java
free.yhc.feeder.model.BGTaskDownloadToFile.java
free.yhc.feeder.model.BGTaskDownloadToItemContent.java
free.yhc.feeder.model.BGTaskManager.java
free.yhc.feeder.model.BGTaskUpdateChannel.java
free.yhc.feeder.model.BGTask.java
free.yhc.feeder.model.BaseBGTask.java
free.yhc.feeder.model.ContentsManager.java
free.yhc.feeder.model.DelayedAction.java
free.yhc.feeder.model.Environ.java
free.yhc.feeder.model.Err.java
free.yhc.feeder.model.FeedParser.java
free.yhc.feeder.model.FeedPolicy.java
free.yhc.feeder.model.Feed.java
free.yhc.feeder.model.FeederException.java
free.yhc.feeder.model.HtmlParser.java
free.yhc.feeder.model.ItemActionHandler.java
free.yhc.feeder.model.KeyBasedLinkedList.java
free.yhc.feeder.model.ListenerManager.java
free.yhc.feeder.model.NetLoader.java
free.yhc.feeder.model.RSSParser.java
free.yhc.feeder.model.RTTask.java
free.yhc.feeder.model.ThreadEx.java
free.yhc.feeder.model.UnexpectedExceptionHandler.java
free.yhc.feeder.model.UsageReport.java
free.yhc.feeder.model.Utils.java