org.eclipse.andmore.android.db.devices.model.DeviceDbNode.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.andmore.android.db.devices.model.DeviceDbNode.java

Source

/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.eclipse.andmore.android.db.devices.model;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

import org.eclipse.andmore.android.DDMSFacade;
import org.eclipse.andmore.android.DDMSUtils;
import org.eclipse.andmore.android.common.log.AndmoreLogger;
import org.eclipse.andmore.android.common.utilities.EclipseUtils;
import org.eclipse.andmore.android.common.utilities.FileUtil;
import org.eclipse.andmore.android.db.core.CanRefreshStatus;
import org.eclipse.andmore.android.db.core.DbCoreActivator;
import org.eclipse.andmore.android.db.core.exception.AndmoreDbException;
import org.eclipse.andmore.android.db.core.model.DbModel;
import org.eclipse.andmore.android.db.core.model.TableModel;
import org.eclipse.andmore.android.db.core.ui.AbstractDbResultManagerAdapter;
import org.eclipse.andmore.android.db.core.ui.DbNode;
import org.eclipse.andmore.android.db.core.ui.IDbNode;
import org.eclipse.andmore.android.db.core.ui.ITableNode;
import org.eclipse.andmore.android.db.core.ui.ITreeNode;
import org.eclipse.andmore.android.db.devices.DbDevicesPlugin;
import org.eclipse.andmore.android.db.devices.i18n.DbDevicesNLS;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.datatools.sqltools.result.ResultsViewAPI;
import org.eclipse.datatools.sqltools.result.core.IResultManagerListener;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.console.IOConsoleOutputStream;
import org.eclipse.ui.plugin.AbstractUIPlugin;

/**
 * This class represents a tree node for a given SQLite3 database file located
 * on a Android device.
 */
public class DeviceDbNode extends DbNode implements IDbNode {
    /**
      * 
      */
    private static final int REMOTE_OPERATIONS_TIMEOUT = 2000;

    private final IPath remoteDbPath;

    private final String serialNumber;

    private IResultManagerListener resultManagerListener;

    private String localFileMd5;

    public boolean isDirty;

    private class ResultManagerListener extends AbstractDbResultManagerAdapter {
        /*
         * (non-Javadoc)
         * 
         * @see
         * org.eclipse.andmore.android.db.core.ui.AbstractDbResultManagerAdapter
         * #statementExecuted(java.lang.String, java.lang.String)
         */
        @Override
        public void statementExecuted(String profilename, String sqlStatement) {
            if ((model != null) && model.getProfileName().equals(profilename)) {
                if ((!sqlStatement.equals("Group Execution")) //$NON-NLS-1$
                        && (sqlStatement.trim().toLowerCase().indexOf("select") != 0) //$NON-NLS-1$
                        && (!sqlStatement.trim().equals(""))) //$NON-NLS-1$
                {
                    IStatus status = checkMd5Sum(true);

                    if (status.isOK()) {
                        status = pushLocalDbFile();
                    }
                    if (!status.isOK()) {
                        isDirty = true;
                    }

                }
            }
        }

    };

    /**
     * Creates a new DeviceDbNode for an already existent SQLite3 database
     * 
     * @param remoteDbPath
     *            the SQLite3 database file location at the device
     * @param parent
     *            this node parent
     */
    public DeviceDbNode(IPath remoteDbPath, String serialNumber, ITreeNode parent) {
        super(parent);
        setId(serialNumber + "." + remoteDbPath.toString()); //$NON-NLS-1$
        this.remoteDbPath = remoteDbPath;
        this.serialNumber = serialNumber;
        setName(remoteDbPath.lastSegment());
        ImageDescriptor icon = AbstractUIPlugin.imageDescriptorFromPlugin(DbCoreActivator.PLUGIN_ID,
                DbNode.ICON_PATH);
        setIcon(icon);
        setTooltip(NLS.bind(DbDevicesNLS.DeviceDbNode_Tootip_Prefix, remoteDbPath.toString()));
    }

    /**
     * Creates a new DeviceDbNode by creating a new SQLite3 database file if
     * requested. This constructor will create a local temp file with the new
     * SQLite3 database. the temp file will then be copied to the remotePath at
     * the device.
     * 
     * @param remoteDbPath
     *            The SQLite database File location at the device
     * @param parent
     *            The parent of the new node.
     * @param create
     *            set this flag to true if you want to create a new db file, if
     *            the flag is false the behavior is the same as the constructor
     *            DeviceDbNode(IPath remoteDbPath, String serialNumber,
     *            ITreeNode parent)
     * @throws AndmoreDbException
     *             if a problem occurred during database creation.
     */
    public DeviceDbNode(IPath remoteDbPath, String serialNumber, ITreeNode parent, boolean create)
            throws AndmoreDbException {
        this(remoteDbPath, serialNumber, parent);
        if (create) {
            try {
                File tempFile = getLocalTempFile();
                Path localDbPath = null;
                if (tempFile != null) {
                    localDbPath = new Path(tempFile.getAbsolutePath());
                    model = new DbModel(localDbPath, create, true);
                    IStatus status = pushLocalDbFile(false);
                    if (!status.isOK()) {
                        deleteLocalDbModel();
                        throw new AndmoreDbException(DbDevicesNLS.DeviceDbNode_Create_Device_Db_Failed);
                    }
                } else {
                    throw new AndmoreDbException(DbDevicesNLS.DeviceDbNode_Could_Not_Create_DeviceDbNode);
                }

            } catch (IOException e) {
                throw new AndmoreDbException(DbDevicesNLS.DeviceDbNode_Could_Not_Create_DeviceDbNode, e);
            }
        }
    }

    /**
     * @return
     * @throws IOException
     */
    private File getLocalTempFile() throws IOException {
        IPreferenceStore preferenceStore = DbDevicesPlugin.getDefault().getPreferenceStore();
        File tempLocationFile = null;

        if (!preferenceStore.isDefault(DbDevicesPlugin.DB_TEMP_PATH_PREFERENCE)) {
            String tempLocation = preferenceStore.getString(DbDevicesPlugin.DB_TEMP_PATH_PREFERENCE);
            tempLocationFile = new File(tempLocation);

            if (!tempLocationFile.isDirectory() || !FileUtil.canWrite(tempLocationFile)) {
                EclipseUtils.showErrorDialog(DbDevicesNLS.ERR_DbUtils_Local_Db_Title,
                        NLS.bind(DbDevicesNLS.ERR_DbUtils_Local_Db_Msg, tempLocation));
                preferenceStore.setToDefault(DbDevicesPlugin.DB_TEMP_PATH_PREFERENCE);
            }

        }

        // If tempLocationFile is null the file will be created on system's
        // default temp dir.
        File tempFile = File.createTempFile(serialNumber + "_" + remoteDbPath.segment(1) + "_" + getName(), //$NON-NLS-1$ //$NON-NLS-2$
                "db", tempLocationFile); //$NON-NLS-1$
        tempFile.deleteOnExit();
        return tempFile;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.andmore.android.db.core.ui.IDbNode#connect()
     */
    @Override
    public IStatus connect() {
        IStatus status = null;

        File tempFile = null;
        try {
            tempFile = getLocalTempFile();
            status = pullRemoteTempFile(tempFile);
        } catch (IOException e) {
            status = new Status(IStatus.ERROR, DbDevicesPlugin.PLUGIN_ID,
                    DbDevicesNLS.DeviceDbNode_Create_Temp_Local_Db_Failed, e);
        }

        if ((model != null) && status.isOK()) // Local model already exists, we
        // must verify the md5 and
        // update the localDbModel if
        // needed.
        {
            try {
                String newMd5Sum = FileUtil.calculateMd5Sum(tempFile);
                if (!newMd5Sum.equals(localFileMd5)) {
                    deleteLocalDbModel(); // Remote file has been changed.
                    // localDbModel must be updated
                }
            } catch (IOException e) {
                status = new Status(IStatus.ERROR, DbDevicesPlugin.PLUGIN_ID,
                        DbDevicesNLS.DeviceDbNode_Calculate_Local_Md5_Failed, e);
            }

        }

        // model will be null if the remote file has been changed.
        if ((model == null) && status.isOK()) {
            try {
                model = new DbModel(Path.fromOSString(tempFile.getAbsolutePath()));
            } catch (AndmoreDbException e) {
                status = new Status(IStatus.ERROR, DbDevicesPlugin.PLUGIN_ID, e.getMessage());
            }
        }

        if ((model != null) && status.isOK()) {
            try {
                localFileMd5 = getLocalMd5Sum();
                model.connect();
            } catch (IOException e) {
                status = new Status(IStatus.ERROR, DbDevicesPlugin.PLUGIN_ID,
                        DbDevicesNLS.DeviceDbNode_Calculate_Local_Md5_Failed, e);
            }
        }

        if (status.isOK()) {
            if (resultManagerListener == null) {
                resultManagerListener = new ResultManagerListener();
                ResultsViewAPI.getInstance().getResultManager().addResultManagerListener(resultManagerListener);
            }
            isDirty = false;
        }

        setNodeStatus(status);

        return status != null ? status : Status.OK_STATUS;
    }

    /**
     * @return
     * @throws IOException
     */
    private String getLocalMd5Sum() throws IOException {
        return FileUtil.calculateMd5Sum(model.getDbPath().toFile());
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.andmore.android.db.core.ui.IDbNode#disconnect()
     */
    @Override
    public IStatus disconnect() {
        IStatus status = Status.OK_STATUS;
        if ((model != null) && model.isConnected()) {

            boolean canDisconnect = true;
            status = closeAssociatedEditors();

            canDisconnect = status.isOK();
            if (canDisconnect) {
                status = model.disconnect();
                if (status.isOK()) {
                    deleteLocalDbModel();
                    if (resultManagerListener != null) {
                        ResultsViewAPI.getInstance().getResultManager()
                                .removeResultManagerListener(resultManagerListener);
                        resultManagerListener = null;
                    }
                }
                clear();
                setNodeStatus(status);
            }
        }

        return status;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.eclipse.andmore.android.db.core.ui.IDbNode#createTables(java.util
     * .List)
     */
    @Override
    public IStatus createTables(List<TableModel> tables) {
        IStatus status;
        status = checkMd5Sum(true);

        if (status.isOK()) {
            status = super.createTables(tables);
            if (status.isOK()) {
                pushLocalDbFile();
            }
        }

        return status;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.eclipse.andmore.android.db.core.ui.IDbNode#createTable(org.eclipse
     * .andmore.android.db.core.model.TableModel)
     */
    @Override
    public IStatus createTable(TableModel table) {
        IStatus status;
        status = checkMd5Sum(true);

        if (status.isOK()) {
            status = super.createTable(table);
            if (status.isOK()) {
                pushLocalDbFile();
            }
        }

        return status;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.eclipse.andmore.android.db.core.ui.IDbNode#deleteTable(java.lang.
     * String)
     */
    @Override
    public IStatus deleteTable(ITableNode tableNode) {
        IStatus status;
        status = checkMd5Sum(true);

        if (status.isOK()) {
            status = super.deleteTable(tableNode);
            if (status.isOK()) {
                pushLocalDbFile();
            }
        }

        return status;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.andmore.android.db.core.ui.IDbNode#deleteDb()
     */
    @Override
    public IStatus deleteDb() {
        IStatus status = null;
        try {
            closeAssociatedEditors(true, forceCloseEditors);
            DDMSFacade.deleteFile(serialNumber, remoteDbPath.toString());
            disconnect();
        } catch (IOException e) {
            status = new Status(IStatus.ERROR, DbDevicesPlugin.PLUGIN_ID,
                    NLS.bind(DbDevicesNLS.DeviceDbNode_Delete_Remote_File_Failed, remoteDbPath.toString(),
                            DDMSFacade.getNameBySerialNumber(serialNumber)));
        }
        return status != null ? status : Status.OK_STATUS;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.andmore.android.db.core.ui.AbstractTreeNode#canRefresh()
     */
    @Override
    public IStatus canRefresh() {
        IStatus status = null;
        if (isDirty) {
            status = checkMd5Sum(false);
            if (!status.isOK()) {
                status = new CanRefreshStatus(CanRefreshStatus.ASK_USER | CanRefreshStatus.CANCELABLE,
                        DbDevicesPlugin.PLUGIN_ID,
                        NLS.bind(DbDevicesNLS.DeviceDbNode_DBOutOfSync_Refresh_Message, getName()));
            }
        } else {
            Set<IEditorPart> associatedEditors = getAssociatedEditors();
            if (!associatedEditors.isEmpty()) {
                status = new CanRefreshStatus(CanRefreshStatus.ASK_USER, DbDevicesPlugin.PLUGIN_ID,
                        NLS.bind(DbDevicesNLS.DeviceDbNode_RefreshQuestion, getName()));
            }
        }
        return status != null ? status : Status.OK_STATUS;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.andmore.android.db.core.ui.AbstractTreeNode#refresh()
     */
    @Override
    public void refresh() {
        if (model != null) {
            if (model.isConnected()) {
                IStatus checkMd5Sum = checkMd5Sum(false);
                if (!checkMd5Sum.isOK()) {
                    model.disconnect();
                    deleteLocalDbModel();
                    clear();
                }
            }
        }

        IStatus status = Status.OK_STATUS;
        if ((model == null) || !model.isConnected()) {
            status = connect(); // Force getting a fresh device db file
        }

        if (status.isOK()) {
            super.refresh();
        }
    }

    private boolean deleteLocalDbModel() {
        IStatus deleteDb = model.deleteDb();
        model = null;

        return deleteDb.isOK();
    }

    private IStatus pullRemoteTempFile(File tempFile) {
        IStatus status = null;
        IOConsoleOutputStream stream = null;
        try {
            IPath localDbPath = new Path(tempFile.getAbsolutePath());
            List<File> localList = Arrays.asList(new File[] { localDbPath.toFile() });
            List<String> remoteList = Arrays.asList(new String[] { remoteDbPath.toString() });

            stream = EclipseUtils.getStudioConsoleOutputStream(false);
            status = DDMSFacade.pullFiles(serialNumber, localList, remoteList, REMOTE_OPERATIONS_TIMEOUT,
                    new NullProgressMonitor(), stream);
        } catch (Exception e) {
            status = new Status(IStatus.ERROR, DbDevicesPlugin.PLUGIN_ID,
                    DbDevicesNLS.DeviceDbNode_Create_Temp_Local_Db_Failed, e);
        } finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    AndmoreLogger.error("Could not close stream: ", e.getMessage()); //$NON-NLS-1$
                }
            }
        }

        return status != null ? status : Status.OK_STATUS;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.eclipse.andmore.android.db.core.ui.AbstractTreeNode#refresh(boolean)
     */
    @Override
    public void refresh(boolean canRefreshYesResponse) {
        if (canRefreshYesResponse) {
            closeAssociatedEditors(false, true);
        } else {
            pushLocalDbFile(false);
        }

        refresh();
    }

    private IStatus pushLocalDbFile() {
        return pushLocalDbFile(true);
    }

    private IStatus pushLocalDbFile(boolean warnUser) {
        IStatus status = null;
        IOConsoleOutputStream stream = null;
        try {
            IPath localDbPath = model.getDbPath();
            File localDbFile = localDbPath.toFile();
            List<File> localList = Arrays.asList(new File[] { localDbFile });
            List<String> remoteList = Arrays.asList(new String[] { remoteDbPath.toString() });
            stream = EclipseUtils.getStudioConsoleOutputStream(false);
            status = DDMSFacade.pushFiles(serialNumber, localList, remoteList, REMOTE_OPERATIONS_TIMEOUT,
                    new NullProgressMonitor(), stream);
            if (status.isOK()) {
                isDirty = false;
            }

            // Update the local Md5Sum everytime the file is pushed to the
            // device.
            localFileMd5 = FileUtil.calculateMd5Sum(localDbFile);

            String appName = getParent().getName();
            if (warnUser) {
                boolean applicationRunning = DDMSFacade.isApplicationRunning(serialNumber, appName);

                if (applicationRunning) {
                    EclipseUtils.showInformationDialog(DbDevicesNLS.DeviceDbNode_Application_Running_Msg_Title,
                            NLS.bind(DbDevicesNLS.DeviceDbNode_Application_Running_Msg_Text, appName));
                }
            }
        } catch (Exception e) {
            status = new Status(IStatus.ERROR, DbDevicesPlugin.PLUGIN_ID,
                    NLS.bind(DbDevicesNLS.DeviceDbNode_Push_Local_File_To_Device_Failed, serialNumber), e);
        } finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    AndmoreLogger.error("Could not close stream: ", e.getMessage()); //$NON-NLS-1$
                }
            }
        }

        return status != null ? status : Status.OK_STATUS;
    }

    /**
     * @param status
     * @return
     */
    private IStatus checkMd5Sum(boolean warnUser) {
        File tempFile = null;
        IStatus status = null;
        if (localFileMd5 != null) // It will be null during create Db process.
        {
            try {
                tempFile = getLocalTempFile(); // Create a new tempFile,
                // different from the local db
                // model file, in order to
                // compare MD5 sum.
                status = pullRemoteTempFile(tempFile);
                String newMd5Sum = FileUtil.calculateMd5Sum(tempFile);
                if (!localFileMd5.equals(newMd5Sum)) {
                    if (warnUser) {
                        boolean canOverwrite = EclipseUtils.showQuestionDialog(
                                DbDevicesNLS.DeviceDbNode_Remote_File_Modified_Title,
                                NLS.bind(DbDevicesNLS.DeviceDbNode_Remote_File_Modified_Msg, getName()));
                        if (!canOverwrite) {
                            status = new Status(IStatus.CANCEL, DbDevicesPlugin.PLUGIN_ID,
                                    DbDevicesNLS.DeviceDbNode_User_Canceled_Overwrite);
                        }
                    } else {
                        status = new Status(IStatus.ERROR, DbDevicesPlugin.PLUGIN_ID,
                                DbDevicesNLS.DeviceDbNode_Md5Sum_Differs);
                    }
                }

            } catch (IOException e) {
                status = new Status(IStatus.ERROR, DbDevicesPlugin.PLUGIN_ID,
                        DbDevicesNLS.DeviceDbNode_Create_Temp_Local_Db_Failed, e);
            } finally {
                if (tempFile != null) {
                    tempFile.delete();
                }
            }
        }

        return status != null ? status : Status.OK_STATUS;
    }

    public boolean remoteFileExists() {
        boolean remoteFileExists = false;
        try {
            remoteFileExists = DDMSUtils.remoteFileExists(serialNumber, remoteDbPath.toString());
        } catch (IOException e) {
            // Return false on error
        }
        return remoteFileExists;
    }

    /**
     * @return the remoteDbPath
     */
    public IPath getRemoteDbPath() {
        return remoteDbPath;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.andmore.android.db.core.ui.DbNode#clean()
     */
    @Override
    public void cleanUp() {
        if (DDMSFacade.isDeviceOnline(serialNumber)) {
            super.cleanUp();
        } else {
            closeAssociatedEditors(true, forceCloseEditors);
            clear();
        }
    }
}