summaryrefslogtreecommitdiffstats
path: root/Plugins/DbAndroid/dbandroidpathdialog.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Plugins/DbAndroid/dbandroidpathdialog.cpp')
-rw-r--r--Plugins/DbAndroid/dbandroidpathdialog.cpp579
1 files changed, 579 insertions, 0 deletions
diff --git a/Plugins/DbAndroid/dbandroidpathdialog.cpp b/Plugins/DbAndroid/dbandroidpathdialog.cpp
new file mode 100644
index 0000000..da1cf5c
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroidpathdialog.cpp
@@ -0,0 +1,579 @@
+#include "dbandroidpathdialog.h"
+#include "ui_dbandroidpathdialog.h"
+#include "common/ipvalidator.h"
+#include "dbandroid.h"
+#include "common/widgetcover.h"
+#include "adbmanager.h"
+#include "uiutils.h"
+#include "iconmanager.h"
+#include "dbandroidconnection.h"
+#include "dbandroidconnectionfactory.h"
+#include <QDebug>
+#include <QTimer>
+#include <QPushButton>
+#include <QInputDialog>
+#include <QMessageBox>
+#include <QtConcurrent/QtConcurrent>
+#include <common/userinputfilter.h>
+
+DbAndroidPathDialog::DbAndroidPathDialog(const DbAndroid* plugin, QWidget *parent) :
+ QDialog(parent),
+ plugin(plugin),
+ ui(new Ui::DbAndroidPathDialog)
+{
+ init();
+}
+
+DbAndroidPathDialog::~DbAndroidPathDialog()
+{
+ delete ui;
+}
+
+void DbAndroidPathDialog::setUrl(const QString& url)
+{
+ dbUrl = DbAndroidUrl(url);
+ loadUrl();
+}
+
+void DbAndroidPathDialog::setUrl(const DbAndroidUrl& url)
+{
+ dbUrl = url;
+ loadUrl();
+}
+
+const DbAndroidUrl& DbAndroidPathDialog::getUrl() const
+{
+ return dbUrl;
+}
+
+void DbAndroidPathDialog::init()
+{
+ ui->setupUi(this);
+
+ dbListCover = new WidgetCover(ui->databaseCombo);
+ appListCover = new WidgetCover(ui->appCombo);
+ new UserInputFilter(ui->appFilterEdit, this, SLOT(applyAppFilter(QString)));
+
+ ui->createDatabaseButton->setIcon(ICONS.PLUS);
+ ui->deleteDatabaseButton->setIcon(ICONS.DELETE);
+
+ dbListUpdateTimer = new QTimer(this);
+ dbListUpdateTimer->setSingleShot(true);
+ dbListUpdateTimer->setInterval(500);
+ connect(dbListUpdateTimer, SIGNAL(timeout()), this, SLOT(refreshDbList()));
+
+ appListUpdateTimer = new QTimer(this);
+ appListUpdateTimer->setSingleShot(true);
+ appListUpdateTimer->setInterval(500);
+ connect(appListUpdateTimer, SIGNAL(timeout()), this, SLOT(refreshAppList()));
+
+ connect(ui->deviceCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(scheduleAppListUpdate()));
+ connect(ui->databaseCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateState()));
+ connect(ui->portSpin, SIGNAL(valueChanged(int)), this, SLOT(scheduleDbListUpdate()));
+ connect(ui->createDatabaseButton, SIGNAL(clicked()), this, SLOT(createNewDatabase()));
+ connect(ui->deleteDatabaseButton, SIGNAL(clicked()), this, SLOT(deleteSelectedDatabase()));
+ connect(ui->passwordGroup, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ connect(ui->passwordGroup, SIGNAL(toggled(bool)), this, SLOT(scheduleDbListUpdate()));
+ connect(ui->passwordEdit, SIGNAL(textChanged(QString)), this, SLOT(scheduleDbListUpdate()));
+
+ connect(this, SIGNAL(asyncDbListUpdatingFinished(bool)), this, SLOT(handleFinishedAsyncDbListUpdate(bool)));
+ connect(this, SIGNAL(asyncAppListUpdatingFinished()), this, SLOT(handleFinishedAsyncAppListUpdate()));
+ connect(this, SIGNAL(callForDbListUpdate(QStringList)), this, SLOT(handleUpdateDbList(QStringList)));
+ connect(this, SIGNAL(callForAppListUpdate(QStringList)), this, SLOT(handleUpdateAppList(QStringList)));
+ connect(this, SIGNAL(callForValidations()), this, SLOT(updateValidations()));
+ connect(this, SIGNAL(callForDbCreationUpdate(bool)), this, SLOT(handleDbCreationUpdate(bool)));
+
+ if (!plugin->isAdbValid())
+ {
+ ui->ipRadio->setChecked(true);
+ ui->usbRadio->setEnabled(false);
+ ui->shellRadio->setEnabled(false);
+ }
+ else
+ {
+ refreshDevices();
+ connect(plugin->getAdbManager(), SIGNAL(deviceDetailsChanged(QList<Device>)), this, SLOT(updateDeviceList()));
+ }
+
+ connect(ui->ipRadio, SIGNAL(toggled(bool)), this, SLOT(modeChanged(bool)));
+ connect(ui->usbRadio, SIGNAL(toggled(bool)), this, SLOT(modeChanged(bool)));
+ connect(ui->shellRadio, SIGNAL(toggled(bool)), this, SLOT(modeChanged(bool)));
+ connect(ui->ipEdit, SIGNAL(textChanged(QString)), this, SLOT(scheduleDbListUpdate()));
+ setDbListUpdatesEnabled(true);
+
+ handleDbCreationUpdate(false);
+ updateState();
+ adjustSize();
+}
+
+void DbAndroidPathDialog::updateUrl()
+{
+ DbAndroidMode mode = getSelectedMode();
+ dbUrl.setEnforcedMode(mode);
+ switch (mode)
+ {
+ case DbAndroidMode::NETWORK:
+ dbUrl.setHost(ui->ipEdit->text());
+ dbUrl.setPort(ui->portSpin->value());
+ break;
+ case DbAndroidMode::USB:
+ dbUrl.setDevice(ui->deviceCombo->currentData().toString());
+ dbUrl.setPort(ui->portSpin->value());
+ break;
+ case DbAndroidMode::SHELL:
+ dbUrl.setDevice(ui->deviceCombo->currentData().toString());
+ dbUrl.setApplication(ui->appCombo->currentText());
+ break;
+ case DbAndroidMode::null:
+ qCritical() << "Unknown mode in DbAndroidPathDialog::updateUrl()";
+ return;
+ }
+
+ dbUrl.setDbName(ui->databaseCombo->currentText());
+ if (ui->passwordGroup->isChecked())
+ dbUrl.setPassword(ui->passwordEdit->text());
+ else
+ dbUrl.setPassword(QString());
+}
+
+void DbAndroidPathDialog::loadUrl()
+{
+ if (!dbUrl.isValid())
+ return;
+
+ switch (dbUrl.getMode())
+ {
+ case DbAndroidMode::NETWORK:
+ ui->ipRadio->setChecked(true);
+ ui->ipEdit->setText(dbUrl.getHost());
+ break;
+ case DbAndroidMode::SHELL:
+ ui->shellRadio->setChecked(true);
+ ui->deviceCombo->setCurrentIndex(ui->deviceCombo->findData(dbUrl.getDevice()));
+ setDbListUpdatesEnabled(false);
+ if (ui->appCombo->findText(dbUrl.getApplication()) == -1)
+ ui->appCombo->addItem(dbUrl.getApplication());
+
+ ui->appCombo->setCurrentText(dbUrl.getApplication());
+ setDbListUpdatesEnabled(true);
+ break;
+ case DbAndroidMode::USB:
+ ui->usbRadio->setChecked(true);
+ ui->deviceCombo->setCurrentIndex(ui->deviceCombo->findData(dbUrl.getDevice()));
+ break;
+ case DbAndroidMode::null:
+ qCritical() << "Cannot load URL of mode 'null' in DbAndroidPathDialog::loadUrl().";
+ return;
+ }
+
+ ui->portSpin->setValue(dbUrl.getPort());
+ if (ui->databaseCombo->findText(dbUrl.getDbName()) == -1)
+ ui->databaseCombo->addItem(dbUrl.getDbName());
+
+ ui->databaseCombo->setCurrentText(dbUrl.getDbName());
+
+ if (!dbUrl.getPassword().isNull())
+ {
+ ui->passwordGroup->setChecked(true);
+ ui->passwordEdit->setText(dbUrl.getPassword());
+ }
+}
+
+void DbAndroidPathDialog::scheduleDbListUpdate()
+{
+ if (suspendDbListUpdates)
+ return;
+
+ bool startCover = true;
+ if (dbListUpdateTimer->isActive())
+ {
+ dbListUpdateTimer->stop();
+ startCover = false;
+ }
+
+ dbListUpdateTimer->start();
+ if (startCover)
+ dbListCover->show();
+
+ handleDbCreationUpdate(false);
+ updateValidations();
+}
+
+void DbAndroidPathDialog::scheduleAppListUpdate()
+{
+ if (getSelectedMode() != DbAndroidMode::SHELL)
+ return;
+
+ if (suspendAppListUpdates)
+ return;
+
+ bool startCover = true;
+ if (appListUpdateTimer->isActive())
+ {
+ appListUpdateTimer->stop();
+ startCover = false;
+ }
+
+ appListUpdateTimer->start();
+ if (startCover)
+ appListCover->show();
+
+ updateValidations();
+}
+
+void DbAndroidPathDialog::refreshDbList()
+{
+ if (updatingDbList)
+ {
+ // Already busy, schedule next update afterwards.
+ scheduleDbListUpdate();
+ return;
+ }
+
+ updateUrl();
+ ui->databaseCombo->clear();
+
+ if (!dbUrl.isValid(false))
+ {
+ dbListCover->hide();
+ return;
+ }
+
+ updatingDbList = true;
+ QtConcurrent::run(this, &DbAndroidPathDialog::asyncDbUpdate, dbUrl.toUrlString(), dbUrl.getMode());
+}
+
+void DbAndroidPathDialog::refreshAppList()
+{
+ if (updatingAppList)
+ {
+ // Already busy, schedule next update afterwards.
+ scheduleAppListUpdate();
+ return;
+ }
+
+ updateUrl();
+ setDbListUpdatesEnabled(false);
+ ui->appCombo->clear();
+ setDbListUpdatesEnabled(true);
+
+ if (!dbUrl.isValid(false))
+ {
+ appListCover->hide();
+ return;
+ }
+
+ updatingAppList = true;
+ QtConcurrent::run(this, &DbAndroidPathDialog::asyncAppUpdate, dbUrl.toUrlString(), dbUrl.getMode());
+}
+
+void DbAndroidPathDialog::asyncDbUpdate(const QString& connectionUrl, DbAndroidMode enforcedMode)
+{
+ DbAndroidUrl url(connectionUrl);
+ url.setEnforcedMode(enforcedMode);
+
+ QScopedPointer<DbAndroidConnection> connection(plugin->getConnectionFactory()->create(url));
+ if (!connection->connectToAndroid(url))
+ {
+ qDebug() << "Could not open db connection" << connectionUrl;
+ emit asyncDbListUpdatingFinished(connection->isAppOkay());
+ emit callForValidations();
+ return;
+ }
+
+ QStringList dbList = connection->getDbList();
+ bool appOk = connection->isAppOkay();
+
+ connection->disconnectFromAndroid();
+
+ emit callForDbCreationUpdate(appOk);
+ emit callForDbListUpdate(dbList);
+ emit asyncDbListUpdatingFinished(appOk);
+ emit callForValidations();
+}
+
+void DbAndroidPathDialog::asyncAppUpdate(const QString& connectionUrl, DbAndroidMode enforcedMode)
+{
+ DbAndroidUrl url(connectionUrl);
+ url.setEnforcedMode(enforcedMode);
+
+ QScopedPointer<DbAndroidConnection> connection(plugin->getConnectionFactory()->create(url));
+ QStringList appList = connection->getAppList();
+ emit callForAppListUpdate(appList);
+ emit asyncAppListUpdatingFinished();
+ emit callForValidations();
+}
+
+void DbAndroidPathDialog::refreshDevices()
+{
+ static_qstring(displayNameTpl, "%1 (%2)");
+ ui->deviceCombo->clear();
+
+ QString displayName;
+ QList<AdbManager::Device> deviceDetails = plugin->getAdbManager()->getDeviceDetails();
+ for (const AdbManager::Device& details : deviceDetails)
+ {
+ if (details.fullName.isEmpty())
+ displayName = details.id;
+ else
+ displayName = displayNameTpl.arg(details.fullName, details.id);
+
+ ui->deviceCombo->addItem(displayName, details.id);
+ }
+}
+
+DbAndroidMode DbAndroidPathDialog::getSelectedMode() const
+{
+ if (ui->ipRadio->isChecked())
+ return DbAndroidMode::NETWORK;
+
+ if (ui->usbRadio->isChecked())
+ return DbAndroidMode::USB;
+
+ return DbAndroidMode::SHELL;
+}
+
+void DbAndroidPathDialog::setDbListUpdatesEnabled(bool enabled)
+{
+ suspendDbListUpdates = !enabled;
+ if (enabled)
+ {
+ connect(ui->deviceCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(scheduleDbListUpdate()));
+ connect(ui->appCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(scheduleDbListUpdate()));
+ }
+ else
+ {
+ disconnect(ui->deviceCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(scheduleDbListUpdate()));
+ disconnect(ui->appCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(scheduleDbListUpdate()));
+ }
+}
+
+void DbAndroidPathDialog::updateDeviceList()
+{
+ suspendDbListUpdates = true;
+
+ bool dbListNeedsUpdate = false;
+ QString oldValue = ui->deviceCombo->currentData().toString();
+
+ refreshDevices();
+ int idx = ui->deviceCombo->findData(oldValue);
+ if (idx > -1)
+ ui->deviceCombo->setCurrentIndex(idx);
+ else
+ dbListNeedsUpdate = true;
+
+ suspendDbListUpdates = false;
+
+ updateValidations();
+
+ if (dbListNeedsUpdate)
+ scheduleDbListUpdate();
+}
+
+void DbAndroidPathDialog::updateValidations()
+{
+ bool isUpdating = dbListUpdateTimer->isActive();
+ bool ipOk = true;
+ bool deviceOk = true;
+ if (ui->ipRadio->isChecked())
+ {
+ ipOk = IpValidator::check(ui->ipEdit->text());
+ setValidState(ui->ipEdit, ipOk, tr("Enter valid IP address."));
+ }
+ else
+ {
+ deviceOk = !ui->deviceCombo->currentData().toString().isEmpty();
+ setValidState(ui->deviceCombo, deviceOk, tr("Pick Android device."));
+ }
+
+ bool dbOk = !ui->databaseCombo->currentText().isEmpty();
+ setValidState(ui->databaseCombo, dbOk, tr("Pick Android database."));
+
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(ipOk && deviceOk && dbOk && !isUpdating);
+}
+
+void DbAndroidPathDialog::handleUpdateDbList(const QStringList& dbList)
+{
+ ui->databaseCombo->addItems(dbList);
+ if (dbList.contains(dbUrl.getDbName()))
+ ui->databaseCombo->setCurrentText(dbUrl.getDbName());
+}
+
+void DbAndroidPathDialog::handleUpdateAppList(const QStringList& apps)
+{
+ fullAppList = apps;
+ QStringList filtered = apps.filter(ui->appFilterEdit->text(), Qt::CaseInsensitive);
+ ui->appCombo->addItems(filtered);
+ if (filtered.contains(dbUrl.getApplication()))
+ ui->appCombo->setCurrentText(dbUrl.getApplication());
+}
+
+void DbAndroidPathDialog::handleFinishedAsyncDbListUpdate(bool appOkay)
+{
+ if (getSelectedMode() == DbAndroidMode::SHELL)
+ setValidState(ui->appCombo, appOkay, tr("Selected Android application is unknown, or not debuggable."));
+
+ dbListCover->hide();
+ updatingDbList = false;
+}
+
+void DbAndroidPathDialog::handleFinishedAsyncAppListUpdate()
+{
+ appListCover->hide();
+ updatingAppList = false;
+}
+
+void DbAndroidPathDialog::handleDbCreationUpdate(bool canCreateDatabases)
+{
+ ui->createDatabaseButton->setEnabled(canCreateDatabases);
+}
+
+void DbAndroidPathDialog::createNewDatabase()
+{
+ DbAndroidUrl tmpUrl(dbUrl);
+ tmpUrl.setDbName(QString());
+
+ DbAndroidConnection::ExecutionResult results;
+ QString name;
+ bool ok = false;
+ while (!ok)
+ {
+ name = QInputDialog::getText(this, tr("Create new database"), tr("Please provide name for the new database.\n"
+ "It's the name which Android application will use to connect to the database:"));
+
+ if (name.isNull())
+ break;
+
+ if (ui->databaseCombo->findText(name) > -1)
+ {
+ QMessageBox::warning(this, tr("Invalid name"), tr("Database with the same name (%1) already exists on the device.\n"
+ "The name must be unique."));
+ continue;
+ }
+
+ tmpUrl.setDbName(name);
+ QScopedPointer<DbAndroidConnection> connection(plugin->getConnectionFactory()->create(tmpUrl));
+ if (!connection->connectToAndroid(tmpUrl))
+ {
+ QMessageBox::warning(this, tr("Invalid name"), tr("Could not create database '%1', because could not connect to the device.").arg(name));
+ continue;
+ }
+
+ results = connection->executeQuery("PRAGMA encoding;");
+ ok = !results.wasError && results.resultDataList.size() > 0;
+ connection->disconnectFromAndroid();
+
+ if (!ok)
+ QMessageBox::warning(this, tr("Invalid name"), tr("Could not create database '%1'.\nDetails: %2").arg(name, results.errorMsg));
+ }
+
+ if (ok)
+ {
+ ui->databaseCombo->addItem(name);
+ ui->databaseCombo->setCurrentText(name);
+ }
+}
+
+void DbAndroidPathDialog::deleteSelectedDatabase()
+{
+ updateUrl();
+ QString dbName = dbUrl.getDbName();
+
+ QMessageBox::StandardButton res = QMessageBox::question(this, tr("Delete database"), tr("Are you sure you want to delete database '%1' from %2?")
+ .arg(dbName, dbUrl.getDisplayName()));
+
+ if (res != QMessageBox::Yes)
+ return;
+
+ int idx = ui->databaseCombo->findText(dbName);
+ if (idx < 0)
+ {
+ QStringList dbList;
+ for (int i = 0, total = ui->databaseCombo->count(); i < total; ++i)
+ dbList << ui->databaseCombo->itemText(i);
+
+ qCritical() << "Tried to delete database, but it's not in the list of databases:" << dbName << "and the list is:" << dbList;
+ return;
+ }
+
+ // Local db instance, will close on deletion.
+ QScopedPointer<DbAndroidConnection> connection(plugin->getConnectionFactory()->create(dbUrl));
+ if (!connection->connectToAndroid(dbUrl))
+ {
+ QMessageBox::critical(this, tr("Error deleting"), tr("Could not connect to %1 in order to delete database '%2'.").arg(dbUrl.getDisplayName(), dbName));
+ return;
+ }
+
+ if (!connection->deleteDatabase(dbName))
+ {
+ QMessageBox::critical(this, tr("Error deleting"), tr("Could not delete database named '%1' from the device.\n"
+ "Android device refused deletion, or it was impossible.").arg(dbName));
+
+ connection->disconnectFromAndroid();
+ return;
+ }
+ connection->disconnectFromAndroid();
+
+ ui->databaseCombo->removeItem(idx);
+ if (ui->databaseCombo->count() > 0)
+ {
+ if (idx < ui->databaseCombo->count())
+ ui->databaseCombo->setCurrentIndex(idx);
+ else
+ ui->databaseCombo->setCurrentIndex(ui->databaseCombo->count() - 1);
+ }
+}
+
+void DbAndroidPathDialog::modeChanged(bool checked)
+{
+ if (!checked)
+ return;
+
+ updateState();
+ adjustSize();
+ scheduleAppListUpdate();
+
+ if (getSelectedMode() != DbAndroidMode::SHELL)
+ scheduleDbListUpdate();
+}
+
+void DbAndroidPathDialog::applyAppFilter(const QString& value)
+{
+ QString selectedApp = ui->appCombo->currentText();
+ QStringList filtered = fullAppList.filter(value, Qt::CaseInsensitive);
+ bool callDbListUpdate = false;
+
+ setDbListUpdatesEnabled(false);
+ ui->appCombo->clear();
+ ui->appCombo->addItems(filtered);
+ if (filtered.contains(selectedApp))
+ ui->appCombo->setCurrentText(selectedApp);
+ else
+ callDbListUpdate = true;
+
+ setDbListUpdatesEnabled(true);
+
+ if (callDbListUpdate)
+ scheduleDbListUpdate();
+}
+
+void DbAndroidPathDialog::accept()
+{
+ updateUrl();
+ QDialog::accept();
+}
+
+void DbAndroidPathDialog::updateState()
+{
+ DbAndroidMode mode = getSelectedMode();
+
+ ui->deviceGroup->setVisible(mode == DbAndroidMode::SHELL || mode == DbAndroidMode::USB);
+ ui->ipGroup->setVisible(mode == DbAndroidMode::NETWORK);
+ ui->portGroup->setVisible(mode == DbAndroidMode::NETWORK || mode == DbAndroidMode::USB);
+ ui->appGroup->setVisible(mode == DbAndroidMode::SHELL);
+ ui->passwordGroup->setVisible(mode == DbAndroidMode::NETWORK || mode == DbAndroidMode::USB);
+
+ ui->deleteDatabaseButton->setEnabled(ui->databaseCombo->currentIndex() > -1);
+ ui->passwordEdit->setEnabled(ui->passwordGroup->isChecked());
+ updateValidations();
+}