Android Open Source - netmbuddy D B Manager






From Project

Back to project page netmbuddy.

License

The source code is released under:

------- Default ------- Copyright (C) 2012, 2013, 2014 Younghyung Cho. <yhcting77@gmail.com> All rights reserved. This file is part of NetMBuddy This program is licensed under the FreeBSD license R...

If you think the Android project netmbuddy 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/* ww  w.j  a  v  a2 s. c  om*/
 * Younghyung Cho. <yhcting77@gmail.com>
 * All rights reserved.
 *
 * This file is part of NetMBuddy
 *
 * 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.netmbuddy.db;

import static free.yhc.netmbuddy.utils.Utils.eAssert;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import free.yhc.netmbuddy.R;
import free.yhc.netmbuddy.db.DB.Err;
import free.yhc.netmbuddy.db.DBHistory.FieldNType;
import free.yhc.netmbuddy.model.Policy;
import free.yhc.netmbuddy.utils.Utils;

class DBManager {
    private static final boolean DBG = false;
    private static final Utils.Logger P = new Utils.Logger(DBManager.class);

    private static final String sTableAndroidMetadata = "android_metadata";
    private static final String sStmtAndroidMetadata = "CREATE TABLE android_metadata (locale TEXT)";

    private static final Pattern sPFieldDef     = Pattern.compile("[^(]+\\((.+)\\)\\s*");
    private static final Pattern sPTokenGroup   = Pattern.compile("(\\s*([^,]+)).*");
    private static final Pattern sPFieldNType   = Pattern.compile("([^\\s]+)\\s+([^\\s]+).*");

    private static final String[] sFieldNameNotAllowed = new String[] {
        "foreign",
        "key",
        "select",
        "from",
    };


    private static HashMap<String, String>
    extractFieldAndType(String schemaString) {
        HashMap<String, String> map = new HashMap<String, String>();

        Matcher m = sPFieldDef.matcher(schemaString);
        String str;
        if (m.matches())
            str = m.group(1);
        else
            return map;

        while (true) {
            m = sPTokenGroup.matcher(str);
            if (!m.matches())
                break;

            String group = m.group(1);
            String token = m.group(2);

            m = sPFieldNType.matcher(token);
            if (!m.matches())
                break;

            if (2 != m.groupCount())
                break;

            // group(1) is field, group(2) is type.
            String field = m.group(1);
            String type = m.group(2);

            boolean allowed = true;
            for (String s : sFieldNameNotAllowed) {
                if (s.equalsIgnoreCase(field)) {
                    allowed = false;
                    break;
                }
            }

            if (allowed)
                // ignore field that is not allowed.
                map.put(field, type);

            // update 'str' to move to next token group.
            str = str.replace(group, "");
            if (!str.startsWith(","))
                break; // end of token.
            str = str.substring(1); // remove leading ','
        }
        return map;
    }

    private static Err
    copy(File fDst, File fSrc) {
        Err err = Err.NO_ERR;
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(fSrc);
            fos = new FileOutputStream(fDst);
            Utils.copy(fos, fis);
        } catch (FileNotFoundException e) {
            err = Err.IO_FILE;
        } catch (InterruptedException e) {
            // Unexpected interrupt!!
            err = Err.INTERRUPTED;
        } catch (IOException e) {
            err = Err.IO_FILE;
        } finally {
            try {
                if (null != fis)
                    fis.close();
                if (null != fos)
                    fos.close();
            } catch (IOException ignored) { }
        }
        return err;
    }

    // NOTE
    // [ Algorithm ]
    // Condition to pass
    // - android_metadata, playlist and video table exist at DB
    // - fields and it's types for playlist and video table, matches exactly with DB history (see above)
    //
    // I think this is enough to verify DB...
    private static Err
    verifyDB(SQLiteDatabase db) {
        if (db.getVersion() < 0
            || DB.getVersion() < db.getVersion())
            return Err.INVALID_DB; // trivial case

        Cursor c = db.query("sqlite_master",
                            new String[] {"name", "sql"},
                            "type = 'table'",
                            null, null, null, null);

        HashMap<String, String> map = new HashMap<String, String>();
        if (c.moveToFirst()) {
            do {
                // Key : table name, Value : sql text
                map.put(c.getString(0), c.getString(1));
            } while (c.moveToNext());
        }
        c.close();

        // Verify
        // Check android metadata.
        String stmt = map.get(sTableAndroidMetadata);
        if (null == stmt || !stmt.equals(sStmtAndroidMetadata))
            return Err.INVALID_DB;

        // field and type map.
        // DB version starts from 1. And array index starts from 0.
        int dbVersion = db.getVersion();
        FieldNType[][] ftHistory = DBHistory.sFieldNType[dbVersion - 1];
        eAssert(DBHistory.sTables.length == ftHistory.length);
        for (int i = 0; i < DBHistory.sTables.length; i++) {
            stmt = map.get(DBHistory.sTables[i]);
            if (null == stmt)
                return Err.INVALID_DB;

            HashMap<String, String> ftmap = extractFieldAndType(stmt);
            if (ftmap.size() != ftHistory[i].length)
                return Err.INVALID_DB;

            for (FieldNType ft : ftHistory[i]) {
                String type = ftmap.get(ft.field);
                if (null == type
                    || !type.equalsIgnoreCase(ft.type))
                    return Err.INVALID_DB;
            }
        }
        return Err.NO_ERR;
    }


    private static Err
    verifyExternalDBFile(File exDbf) {
        Err err = Err.INVALID_DB;
        try {
            if (!exDbf.canRead())
                return Err.IO_FILE;

            SQLiteDatabase exDb = null;
            try {
                exDb = SQLiteDatabase.openDatabase(exDbf.getAbsolutePath(), null, SQLiteDatabase.OPEN_READONLY);
            } catch (SQLiteException e) {
                return Err.INVALID_DB;
            }

            err = verifyDB(exDb);
            exDb.close();
        } catch (Exception e) {
            err = Err.INVALID_DB;
        }
        return err;
    }

    private static Err
    copyAndUpgrade(File tempExDb, File exDbf) {
        Err err = copy(tempExDb, exDbf);
        if (Err.NO_ERR != err) {
            tempExDb.delete();
            return err;
        }

        SQLiteDatabase db = null;
        try {
            db = SQLiteDatabase.openDatabase(tempExDb.getAbsolutePath(), null, SQLiteDatabase.OPEN_READWRITE);
            if (!new DBUpgrader(db, db.getVersion(), DB.getVersion()).upgrade())
                return Err.INVALID_DB;
        } catch (SQLiteException e) {
            return Err.INVALID_DB;
        } finally {
            if (null != db)
                db.close();
        }

        return Err.NO_ERR;
    }

    /**
     * Extremely critical function.
     * PREREQUISITE
     *   All operations that might access DB, SHOULD BE STOPPED
     *     before importing DB.
     *   And that operation should be resumed after importing DB.
     * @param exDbf
     */
    static Err
    importDatabase(File exDbf) {
        Err err = verifyExternalDBFile(exDbf);
        if (err != Err.NO_ERR)
            return err;

        // External DB is verified.
        // Let's do real importing.
        DB.get().close();
        try {
            File inDbf = Utils.getAppContext().getDatabasePath(DB.getName());
            File inDbfBackup = new File(inDbf.getAbsolutePath() + "____backup");

            if (!inDbf.renameTo(inDbfBackup)) {
                DB.get().open();
                return Err.IO_FILE;
            }

            err = copy(inDbf, exDbf);
            if (Err.NO_ERR != err) {
                // Restore it
                inDbf.delete();
                inDbfBackup.renameTo(inDbf);
            } else {
                inDbfBackup.delete();
            }
        } finally {
            // Open imported new DB
            DB.get().open();
        }
        return err;
    }


    private static Err
    doMergeDatabase(SQLiteDatabase exDb) {
        DB db = DB.get();
        Cursor excPl = exDb.query(DB.getPlaylistTableName(),
                                  DBUtils.getColNames(ColPlaylist.values()),
                                  null, null, null, null, null);
        if (!excPl.moveToFirst()) {
            // Empty DB.
            // So, nothing to merge!
            excPl.close();
            return Err.NO_ERR;
        }

        final int plColiTitle = excPl.getColumnIndex(ColPlaylist.TITLE.getName());
        final int plColiId = excPl.getColumnIndex(ColPlaylist.ID.getName());
        do {
            int i = 0;
            String plTitle = excPl.getString(plColiTitle);
            while (db.containsPlaylist(plTitle)) {
                i++;
                plTitle = excPl.getString(plColiTitle)+ "_" + Utils.getResString(R.string.merge) + i;
            }

            // Playlist title is chosen.
            ContentValues cvs = DBUtils.copyContent(excPl, ColPlaylist.values());
            cvs.put(ColPlaylist.TITLE.getName(), plTitle);
            cvs.put(ColPlaylist.SIZE.getName(), 0);
            long inPlid = db.insertPlaylist(cvs);

            // Scan all video references belongs to this playlist
            Cursor excVref = exDb.query(DB.getVideoRefTableName(excPl.getLong(plColiId)),
                    DBUtils.getColNames(new ColVideoRef[] { ColVideoRef.VIDEOID }),
                          null, null, null, null, null);

            if (!excVref.moveToFirst()) {
                // Empty playlist! Let's move to next.
                excVref.close();
                continue;
            }

            do {
                // get Youtube video id string of external database.
                Cursor excV = exDb.query(DB.getVideoTableName(),
                                         DBUtils.getColNames(ColVideo.values()),
                                         ColVideo.ID.getName() + " = " + excVref.getLong(0),
                                         null, null, null, null);
                if (!excV.moveToFirst())
                    eAssert(false);

                final int vColiVid = excV.getColumnIndex(ColVideo.VIDEOID.getName());

                Long vid = (Long)db.getVideoInfo(excV.getString(vColiVid), ColVideo.ID);
                if (null == vid) {
                    // This is new video!
                    cvs = DBUtils.copyContent(excV, ColVideo.values());
                    cvs.put(ColVideo.REFCOUNT.getName(), 0);
                    vid = db.insertVideo(cvs);
                }
                db.insertVideoRef(inPlid, vid);

            } while (excVref.moveToNext());
            excVref.close();
        } while (excPl.moveToNext());
        excPl.close();

        return Err.NO_ERR;
    }

    /**
     * Extremely critical function.
     * PREREQUISITE
     *   All operations that might access DB, SHOULD BE STOPPED
     *     before importing DB.
     *   And that operation should be resumed after importing DB.
     * @param exDbf
     */
    static Err
    mergeDatabase(File exDbf) {
        Err err = verifyExternalDBFile(exDbf);
        if (err != Err.NO_ERR)
            return err;

        File fTmp = null;
        try {
            fTmp = File.createTempFile("mergeDBTempFile", null, new File(Policy.APPDATA_TMPDIR));
            err = copyAndUpgrade(fTmp, exDbf);
            exDbf = fTmp;
        } catch (IOException e) {
            err = Err.IO_FILE;
        } finally {
            if (Err.NO_ERR != err) {
                if (null != fTmp)
                    fTmp.delete();
                return err;
            }
        }

        // Now exDbf is temporally-created-file.
        if (null == exDbf)
            return Err.IO_FILE;

        try {
            SQLiteDatabase exDb = null;
            try {
                exDb = SQLiteDatabase.openDatabase(exDbf.getAbsolutePath(), null, SQLiteDatabase.OPEN_READONLY);
            } catch (SQLiteException e) {
                return Err.INVALID_DB;
            }

            DB db = DB.get();
            // Merging Algorithm
            // -----------------
            //
            // * Merge playlist and reference tables
            //   : [if] there is duplicated playlist
            //     retry again and again with modified name - ex. title_#_
            //
            // * For each merged playlist, merge reference tables
            //   : scan vidoes
            //     - [if] there is duplicated video - based on Youtube video ID - in the DB
            //         => reference to the video's DB ID (reference count of this video should be increased.)
            //       [else] add the video to the current DB and reference to it.

            // Merging SHOULD BE ONE-TRANSACTION!
            db.beginTransaction();
            try {
                err = doMergeDatabase(exDb);
                if (Err.NO_ERR != err)
                    return err;

                db.setTransactionSuccessful();
            } finally {
                exDb.close();
                db.endTransaction();
            }
        } finally {
            exDbf.delete();
        }

        return Err.NO_ERR;
    }

    static Err
    exportDatabase(File exDbf) {
        Err err = Err.NO_ERR;

        DB.get().close();

        File inDbf = Utils.getAppContext().getDatabasePath(DB.getName());
        try {
            FileInputStream fis = new FileInputStream(inDbf);
            FileOutputStream fos = new FileOutputStream(exDbf);
            Utils.copy(fos, fis);
            fis.close();
            fos.close();
        } catch (FileNotFoundException e) {
            err = Err.IO_FILE;
        } catch (InterruptedException e) {
            // Unexpected interrupt!!
            err = Err.UNKNOWN;
        } catch (IOException e) {
            err = Err.IO_FILE;
        } finally {
            if (Err.NO_ERR != err) {
                exDbf.delete();
                return err;
            }
        }

        DB.get().open(); // open again.
        return Err.NO_ERR;
    }
}




Java Source Code List

free.yhc.netmbuddy.DiagAsyncTask.java
free.yhc.netmbuddy.Err.java
free.yhc.netmbuddy.FragmentPagerAdapterEx.java
free.yhc.netmbuddy.ImportShareActivity.java
free.yhc.netmbuddy.ImportShareContentActivity.java
free.yhc.netmbuddy.ImportShareFileActivity.java
free.yhc.netmbuddy.LockScreenActivity.java
free.yhc.netmbuddy.MusicsActivity.java
free.yhc.netmbuddy.MusicsAdapter.java
free.yhc.netmbuddy.PlaylistActivity.java
free.yhc.netmbuddy.PlaylistAdapter.java
free.yhc.netmbuddy.VideoPlayerActivity.java
free.yhc.netmbuddy.YTMPActivity.java
free.yhc.netmbuddy.YTMPApp.java
free.yhc.netmbuddy.YTMPPreferenceActivity.java
free.yhc.netmbuddy.YTPlaylistSearchActivity.java
free.yhc.netmbuddy.YTPlaylistSearchAdapter.java
free.yhc.netmbuddy.YTPlaylistSearchFragment.java
free.yhc.netmbuddy.YTSearchActivity.java
free.yhc.netmbuddy.YTSearchAdapter.java
free.yhc.netmbuddy.YTSearchFragment.java
free.yhc.netmbuddy.YTSearchPagerAdapter.java
free.yhc.netmbuddy.YTVideoSearchActivity.java
free.yhc.netmbuddy.YTVideoSearchAdapter.java
free.yhc.netmbuddy.YTVideoSearchAuthorActivity.java
free.yhc.netmbuddy.YTVideoSearchFragment.java
free.yhc.netmbuddy.YTVideoSearchKeywordActivity.java
free.yhc.netmbuddy.YTVideoSearchPlaylistActivity.java
free.yhc.netmbuddy.db.ColPlaylist.java
free.yhc.netmbuddy.db.ColVideoRef.java
free.yhc.netmbuddy.db.ColVideo.java
free.yhc.netmbuddy.db.DBHelper.java
free.yhc.netmbuddy.db.DBHistory.java
free.yhc.netmbuddy.db.DBManager.java
free.yhc.netmbuddy.db.DBUpgrader.java
free.yhc.netmbuddy.db.DBUtils.java
free.yhc.netmbuddy.db.DB.java
free.yhc.netmbuddy.model.AtomicFloat.java
free.yhc.netmbuddy.model.BGTask.java
free.yhc.netmbuddy.model.HttpUtils.java
free.yhc.netmbuddy.model.KBLinkedList.java
free.yhc.netmbuddy.model.MultiThreadRunner.java
free.yhc.netmbuddy.model.NetLoader.java
free.yhc.netmbuddy.model.NotiManager.java
free.yhc.netmbuddy.model.Policy.java
free.yhc.netmbuddy.model.RTState.java
free.yhc.netmbuddy.model.SearchSuggestionProvider.java
free.yhc.netmbuddy.model.UnexpectedExceptionHandler.java
free.yhc.netmbuddy.model.YTConstants.java
free.yhc.netmbuddy.model.YTDownloader.java
free.yhc.netmbuddy.model.YTFeed.java
free.yhc.netmbuddy.model.YTHacker.java
free.yhc.netmbuddy.model.YTPlayerLifeSupportService.java
free.yhc.netmbuddy.model.YTPlayerUI.java
free.yhc.netmbuddy.model.YTPlayerVideoListAdapter.java
free.yhc.netmbuddy.model.YTPlayerVideoListManager.java
free.yhc.netmbuddy.model.YTPlayer.java
free.yhc.netmbuddy.model.YTPlaylistFeed.java
free.yhc.netmbuddy.model.YTSearchHelper.java
free.yhc.netmbuddy.model.YTVideoFeed.java
free.yhc.netmbuddy.scmp.DNLoop.java
free.yhc.netmbuddy.scmp.SCElemChar.java
free.yhc.netmbuddy.scmp.SCElemI.java
free.yhc.netmbuddy.scmp.SCElemSStr.java
free.yhc.netmbuddy.scmp.SCElemSTok.java
free.yhc.netmbuddy.scmp.SCElemTok.java
free.yhc.netmbuddy.scmp.SCElem.java
free.yhc.netmbuddy.scmp.SCmpPolicy.java
free.yhc.netmbuddy.scmp.SCmp.java
free.yhc.netmbuddy.share.ExporterPlaylist.java
free.yhc.netmbuddy.share.ImporterPlaylist.java
free.yhc.netmbuddy.share.Importer.java
free.yhc.netmbuddy.share.Json.java
free.yhc.netmbuddy.share.Share.java
free.yhc.netmbuddy.utils.BookmarkListAdapter.java
free.yhc.netmbuddy.utils.FileUtils.java
free.yhc.netmbuddy.utils.ImageUtils.java
free.yhc.netmbuddy.utils.ReportUtils.java
free.yhc.netmbuddy.utils.SimilarTitlesListAdapter.java
free.yhc.netmbuddy.utils.UiUtils.java
free.yhc.netmbuddy.utils.Utils.java
free.yhc.netmbuddy.utils.YTUtils.java