Android Open Source - feedhive D B Manager Activity






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/*from   w w w. j a  v  a 2s  .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;

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

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.Comparator;

import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.res.Configuration;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.TextView;
import free.yhc.feeder.UiHelper.OnConfirmDialogAction;
import free.yhc.feeder.db.ColumnChannel;
import free.yhc.feeder.db.DB;
import free.yhc.feeder.db.DBPolicy;
import free.yhc.feeder.model.Environ;
import free.yhc.feeder.model.Err;
import free.yhc.feeder.model.RTTask;
import free.yhc.feeder.model.UnexpectedExceptionHandler;
import free.yhc.feeder.model.Utils;

public class DBManagerActivity extends Activity implements
UnexpectedExceptionHandler.TrackedModule {
    private static final boolean DBG = false;
    private static final Utils.Logger P = new Utils.Logger(DBManagerActivity.class);

    public static final String KEY_DB_UPDATED = "dbUpdated";

    private static final long  ID_ALL_CHANNEL   = -1;
    private static final int   POS_ALL_CHANNEL  = -1;

    private final DBPolicy      mDbp = DBPolicy.get();
    private final RTTask        mRtt = RTTask.get();

    private String mExDBFilePath = null;
    private String mInDBFilePath = null;
    private DBInfo mDbInfo       = new DBInfo();

    private static class DBInfo {
        int         sz;    // db file sz (KB)
        ChannInfo[] channs = new ChannInfo[0];
        Comparator<ChannInfo> channInfoComparator = new Comparator<ChannInfo>() {
            @Override
            public int compare(ChannInfo ci0, ChannInfo ci1) {
                // Descending order
                if (ci0.nrItmes < ci1.nrItmes)
                    return 1;
                else if (ci0.nrItmes > ci1.nrItmes)
                    return -1;
                else
                    return 0;
            }
        };

        static class ChannInfo {
            long    id;
            String  title;
            int     nrItmes; // items of this channel.
        }
    }

    private static class ChannInfoAdapter extends ArrayAdapter<DBInfo.ChannInfo> {
        private int _mResId;
        ChannInfoAdapter(Context context, int resId, DBInfo.ChannInfo[] data) {
            super(context, resId, data);
            _mResId = resId;
        }

        @Override
        public View
        getView(int position, View convertView, ViewGroup parent) {
            View v = convertView;
            if (null == v) {
                LayoutInflater li = (LayoutInflater)Environ.getAppContext()
                                                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                v = li.inflate(_mResId, null);
            }
            DBInfo.ChannInfo e = getItem(position);
            ((TextView)v.findViewById(R.id.chann_name)).setText(e.title);
            ((TextView)v.findViewById(R.id.nr_items)).setText("" + e.nrItmes);
            return v;
        }
    }

    /**
     * IMPORTANT : This function should be run on main UI thread!
     *
     * @return
     */
    private boolean
    getExclusiveDBAccess() {
        // There is BGtask for updating channel or there is active scheduled update instance.
        // That means, DB is in use!
        // And there isn't any other use case that db is in use except for these two cases.
        //
        // NOTE
        // Is there any other use case?
        // I'm not sure, but these two are enough I think.
        //
        // To avoid race condition this function should be run on main UI thread!
        if (ScheduledUpdateService.doesInstanceExist()
            || mRtt.getChannelsUpdating().length > 0)
            return false;

        // To get exclusive access right to DB, disabling scheduled updater service is enough
        // NOTE
        // Is there any other use case to consider???
        ScheduledUpdateService.disable();
        return true;
    }

    /**
     * Should be run on main UI thread!
     * @return
     */
    private void
    putExclusiveDBAccess() {
        ScheduledUpdateService.enable();
    }

    private Err
    verifyCandidateDB(File dbf) {
        SQLiteDatabase db = null;
        try {
            db = SQLiteDatabase.openDatabase(dbf.getAbsolutePath(), null, SQLiteDatabase.OPEN_READONLY);
        } catch (SQLiteException e0) {
            return Err.DB_UNKNOWN;
        }
        return DB.verifyDB(db);
    }

    /**
     * Only 'export DB' and 'import DB' requires exclusive DB access.
     * Because these two cases exceptionally uses 'File' operations (NOT SQLite DB operation),
     *   Synchronization supported SQLiteDatabase module, isn't used.
     * So, race-condition of file-system-level may be issued.
     */
    private void
    exportDBAsync() {
        if (!getExclusiveDBAccess()) {
            UiHelper.showTextToast(this, R.string.warn_db_in_use);
            return;
        }

        DiagAsyncTask.Worker exportWork = new DiagAsyncTask.Worker() {
            @Override
            public Err
            doBackgroundWork(DiagAsyncTask task) {
                try {
                    FileInputStream fis = new FileInputStream(new File(mInDBFilePath));
                    FileOutputStream fos = new FileOutputStream(new File(mExDBFilePath));
                    Utils.copy(fos, fis);
                    fis.close();
                    fos.close();
                } catch (Exception e) {
                    return Err.IO_FILE;
                }
                return Err.NO_ERR;
            }

            @Override
            public void
            onPostExecute(DiagAsyncTask task, Err result) {
                if (result != Err.NO_ERR)
                    UiHelper.showTextToast(DBManagerActivity.this, result.getMsgId());

                putExclusiveDBAccess();
            }

            @Override
            public void
            onCancelled(DiagAsyncTask task) {
                putExclusiveDBAccess();
            }
        };

        DiagAsyncTask task = new DiagAsyncTask(this,
                                               exportWork,
                                               DiagAsyncTask.Style.SPIN,
                                               R.string.exporting);
        task.run();
    }

    private void
    actionExportDB() {
        CharSequence title = getResources().getText(R.string.exportdb);
        CharSequence msg = getResources().getText(R.string.database) + " => " + mExDBFilePath;
        UiHelper.buildConfirmDialog(this, title, msg, new OnConfirmDialogAction() {
            @Override
            public void onOk(Dialog dialog) {
                exportDBAsync();
            }
            @Override
            public void onCancel(Dialog dialog) { }
        }).show();
    }

    /**
     * NOTE
     * This function should be implemented with great care.
     * Corrupting database file may make application be unavailable forever before
     *   all database is deleted.
     *
     * See {@link #exportDBAsync()} for more comments.
     */
    private void
    importDBAsync() {
        final String inDBBackupSuffix = "______backup";
        final File exDbf = new File(mExDBFilePath);
        if (!exDbf.exists()) {
            UiHelper.showTextToast(this, R.string.warn_exdb_access_denied);
            return;
        }

        switch(verifyCandidateDB(exDbf)) {
        case VERSION_MISMATCH:
            UiHelper.showTextToast(this, R.string.warn_db_version_mismatch);
            return;

        case NO_ERR:
            break;

        default:
            UiHelper.showTextToast(this, R.string.warn_exdb_not_compatible);
            return;
        }

        if (!getExclusiveDBAccess()) {
            UiHelper.showTextToast(this, R.string.warn_db_in_use);
            return;
        }

        final File inDbf = new File(mInDBFilePath);
        final File inDbfBackup = new File(inDbf.getAbsoluteFile() + inDBBackupSuffix);
        if (!inDbf.renameTo(inDbfBackup)) {
            UiHelper.showTextToast(this, Err.IO_FILE.getMsgId());
            putExclusiveDBAccess();
            return;
        }

        DiagAsyncTask.Worker importWork = new DiagAsyncTask.Worker() {
            @Override
            public Err
            doBackgroundWork(DiagAsyncTask task) {
                try {
                    FileInputStream fis = new FileInputStream(exDbf);
                    FileOutputStream fos = new FileOutputStream(inDbf);
                    Utils.copy(fos, fis);
                    fis.close();
                    fos.close();
                } catch (Exception e) {
                    return Err.IO_FILE;
                }

                mDbp.reloadDatabase();

                return Err.NO_ERR;
            }

            @Override
            public void
            onPostExecute(DiagAsyncTask task, Err result) {
                if (result == Err.NO_ERR) {
                    // All are done successfully!
                    // Delete useless backup file!
                    inDbfBackup.delete();
                    onDBChanged(ID_ALL_CHANNEL, 0);
                } else {
                    if (inDbfBackup.renameTo(inDbf))
                        UiHelper.showTextToast(DBManagerActivity.this, Err.DB_CRASH.getMsgId());
                    else
                        UiHelper.showTextToast(DBManagerActivity.this, Err.IO_FILE.getMsgId());
                }
                putExclusiveDBAccess();
            }

            @Override
            public void
            onCancelled(DiagAsyncTask task) {
                putExclusiveDBAccess();
            }
        };

        DiagAsyncTask task = new DiagAsyncTask(this,
                                               importWork,
                                               DiagAsyncTask.Style.SPIN,
                                               R.string.importing);
        task.run();
    }

    private void
    actionImportDB() {
        CharSequence title = getResources().getText(R.string.importdb);
        CharSequence msg = getResources().getText(R.string.database) + " <= " + mExDBFilePath;
        UiHelper.buildConfirmDialog(this, title, msg, new OnConfirmDialogAction() {
            @Override
            public void
            onOk(Dialog dialog) {
                importDBAsync();
            }
            @Override
            public void onCancel(Dialog dialog) { }
        }).show();
    }

    /**
     *
     * @param cid
     *   ID_ALL_CHANNEL means 'for all channel' - that is for whole DB.
     * @param percent
     */
    private void
    shrinkItemsAsync(final long cid, final int percent) {
        DiagAsyncTask.Worker shrinkWork = new DiagAsyncTask.Worker() {
            private int nr = 0;
            @Override
            public Err
            doBackgroundWork(DiagAsyncTask task) {
                nr = mDbp.deleteOldItems(cid, percent);
                return Err.NO_ERR;
            }

            @Override
            public void
            onPostExecute(DiagAsyncTask task, Err result) {
                UiHelper.showTextToast(DBManagerActivity.this,
                                       nr + " " + getResources().getText(R.string.nr_deleted_items_noti));
                onDBChanged(cid, nr);
            }

            @Override
            public void
            onCancel(DiagAsyncTask task) {
                eAssert(false);
            }
        };
        DiagAsyncTask task = new DiagAsyncTask(this,
                                               shrinkWork,
                                               DiagAsyncTask.Style.SPIN,
                                               R.string.deleting);
        task.run();
    }

    /**
    * @param position
    *   '>= 0' for specific channel located at given position, ID_ALL_CHANNEL for shrinking entire item table.
    * @param anchor
    */
    private void
    actionShrinkDB(final int position, View anchor) {
        PopupMenu popup = new PopupMenu(this, anchor);
        popup.getMenuInflater().inflate(R.menu.popup_dbmgmt_shrink, popup.getMenu());
        popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                int delPercent = 0;
                switch (item.getItemId()) {
                case R.id.all:
                    delPercent = 100;
                    break;
                case R.id.half:
                    delPercent = 50;
                    break;
                case R.id.per30:
                    delPercent = 30;
                    break;
                }
                if (POS_ALL_CHANNEL == position)
                    shrinkItemsAsync(ID_ALL_CHANNEL, delPercent);
                else
                    shrinkItemsAsync(mDbInfo.channs[position].id, delPercent);
                return true;
            }
        });
        popup.show();
    }

    private void
    loadChannInfoListAsync() {
        DiagAsyncTask.Worker loadDBInfoWork = new DiagAsyncTask.Worker() {
            @Override
            public Err
            doBackgroundWork(DiagAsyncTask task) {
                // Load 'used channel information'
                Cursor c = mDbp.queryChannel(new ColumnChannel[] { ColumnChannel.ID,
                                                                   ColumnChannel.TITLE });

                mDbInfo.channs = new DBInfo.ChannInfo[c.getCount()];
                c.moveToFirst();
                for (int i = 0; i < mDbInfo.channs.length; i++) {
                    mDbInfo.channs[i] = new DBInfo.ChannInfo();
                    mDbInfo.channs[i].id = c.getLong(0);
                    mDbInfo.channs[i].nrItmes = mDbp.getChannelInfoNrItems(c.getLong(0));
                    mDbInfo.channs[i].title = c.getString(1);
                    c.moveToNext();
                }
                c.close();

                // sorting by number of items
                Arrays.sort(mDbInfo.channs, mDbInfo.channInfoComparator);
                return Err.NO_ERR;
            }

            @Override
            public void
            onPostExecute(DiagAsyncTask task, Err result) {
                activateChannelInfoListView(true);
                ListView lv = (ListView)DBManagerActivity.this.findViewById(R.id.list);
                ChannInfoAdapter adapter = new ChannInfoAdapter(DBManagerActivity.this, R.layout.db_manager_channel_row, mDbInfo.channs);
                lv.setAdapter(adapter);
                lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                    @Override
                    public void
                    onItemClick(AdapterView<?> parent, View view, int position, long itemId) {
                        actionShrinkDB(position, view);
                    }
                });
            }
        };
        DiagAsyncTask task = new DiagAsyncTask(this,
                                               loadDBInfoWork,
                                               DiagAsyncTask.Style.SPIN,
                                               R.string.analyzing_db);
        task.run();
    }

    /**
     *
     * @param cid
     *   ID_ALL_CHANNEL for all channels - entire item table.
     * @param nrDeleted
     */
    private void
    onDBChanged(long cid, int nrDeleted) {
        mDbInfo.sz = (int)(new File(mInDBFilePath).length() / 1024);
        CharSequence text = getResources().getText(R.string.db_size) + " : " + mDbInfo.sz + " KB";
        ((TextView)findViewById(R.id.total_dbsz)).setText(text);
        if (ID_ALL_CHANNEL == cid)
            activateChannelInfoListView(false);
        else {
            for (DBInfo.ChannInfo ci : mDbInfo.channs) {
                if (ci.id == cid)
                    ci.nrItmes -= nrDeleted;
            }
            Arrays.sort(mDbInfo.channs, mDbInfo.channInfoComparator);
            ((ChannInfoAdapter)((ListView)findViewById(R.id.list)).getAdapter()).notifyDataSetChanged();
        }
    }

    private void
    activateChannelInfoListView(boolean activate) {
        if (activate) {
            findViewById(R.id.channinfo_list).setVisibility(View.VISIBLE);
            findViewById(R.id.per_chann_mgmt).setVisibility(View.GONE);
            int nrItems = 0;
            for (DBInfo.ChannInfo ci : mDbInfo.channs)
                nrItems += ci.nrItmes;
            ((TextView)findViewById(R.id.nr_all_items)).setText(
                    getResources().getText(R.string.nr_all_items) + " : " + nrItems);

        } else {
            findViewById(R.id.channinfo_list).setVisibility(View.GONE);
            findViewById(R.id.per_chann_mgmt).setVisibility(View.VISIBLE);
        }
    }

    @Override
    public String
    dump(UnexpectedExceptionHandler.DumpLevel lv) {
        return "[ DBManagerActivity ]";
    }

    @Override
    public void
    onCreate(Bundle savedInstanceState) {
        UnexpectedExceptionHandler.get().registerModule(this);
        super.onCreate(savedInstanceState);

        setContentView(R.layout.db_manager);

        mExDBFilePath = Environ.get().getAppRootDirectoryPath()
                        + getResources().getText(R.string.app_name) + ".db";
        mInDBFilePath = getDatabasePath(DB.getDBName()).getAbsolutePath();

        ((Button)findViewById(R.id.exportdb)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                actionExportDB();
            }
        });

        ((Button)findViewById(R.id.importdb)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                actionImportDB();
            }
        });

        ((Button)findViewById(R.id.shrinkdb)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                actionShrinkDB(POS_ALL_CHANNEL, v);
            }
        });

        ((Button)findViewById(R.id.per_chann_mgmt)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                loadChannInfoListAsync();
            }
        });

        onDBChanged(ID_ALL_CHANNEL, 0);
    }

    @Override
    protected void
    onStart() {
        super.onStart();
    }

    @Override
    protected void
    onResume() {
        super.onResume();
    }

    @Override
    protected void
    onPause() {
        super.onPause();
    }

    @Override
    protected void
    onStop() {
        super.onStop();
    }

    @Override
    protected void
    onDestroy() {
        super.onDestroy();
        UnexpectedExceptionHandler.get().unregisterModule(this);
    }

    @Override
    public void
    onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        // Do nothing!
    }

    @Override
    public void
    onBackPressed() {
        super.onBackPressed();
    }
}




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