aboutsummaryrefslogtreecommitdiffstats
path: root/Plugins
diff options
context:
space:
mode:
authorLibravatarUnit 193 <unit193@ubuntu.com>2015-11-25 16:48:41 -0500
committerLibravatarUnit 193 <unit193@ubuntu.com>2015-11-25 16:48:41 -0500
commit8e640722c62692818ab840d50b3758f89a41a54e (patch)
tree38197eb1688a5afc338081ea17e15f938976e422 /Plugins
parent9618f0ebbf4b88045247c01ce8c8f58203508ebf (diff)
downloadsqlitestudio-8e640722c62692818ab840d50b3758f89a41a54e.tar.bz2
sqlitestudio-8e640722c62692818ab840d50b3758f89a41a54e.tar.xz
sqlitestudio-8e640722c62692818ab840d50b3758f89a41a54e.tar.zst
Imported Upstream version 3.0.7upstream/3.0.7
Diffstat (limited to 'Plugins')
-rw-r--r--Plugins/ConfigMigration/ConfigMigration.pro4
-rw-r--r--Plugins/ConfigMigration/ConfigMigration_it.ts150
-rw-r--r--Plugins/CsvExport/CsvExport.pro4
-rw-r--r--Plugins/CsvExport/CsvExport_it.ts57
-rw-r--r--Plugins/CsvExport/CsvExport_zh_CN.ts20
-rw-r--r--Plugins/CsvImport/CsvImport.pro4
-rw-r--r--Plugins/CsvImport/CsvImport_it.ts85
-rw-r--r--Plugins/DbAndroid/DbAndroid.pro53
-rw-r--r--Plugins/DbAndroid/adbmanager.cpp426
-rw-r--r--Plugins/DbAndroid/adbmanager.h65
-rw-r--r--Plugins/DbAndroid/dbandroid.cpp209
-rw-r--r--Plugins/DbAndroid/dbandroid.h63
-rw-r--r--Plugins/DbAndroid/dbandroid.json9
-rw-r--r--Plugins/DbAndroid/dbandroid.qrc1
-rw-r--r--Plugins/DbAndroid/dbandroid_global.h12
-rw-r--r--Plugins/DbAndroid/dbandroidconnection.cpp14
-rw-r--r--Plugins/DbAndroid/dbandroidconnection.h44
-rw-r--r--Plugins/DbAndroid/dbandroidconnectionfactory.cpp28
-rw-r--r--Plugins/DbAndroid/dbandroidconnectionfactory.h21
-rw-r--r--Plugins/DbAndroid/dbandroidinstance.cpp143
-rw-r--r--Plugins/DbAndroid/dbandroidinstance.h51
-rw-r--r--Plugins/DbAndroid/dbandroidjsonconnection.cpp430
-rw-r--r--Plugins/DbAndroid/dbandroidjsonconnection.h65
-rw-r--r--Plugins/DbAndroid/dbandroidmode.h13
-rw-r--r--Plugins/DbAndroid/dbandroidpathdialog.cpp579
-rw-r--r--Plugins/DbAndroid/dbandroidpathdialog.h80
-rw-r--r--Plugins/DbAndroid/dbandroidpathdialog.ui244
-rw-r--r--Plugins/DbAndroid/dbandroidshellconnection.cpp363
-rw-r--r--Plugins/DbAndroid/dbandroidshellconnection.h59
-rw-r--r--Plugins/DbAndroid/dbandroidurl.cpp217
-rw-r--r--Plugins/DbAndroid/dbandroidurl.h59
-rw-r--r--Plugins/DbAndroid/sqlqueryandroid.cpp160
-rw-r--r--Plugins/DbAndroid/sqlqueryandroid.h47
-rw-r--r--Plugins/DbAndroid/sqlresultrowandroid.cpp12
-rw-r--r--Plugins/DbAndroid/sqlresultrowandroid.h13
-rw-r--r--Plugins/DbSqlite2/DbSqlite2.pro1
-rw-r--r--Plugins/HtmlExport/HtmlExport.pro4
-rw-r--r--Plugins/HtmlExport/HtmlExport_it.ts173
-rw-r--r--Plugins/JsonExport/JsonExport.pro4
-rw-r--r--Plugins/JsonExport/JsonExport_it.ts22
-rw-r--r--Plugins/JsonExport/JsonExport_zh_CN.ts6
-rw-r--r--Plugins/PdfExport/PdfExport.pro4
-rw-r--r--Plugins/PdfExport/PdfExport_de.ts96
-rw-r--r--Plugins/PdfExport/PdfExport_it.ts256
-rw-r--r--Plugins/Plugins.pro3
-rw-r--r--Plugins/Printing/Printing.pro4
-rw-r--r--Plugins/Printing/Printing_de.ts12
-rw-r--r--Plugins/Printing/Printing_it.ts40
-rw-r--r--Plugins/Printing/Printing_zh_CN.ts12
-rw-r--r--Plugins/RegExpImport/RegExpImport.pro4
-rw-r--r--Plugins/RegExpImport/RegExpImport_it.ts83
-rw-r--r--Plugins/ScriptingTcl/ScriptingTcl.pro8
-rw-r--r--Plugins/ScriptingTcl/ScriptingTcl_it.ts22
-rw-r--r--Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter.pro4
-rw-r--r--Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_de.ts31
-rw-r--r--Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_es.ts31
-rw-r--r--Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_fr.ts31
-rw-r--r--Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_it.ts233
-rw-r--r--Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_pl.ts31
-rw-r--r--Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_pt_BR.ts31
-rw-r--r--Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_ru.ts33
-rw-r--r--Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_sk.ts31
-rw-r--r--Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_zh_CN.ts31
-rw-r--r--Plugins/SqlEnterpriseFormatter/formatempty.cpp5
-rw-r--r--Plugins/SqlEnterpriseFormatter/formatempty.h3
-rw-r--r--Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.cpp206
-rw-r--r--Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.h23
-rw-r--r--Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.ui69
-rw-r--r--Plugins/SqlExport/SqlExport.pro4
-rw-r--r--Plugins/SqlExport/SqlExport_it.ts98
-rw-r--r--Plugins/SqlExport/SqlExport_zh_CN.ts14
-rw-r--r--Plugins/SqlFormatterSimple/SqlFormatterSimple.pro4
-rw-r--r--Plugins/SqlFormatterSimple/SqlFormatterSimple_it.ts17
-rw-r--r--Plugins/SqlFormatterSimple/SqlFormatterSimple_zh_CN.ts4
-rw-r--r--Plugins/XmlExport/XmlExport.pro4
-rw-r--r--Plugins/XmlExport/XmlExport_it.ts70
76 files changed, 5433 insertions, 133 deletions
diff --git a/Plugins/ConfigMigration/ConfigMigration.pro b/Plugins/ConfigMigration/ConfigMigration.pro
index 8983b8d..52eeaec 100644
--- a/Plugins/ConfigMigration/ConfigMigration.pro
+++ b/Plugins/ConfigMigration/ConfigMigration.pro
@@ -31,7 +31,8 @@ RESOURCES += \
configmigration.qrc
-TRANSLATIONS += ConfigMigration_zh_CN.ts \
+TRANSLATIONS += ConfigMigration_it.ts \
+ ConfigMigration_zh_CN.ts \
ConfigMigration_sk.ts \
ConfigMigration_de.ts \
ConfigMigration_ru.ts \
@@ -51,3 +52,4 @@ TRANSLATIONS += ConfigMigration_zh_CN.ts \
+
diff --git a/Plugins/ConfigMigration/ConfigMigration_it.ts b/Plugins/ConfigMigration/ConfigMigration_it.ts
new file mode 100644
index 0000000..c71c354
--- /dev/null
+++ b/Plugins/ConfigMigration/ConfigMigration_it.ts
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="it_IT">
+<context>
+ <name>ConfigMigration</name>
+ <message>
+ <location filename="configmigration.cpp" line="36"/>
+ <source>A configuration from old SQLiteStudio 2.x.x has been detected. Would you like to migrate old settings into the current version? &lt;a href=&quot;%1&quot;&gt;Click here to do that&lt;/a&gt;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigration.cpp" line="136"/>
+ <source>Bug reports history (%1)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigration.cpp" line="145"/>
+ <source>Database list (%1)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigration.cpp" line="154"/>
+ <source>Custom SQL functions (%1)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigration.cpp" line="163"/>
+ <source>SQL queries history (%1)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ConfigMigrationWizard</name>
+ <message>
+ <location filename="configmigrationwizard.ui" line="14"/>
+ <source>Configuration migration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.ui" line="24"/>
+ <source>Items to migrate</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.ui" line="27"/>
+ <source>This is a list of items found in the old configuration file, which can be migrated into the current configuration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.ui" line="58"/>
+ <source>Options</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.ui" line="64"/>
+ <source>Put imported databases into separate group</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.ui" line="76"/>
+ <source>Group name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.cpp" line="60"/>
+ <source>Enter a non-empty name.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.cpp" line="68"/>
+ <source>Top level group named &apos;%1&apos; already exists. Enter a group name that does not exist yet.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.cpp" line="102"/>
+ <source>Could not open old configuration file in order to migrate settings from it.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.cpp" line="110"/>
+ <source>Could not open current configuration file in order to migrate settings from old configuration file.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.cpp" line="119"/>
+ <source>Could not commit migrated data into new configuration file: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.cpp" line="163"/>
+ <source>Could not read bug reports history from old configuration file in order to migrate it: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.cpp" line="180"/>
+ <source>Could not insert a bug reports history entry into new configuration file: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.cpp" line="201"/>
+ <source>Could not read database list from old configuration file in order to migrate it: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.cpp" line="215"/>
+ <source>Could not query for available order for containing group in new configuration file in order to migrate the database list: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.cpp" line="226"/>
+ <source>Could not create containing group in new configuration file in order to migrate the database list: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.cpp" line="247"/>
+ <source>Could not insert a database entry into new configuration file: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.cpp" line="259"/>
+ <source>Could not query for available order for next database in new configuration file in order to migrate the database list: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.cpp" line="270"/>
+ <source>Could not create group referencing the database in new configuration file: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.cpp" line="288"/>
+ <source>Could not read function list from old configuration file in order to migrate it: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.cpp" line="323"/>
+ <source>Could not read SQL queries history from old configuration file in order to migrate it: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.cpp" line="330"/>
+ <source>Could not read next ID for SQL queries history in new configuration file: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="configmigrationwizard.cpp" line="346"/>
+ <source>Could not insert SQL history entry into new configuration file: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/Plugins/CsvExport/CsvExport.pro b/Plugins/CsvExport/CsvExport.pro
index 1599a13..9e509b2 100644
--- a/Plugins/CsvExport/CsvExport.pro
+++ b/Plugins/CsvExport/CsvExport.pro
@@ -29,7 +29,8 @@ RESOURCES += \
-TRANSLATIONS += CsvExport_zh_CN.ts \
+TRANSLATIONS += CsvExport_it.ts \
+ CsvExport_zh_CN.ts \
CsvExport_sk.ts \
CsvExport_de.ts \
CsvExport_ru.ts \
@@ -49,3 +50,4 @@ TRANSLATIONS += CsvExport_zh_CN.ts \
+
diff --git a/Plugins/CsvExport/CsvExport_it.ts b/Plugins/CsvExport/CsvExport_it.ts
new file mode 100644
index 0000000..30f4907
--- /dev/null
+++ b/Plugins/CsvExport/CsvExport_it.ts
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="it_IT">
+<context>
+ <name>CsvExport</name>
+ <message>
+ <location filename="CsvExport.ui" line="20"/>
+ <source>Column names in first row</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="CsvExport.ui" line="30"/>
+ <source>Column separator:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="CsvExport.ui" line="41"/>
+ <source>, (comma)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="CsvExport.ui" line="46"/>
+ <source>; (semicolon)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="CsvExport.ui" line="51"/>
+ <source>\t (tab)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="CsvExport.ui" line="56"/>
+ <source> (whitespace)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="CsvExport.ui" line="61"/>
+ <source>Custom:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="CsvExport.ui" line="82"/>
+ <source>Export NULL values as:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="CsvExport.ui" line="89"/>
+ <source>Empty string</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="csvexport.cpp" line="42"/>
+ <source>Enter the custom separator character.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/Plugins/CsvExport/CsvExport_zh_CN.ts b/Plugins/CsvExport/CsvExport_zh_CN.ts
index e73fe4e..6d31621 100644
--- a/Plugins/CsvExport/CsvExport_zh_CN.ts
+++ b/Plugins/CsvExport/CsvExport_zh_CN.ts
@@ -6,52 +6,52 @@
<message>
<location filename="CsvExport.ui" line="20"/>
<source>Column names in first row</source>
- <translation type="unfinished"></translation>
+ <translation>第一行显示列名</translation>
</message>
<message>
<location filename="CsvExport.ui" line="30"/>
<source>Column separator:</source>
- <translation type="unfinished"></translation>
+ <translation>列分隔符:</translation>
</message>
<message>
<location filename="CsvExport.ui" line="41"/>
<source>, (comma)</source>
- <translation type="unfinished"></translation>
+ <translation>,(逗号)</translation>
</message>
<message>
<location filename="CsvExport.ui" line="46"/>
<source>; (semicolon)</source>
- <translation type="unfinished"></translation>
+ <translation>;(分号)</translation>
</message>
<message>
<location filename="CsvExport.ui" line="51"/>
<source>\t (tab)</source>
- <translation type="unfinished"></translation>
+ <translation>\t(tab位)</translation>
</message>
<message>
<location filename="CsvExport.ui" line="56"/>
<source> (whitespace)</source>
- <translation type="unfinished"></translation>
+ <translation> (空格)</translation>
</message>
<message>
<location filename="CsvExport.ui" line="61"/>
<source>Custom:</source>
- <translation type="unfinished"></translation>
+ <translation>自定义:</translation>
</message>
<message>
<location filename="CsvExport.ui" line="82"/>
<source>Export NULL values as:</source>
- <translation type="unfinished"></translation>
+ <translation>NULL导出为:</translation>
</message>
<message>
<location filename="CsvExport.ui" line="89"/>
<source>Empty string</source>
- <translation type="unfinished"></translation>
+ <translation>空字符串</translation>
</message>
<message>
<location filename="csvexport.cpp" line="42"/>
<source>Enter the custom separator character.</source>
- <translation type="unfinished"></translation>
+ <translation>输入自定义分隔符 。</translation>
</message>
</context>
</TS>
diff --git a/Plugins/CsvImport/CsvImport.pro b/Plugins/CsvImport/CsvImport.pro
index 6ce08d1..7e8dce0 100644
--- a/Plugins/CsvImport/CsvImport.pro
+++ b/Plugins/CsvImport/CsvImport.pro
@@ -28,7 +28,8 @@ RESOURCES += \
csvimport.qrc
-TRANSLATIONS += CsvImport_zh_CN.ts \
+TRANSLATIONS += CsvImport_it.ts \
+ CsvImport_zh_CN.ts \
CsvImport_sk.ts \
CsvImport_de.ts \
CsvImport_ru.ts \
@@ -48,3 +49,4 @@ TRANSLATIONS += CsvImport_zh_CN.ts \
+
diff --git a/Plugins/CsvImport/CsvImport_it.ts b/Plugins/CsvImport/CsvImport_it.ts
new file mode 100644
index 0000000..bc60d95
--- /dev/null
+++ b/Plugins/CsvImport/CsvImport_it.ts
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="it_IT">
+<context>
+ <name>CsvImport</name>
+ <message>
+ <location filename="csvimport.cpp" line="30"/>
+ <source>Cannot read file %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="csvimport.cpp" line="62"/>
+ <source>Could not find any data in the file %1.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="csvimport.cpp" line="167"/>
+ <source>Enter the custom separator character.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="csvimport.cpp" line="181"/>
+ <source>Enter the value that will be interpreted as a NULL.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="csvimport.cpp" line="194"/>
+ <source>CSV files (*.csv);;Text files (*.txt);;All files (*)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>csvImportOptions</name>
+ <message>
+ <location filename="CsvImportOptions.ui" line="24"/>
+ <source>, (comma)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="CsvImportOptions.ui" line="29"/>
+ <source>; (semicolon)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="CsvImportOptions.ui" line="34"/>
+ <source>\t (tab)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="CsvImportOptions.ui" line="39"/>
+ <source> (whitespace)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="CsvImportOptions.ui" line="44"/>
+ <source>Custom:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="CsvImportOptions.ui" line="52"/>
+ <source>&lt;p&gt;Enable this if the first data line in your CSV file represents column names. You don&apos;t want column names to be imported into the table as a regular data.&lt;/p&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="CsvImportOptions.ui" line="55"/>
+ <source>First line represents CSV column names</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="CsvImportOptions.ui" line="65"/>
+ <source>Field separator:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="CsvImportOptions.ui" line="85"/>
+ <source>NULL values:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="CsvImportOptions.ui" line="95"/>
+ <source>If your CSV data contains null values, define how are they represented in the CSV.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/Plugins/DbAndroid/DbAndroid.pro b/Plugins/DbAndroid/DbAndroid.pro
new file mode 100644
index 0000000..2895c7f
--- /dev/null
+++ b/Plugins/DbAndroid/DbAndroid.pro
@@ -0,0 +1,53 @@
+#-------------------------------------------------
+#
+# Project created by QtCreator 2015-01-04T19:37:23
+#
+#-------------------------------------------------
+
+QT += widgets network
+
+include($$PWD/../../../sqlitestudio/SQLiteStudio3/plugins.pri)
+
+TARGET = DbAndroid
+TEMPLATE = lib
+
+DEFINES += DBANDROID_LIBRARY
+
+SOURCES += dbandroid.cpp \
+ dbandroidinstance.cpp \
+ sqlqueryandroid.cpp \
+ dbandroidurl.cpp \
+ dbandroidpathdialog.cpp \
+ adbmanager.cpp \
+ sqlresultrowandroid.cpp \
+ dbandroidjsonconnection.cpp \
+ dbandroidshellconnection.cpp \
+ dbandroidconnection.cpp \
+ dbandroidconnectionfactory.cpp
+
+HEADERS += dbandroid.h\
+ dbandroid_global.h \
+ dbandroidinstance.h \
+ sqlqueryandroid.h \
+ dbandroidurl.h \
+ dbandroidpathdialog.h \
+ adbmanager.h \
+ dbandroidconnection.h \
+ dbandroidmode.h \
+ sqlresultrowandroid.h \
+ dbandroidjsonconnection.h \
+ dbandroidshellconnection.h \
+ dbandroidconnectionfactory.h
+
+win32: {
+ LIBS += -lcoreSQLiteStudio -lguiSQLiteStudio
+}
+
+DISTFILES += \
+ dbandroid.json
+
+FORMS += \
+ dbandroidpathdialog.ui
+
+RESOURCES += \
+ dbandroid.qrc
diff --git a/Plugins/DbAndroid/adbmanager.cpp b/Plugins/DbAndroid/adbmanager.cpp
new file mode 100644
index 0000000..ed012a1
--- /dev/null
+++ b/Plugins/DbAndroid/adbmanager.cpp
@@ -0,0 +1,426 @@
+#include "adbmanager.h"
+#include "dbandroid.h"
+#include "common/utils.h"
+#include <QFileInfo>
+#include <QDebug>
+#include <QTimer>
+#include <QRegularExpression>
+
+AdbManager::AdbManager(DbAndroid* dbAndroidPlugin) :
+ QObject(dbAndroidPlugin), plugin(dbAndroidPlugin)
+{
+ connect(this, SIGNAL(internalDeviceListUpdate(QStringList)), this, SLOT(handleNewDeviceList(QStringList)));
+ connect(this, SIGNAL(deviceDetailsChanged(QList<Device>)), this, SLOT(handleNewDetails(QList<Device>)));
+
+ adbRunMonitor = new QTimer(this);
+ connect(adbRunMonitor, SIGNAL(timeout()), this, SLOT(updateDeviceList()));
+ adbRunMonitor->setSingleShot(false);
+ adbRunMonitor->setInterval(1000);
+ adbRunMonitor->start();
+ updateDeviceList();
+}
+
+AdbManager::~AdbManager()
+{
+ adbRunMonitor->stop();
+ updateDevicesFuture.waitForFinished();
+}
+
+const QStringList& AdbManager::getDevices(bool forceSyncUpdate)
+{
+ if (forceSyncUpdate)
+ syncDeviceListUpdate();
+
+ return currentDeviceList;
+}
+
+AdbManager::Device AdbManager::getDetails(const QString& deviceId)
+{
+ if (!currentDeviceDetails.contains(deviceId))
+ {
+ AdbManager::Device device;
+ device.id = deviceId;
+ return device;
+ }
+
+ return currentDeviceDetails[deviceId];
+}
+
+QList<AdbManager::Device> AdbManager::getDeviceDetails()
+{
+ return currentDeviceDetails.values();
+}
+
+QHash<QString, QPair<int, int>> AdbManager::getForwards()
+{
+ QHash<QString, QPair<int, int>> forwards;
+ QString stdOut;
+ if (!exec(QStringList({"forward", "--list"}), &stdOut))
+ return forwards;
+
+ QRegularExpression re("(.*)\\s+tcp:(\\d+)\\s+tcp:(\\d+)");
+ QRegularExpressionMatch match;
+ QPair<int, int> forward;
+ QStringList lines = stdOut.split("\n");
+ for (const QString& line : lines)
+ {
+ match = re.match(line);
+ if (!match.hasMatch())
+ continue;
+
+ forward.first = match.captured(2).toInt();
+ forward.second = match.captured(3).toInt();
+ forwards[match.captured(1)] = forward;
+ }
+
+ return forwards;
+}
+
+int AdbManager::makeForwardFor(const QString& device, int targetPort)
+{
+ static_qstring(portTpl, "tcp:%1");
+
+ QHash<QString, QPair<int, int>> forwards = getForwards();
+ if (forwards.contains(device) && forwards[device].second == targetPort)
+ return forwards[device].first;
+
+ int localPort = targetPort;
+ QStringList args = QStringList({"-s", device, "forward"});
+ args << portTpl.arg(localPort);
+ args << portTpl.arg(targetPort);
+
+ int tryCount = 0;
+ QString stdOut;
+ bool res;
+ while (!(res = exec(args, &stdOut)) && tryCount++ < 3)
+ {
+ localPort = rand(1025, 65000);
+ args.replace(3, portTpl.arg(localPort));
+ }
+
+ if (!res)
+ return -1;
+
+ return localPort;
+}
+
+QString AdbManager::findAdb()
+{
+ QStringList candidates;
+#ifdef Q_OS_WIN32
+ candidates << "adb.exe";
+#endif
+
+#ifdef Q_OS_MACX
+ candidates << (QDir::homePath() + "/Library/Android/sdk/platform-tools/adb");
+#endif
+
+#ifdef Q_OS_UNIX
+ candidates << "adb" << "./adb";
+
+ QProcess locate;
+ locate.start("locate", QStringList({"adb"}));
+ if (waitForProc(locate, true))
+ {
+ QFileInfo fi;
+ QStringList locateLines = decode(locate.readAllStandardOutput()).split("\n");
+ for (const QString& filePath : locateLines)
+ {
+ fi.setFile(filePath);
+ if (fi.fileName() != "adb" || !fi.isReadable() || !fi.isExecutable())
+ continue;
+
+ candidates << filePath;
+ }
+ }
+#endif
+
+#ifdef Q_OS_WIN32
+ if (testAdb("adb.exe", true))
+ return "adb.exe";
+
+ static_qstring(winAdbPath, "/../Android/sdk/platform-tools/adb.exe");
+ QString fullPath;
+ for (const QString& path : QStandardPaths::standardLocations(QStandardPaths::AppLocalDataLocation))
+ {
+ fullPath = QDir::cleanPath(path + winAdbPath);
+ if (testAdb(fullPath, true))
+ return fullPath;
+ }
+#endif
+
+ return QString();
+}
+
+bool AdbManager::testCurrentAdb()
+{
+ return testAdb(plugin->getCurrentAdb(), false);
+}
+
+bool AdbManager::testAdb(const QString& adbPath, bool quiet)
+{
+ if (adbPath.isEmpty())
+ return false;
+
+ QProcess adbApp;
+ adbApp.start(adbPath, QStringList({"version"}));
+ if (!waitForProc(adbApp, quiet))
+ return false;
+
+ QString verStr = decode(adbApp.readAllStandardOutput());
+ bool res = verStr.startsWith("Android Debug Bridge", Qt::CaseInsensitive);
+ if (!res && !quiet)
+ qWarning() << "Adb binary correct, but its version string is incorrect:" << verStr;
+
+ return res;
+}
+
+bool AdbManager::execBytes(const QStringList& arguments, QByteArray* stdOut, QByteArray* stdErr)
+{
+ if (!ensureAdbRunning())
+ return false;
+
+ QProcess proc;
+ if (arguments.join(" ").size() > 800)
+ {
+ if (!execLongCommand(arguments, proc, stdErr))
+ return false;
+ }
+ else
+ {
+ proc.start(plugin->getCurrentAdb(), arguments);
+ if (!waitForProc(proc, false))
+ return false;
+ }
+
+ if (stdOut)
+ *stdOut = proc.readAllStandardOutput();
+
+ if (stdErr)
+ *stdErr = proc.readAllStandardError();
+
+ return true;
+}
+
+bool AdbManager::waitForProc(QProcess& proc, bool quiet)
+{
+ if (!proc.waitForFinished(-1))
+ {
+ if (!quiet)
+ qDebug() << "DbAndroid QProcess timed out.";
+
+ return false;
+ }
+
+ if (proc.exitStatus() == QProcess::CrashExit)
+ {
+ if (!quiet)
+ {
+ qDebug() << "DbAndroid QProcess finished by crashing.";
+ qDebug() << proc.readAllStandardOutput() << proc.readAllStandardError();
+ }
+
+ return false;
+ }
+
+ if (proc.exitCode() != 0)
+ {
+ if (!quiet)
+ {
+ qDebug() << "DbAndroid QProcess finished with code:" << proc.exitCode();
+ qDebug() << proc.readAllStandardOutput() << proc.readAllStandardError();
+ }
+
+ return false;
+ }
+
+ return true;
+}
+
+bool AdbManager::ensureAdbRunning()
+{
+ if (!plugin->isAdbValid())
+ return false;
+
+ QProcess adbApp;
+ adbApp.start(plugin->getCurrentAdb(), QStringList({"start-server"}));
+ if (!waitForProc(adbApp, false))
+ return false;
+
+ return true;
+}
+
+bool AdbManager::exec(const QStringList& arguments, QString* stdOut, QString* stdErr)
+{
+ QByteArray* out = stdOut ? new QByteArray() : nullptr;
+ QByteArray* err = stdErr ? new QByteArray() : nullptr;
+ bool res = execBytes(arguments, out, err);
+
+ if (stdOut)
+ {
+ *stdOut = decode(*out);
+ delete out;
+ }
+
+ if (stdErr)
+ {
+ *stdErr = decode(*err);
+ delete err;
+ }
+
+ return res;
+}
+
+QByteArray AdbManager::encode(const QString& input)
+{
+ return input.toUtf8();
+}
+
+QString AdbManager::decode(const QByteArray& input)
+{
+ return QString::fromUtf8(input);
+}
+
+bool AdbManager::execLongCommand(const QStringList& arguments, QProcess& proc, QByteArray* stdErr)
+{
+ // Take off initial arguments from ADB, store it to use with "push".
+ QStringList primaryArguments;
+ QStringList args = arguments;
+ while (args.first() != "shell")
+ primaryArguments << args.takeFirst();
+
+ args.removeFirst(); // remove the shell itself
+
+ // Escape remaining arguments for the script
+ QString cmd = " '" + args.replaceInStrings("'", "'\''").join("' '") + "'";
+
+ // Now, the temporary file for the script
+ QTemporaryFile tmpFile("SQLiteStudio-XXXXXX.sh");
+ if (!tmpFile.open())
+ {
+ if (stdErr)
+ *stdErr = encode(QString("Could not create temporary file: %1").arg(tmpFile.fileName()));
+
+ return false;
+ }
+
+ tmpFile.write(cmd.toUtf8());
+ tmpFile.close();
+
+ // Push the file
+ args = primaryArguments;
+ args << "push" << tmpFile.fileName() << "/data/local/tmp";
+ proc.start(plugin->getCurrentAdb(), args);
+ if (!waitForProc(proc, false))
+ return false;
+
+ QString remoteFile = ("/data/local/tmp/" + QFileInfo(tmpFile.fileName()).fileName());
+
+ // Execute the file
+ args = primaryArguments;
+ args << "shell" << "sh" << remoteFile;
+ proc.start(plugin->getCurrentAdb(), args);
+ if (!waitForProc(proc, false))
+ return false;
+
+ // Delete the file from device
+ args = primaryArguments;
+ args << "shell" << "rm" << remoteFile;
+ QProcess localProc;
+ localProc.start(plugin->getCurrentAdb(), args);
+ if (!waitForProc(localProc, false))
+ {
+ // Not a critical issue...
+ qWarning() << "Could not clean up execution script from the device: " << remoteFile << "\nDetails:\n"
+ << localProc.readAllStandardOutput() << "\n" << localProc.readAllStandardError();
+ }
+
+ return true;
+}
+
+QStringList AdbManager::getDevicesInternal(bool emitSignal)
+{
+ QStringList devices;
+ QString stdOut;
+ if (!exec(QStringList({"devices"}), &stdOut))
+ {
+ if (emitSignal)
+ emit internalDeviceListUpdate(devices);
+
+ return devices;
+ }
+
+ QRegularExpression re("(.*)\\s+device$");
+ QRegularExpressionMatch match;
+ QStringList lines = stdOut.split("\n");
+ for (const QString& line : lines)
+ {
+ match = re.match(line.trimmed());
+ if (!match.hasMatch())
+ continue;
+
+ devices << match.captured(1).trimmed();
+ }
+
+ if (emitSignal)
+ emit internalDeviceListUpdate(devices);
+
+ return devices;
+}
+
+void AdbManager::syncDeviceListUpdate()
+{
+ currentDeviceList = getDevicesInternal(false);
+ updateDetails(currentDeviceList);
+}
+
+void AdbManager::updateDetails(const QStringList& devices)
+{
+ QString stdOut;
+ QList<Device> detailList;
+ for (const QString& deviceId : devices)
+ {
+ Device deviceDetails;
+ deviceDetails.id = deviceId;
+ if (exec(QStringList({"-s", deviceId, "shell", "getprop", "ro.product.manufacturer"}), &stdOut))
+ deviceDetails.fullName = stdOut.trimmed();
+ else
+ qWarning() << "Could not read brand for device" << deviceId;
+
+ if (exec(QStringList({"-s", deviceId, "shell", "getprop", "ro.product.model"}), &stdOut))
+ deviceDetails.fullName += " " + stdOut.trimmed();
+ else
+ qWarning() << "Could not read brand for device" << deviceId;
+
+ deviceDetails.fullName = deviceDetails.fullName.trimmed();
+ detailList << deviceDetails;
+ }
+
+ emit deviceDetailsChanged(detailList);
+}
+
+void AdbManager::updateDeviceList()
+{
+ if (!plugin->isAdbValid())
+ return;
+
+ updateDevicesFuture = QtConcurrent::run(this, &AdbManager::getDevicesInternal, true);
+}
+
+void AdbManager::handleNewDeviceList(const QStringList& devices)
+{
+ if (currentDeviceList == devices)
+ return;
+
+ currentDeviceList = devices;
+ QtConcurrent::run(this, &AdbManager::updateDetails, devices);
+
+ emit deviceListChanged(devices);
+}
+
+void AdbManager::handleNewDetails(const QList<AdbManager::Device>& devices)
+{
+ currentDeviceDetails.clear();
+ for (Device device : devices)
+ currentDeviceDetails[device.id] = device;
+}
diff --git a/Plugins/DbAndroid/adbmanager.h b/Plugins/DbAndroid/adbmanager.h
new file mode 100644
index 0000000..adeca90
--- /dev/null
+++ b/Plugins/DbAndroid/adbmanager.h
@@ -0,0 +1,65 @@
+#ifndef ADBMANAGER_H
+#define ADBMANAGER_H
+
+#include <QObject>
+#include <QProcess>
+#include <QtConcurrent/QtConcurrent>
+
+class DbAndroid;
+class QTimer;
+
+class AdbManager : public QObject
+{
+ Q_OBJECT
+ public:
+ struct Device
+ {
+ QString id;
+ QString fullName;
+ };
+
+ AdbManager(DbAndroid* plugin);
+ ~AdbManager();
+
+ const QStringList& getDevices(bool forceSyncUpdate = false);
+ Device getDetails(const QString& deviceId);
+ QList<Device> getDeviceDetails();
+ QHash<QString,QPair<int,int>> getForwards();
+ int makeForwardFor(const QString& device, int targetPort);
+ QString findAdb();
+ bool testCurrentAdb();
+ bool testAdb(const QString& adbPath, bool quiet = false);
+ bool execBytes(const QStringList& arguments, QByteArray* stdOut = nullptr, QByteArray* stdErr = nullptr);
+ bool exec(const QStringList& arguments, QString* stdOut = nullptr, QString* stdErr = nullptr);
+
+ static QByteArray encode(const QString& input);
+ static QString decode(const QByteArray& input);
+
+ private:
+ bool execLongCommand(const QStringList& arguments, QProcess& proc, QByteArray* stdErr);
+ bool waitForProc(QProcess& proc, bool quiet = false);
+ bool ensureAdbRunning();
+ QStringList getDevicesInternal(bool emitSignal);
+ void syncDeviceListUpdate();
+ void updateDetails(const QStringList& devices);
+
+ DbAndroid* plugin;
+ QTimer* adbRunMonitor = nullptr;
+ QStringList currentDeviceList;
+ QHash<QString,Device> currentDeviceDetails;
+ QFuture<QStringList> updateDevicesFuture;
+
+ private slots:
+ void updateDeviceList();
+ void handleNewDeviceList(const QStringList& devices);
+ void handleNewDetails(const QList<Device>& devices);
+
+ signals:
+ void internalDeviceListUpdate(const QStringList& devices);
+ void deviceListChanged(const QStringList& devices);
+ void deviceDetailsChanged(const QList<Device>& details);
+};
+
+Q_DECLARE_METATYPE(QList<AdbManager::Device>)
+
+#endif // ADBMANAGER_H
diff --git a/Plugins/DbAndroid/dbandroid.cpp b/Plugins/DbAndroid/dbandroid.cpp
new file mode 100644
index 0000000..99150cd
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroid.cpp
@@ -0,0 +1,209 @@
+#include "adbmanager.h"
+#include "dbandroid.h"
+#include "dbandroidinstance.h"
+#include "dbandroidpathdialog.h"
+#include "mainwindow.h"
+#include "services/notifymanager.h"
+#include "uiconfig.h"
+#include "statusfield.h"
+#include "services/dbmanager.h"
+#include "dbandroidconnectionfactory.h"
+#include <QUrl>
+#include <QDebug>
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QtConcurrent/QtConcurrent>
+
+DbAndroid::DbAndroid()
+{
+}
+
+QString DbAndroid::getLabel() const
+{
+ return "Android SQLite";
+}
+
+bool DbAndroid::checkIfDbServedByPlugin(Db* db) const
+{
+ return (db && dynamic_cast<DbAndroidInstance*>(db));
+}
+
+Db* DbAndroid::getInstance(const QString& name, const QString& path, const QHash<QString, QVariant>& options, QString* errorMessage)
+{
+ DbAndroidUrl url(path);
+ if (!url.isValid())
+ {
+ if (errorMessage)
+ *errorMessage = tr("Invalid or incomplete Android Database URL.");
+
+ return nullptr;
+ }
+
+ DbAndroidInstance* db = new DbAndroidInstance(this, name, path, options);
+ return db;
+}
+
+QList<DbPluginOption> DbAndroid::getOptionsList() const
+{
+ QList<DbPluginOption> options;
+
+ DbPluginOption customBrowseOpt;
+ customBrowseOpt.type = DbPluginOption::Type::CUSTOM_PATH_BROWSE;
+ customBrowseOpt.label = tr("Android database URL");
+ customBrowseOpt.toolTip = tr("Select Android database");
+ customBrowseOpt.customBrowseHandler = [this](const QString& initialPath) -> QString
+ {
+ DbAndroidPathDialog dialog(this, MAINWINDOW);
+ dialog.setUrl(initialPath);
+ if (!dialog.exec())
+ return QString();
+
+ return dialog.getUrl().toUrlString();
+ };
+ options << customBrowseOpt;
+
+ return options;
+}
+
+QString DbAndroid::generateDbName(const QVariant& baseValue)
+{
+ // android://drgh:port/dbName
+ QUrl url(baseValue.toString());
+ if (!url.isValid())
+ return baseValue.toString();
+
+ return url.fileName();
+}
+
+bool DbAndroid::init()
+{
+ Q_INIT_RESOURCE(dbandroid);
+
+ qRegisterMetaType<QList<AdbManager::Device>>("QList<Device>");
+
+ connect(this, SIGNAL(adbReady(bool)), this, SLOT(handleValidAdb(bool)));
+ connect(this, SIGNAL(invalidAdb()), this, SLOT(handleInvalidAdb()));
+ connect(MAINWINDOW->getStatusField(), SIGNAL(linkActivated(QString)), this, SLOT(statusFieldLinkClicked(QString)));
+
+ connectionFactory = new DbAndroidConnectionFactory(this);
+
+ adbManager = new AdbManager(this);
+ connect(adbManager, SIGNAL(deviceListChanged(QStringList)), this, SLOT(deviceListChanged()));
+
+ if (adbManager->testCurrentAdb())
+ {
+ qDebug() << "Using ADB binary:" << cfg.DbAndroid.AdbPath.get();
+ adbValid = true;
+ adbManager->getDevices(true);
+ }
+ else
+ {
+ QtConcurrent::run(this, &DbAndroid::initAdb);
+ }
+ return true;
+}
+
+void DbAndroid::deinit()
+{
+ safe_delete(connectionFactory);
+ safe_delete(adbManager);
+ Q_CLEANUP_RESOURCE(dbandroid);
+}
+
+QString DbAndroid::getCurrentAdb()
+{
+ return cfg.DbAndroid.AdbPath.get();
+}
+
+void DbAndroid::initAdb()
+{
+ QString adbPath = adbManager->findAdb();
+ if (!adbPath.isEmpty())
+ {
+ cfg.DbAndroid.AdbPath.set(adbPath);
+ qDebug() << "Found ADB binary:" << cfg.DbAndroid.AdbPath.get();
+ emit adbReady(true);
+ return;
+ }
+
+ emit invalidAdb();
+}
+
+QString DbAndroid::askForAdbPath()
+{
+#if defined(Q_OS_UNIX)
+ QString adbAppName = "adb";
+#elif defined(Q_OS_WIN32)
+ QString adbAppName = "adb.exe";
+#else
+ qCritical() << "Unsupported OS for DbAndroid.";
+ return QString();
+#endif
+ QString file = QFileDialog::getOpenFileName(MAINWINDOW, tr("Select ADB"), getFileDialogInitPath(), QString("Android Debug Bridge (%1)").arg(adbAppName));
+ if (file.isEmpty())
+ return file;
+
+ setFileDialogInitPathByFile(file);
+ return file;
+}
+
+bool DbAndroid::isAdbValid() const
+{
+ return adbValid;
+}
+
+DbAndroidConnectionFactory*DbAndroid::getConnectionFactory() const
+{
+ return connectionFactory;
+}
+
+void DbAndroid::handleValidAdb(bool showMessage)
+{
+ adbValid = true;
+ if (showMessage)
+ notifyInfo(tr("Using Android Debug Bridge: %1").arg(cfg.DbAndroid.AdbPath.get()));
+
+ DBLIST->rescanInvalidDatabasesForPlugin(this);
+}
+
+void DbAndroid::handleInvalidAdb()
+{
+ notifyError(tr("Could not find Android Debug Bridge application. <a href=\"%1\">Click here</a> to point out the location of the ADB application, "
+ "otherwise the %2 plugin will not support USB cable connections, only the network connection..").arg(SELECT_ADB_URL, getLabel()));
+}
+
+void DbAndroid::statusFieldLinkClicked(const QString& link)
+{
+ if (link == SELECT_ADB_URL)
+ {
+ QString file = askForAdbPath();
+ while (!file.isEmpty())
+ {
+ if (adbManager->testAdb(file))
+ {
+ cfg.DbAndroid.AdbPath.set(file);
+ emit adbReady(true);
+ return;
+ }
+
+ int res = QMessageBox::warning(MAINWINDOW, tr("Invalid ADB"), tr("The selected ADB is incorrect.\n"
+ "Would you like to select another one, or leave it unconfigured?"),
+ tr("Select another ADB"), tr("Leave unconfigured"));
+
+ if (res == 1)
+ return;
+
+ file = askForAdbPath();
+ }
+ }
+}
+
+void DbAndroid::deviceListChanged()
+{
+ DBLIST->rescanInvalidDatabasesForPlugin(this);
+}
+
+AdbManager* DbAndroid::getAdbManager() const
+{
+ return adbManager;
+}
diff --git a/Plugins/DbAndroid/dbandroid.h b/Plugins/DbAndroid/dbandroid.h
new file mode 100644
index 0000000..68893d0
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroid.h
@@ -0,0 +1,63 @@
+#ifndef DBANDROID_H
+#define DBANDROID_H
+
+#include "dbandroid_global.h"
+#include "plugins/dbplugin.h"
+#include "plugins/genericplugin.h"
+#include "config_builder.h"
+
+class AdbManager;
+class DbAndroidConnectionFactory;
+
+CFG_CATEGORIES(DbAndroidConfig,
+ CFG_CATEGORY(DbAndroid,
+ CFG_ENTRY(QString, AdbPath, QString())
+ )
+)
+
+class DBANDROIDSHARED_EXPORT DbAndroid : public GenericPlugin, public DbPlugin
+{
+ Q_OBJECT
+ SQLITESTUDIO_PLUGIN("dbandroid.json")
+
+ public:
+ DbAndroid();
+
+ QString getLabel() const;
+ bool checkIfDbServedByPlugin(Db* db) const;
+ Db* getInstance(const QString& name, const QString& path, const QHash<QString, QVariant>& options, QString* errorMessage);
+ QList<DbPluginOption> getOptionsList() const;
+ QString generateDbName(const QVariant& baseValue);
+ bool init();
+ void deinit();
+ QString getCurrentAdb();
+ AdbManager* getAdbManager() const;
+ bool isAdbValid() const;
+ DbAndroidConnectionFactory* getConnectionFactory() const;
+
+ static_char* PASSWORD_OPT = "remote_access_password";
+
+ private:
+ void initAdb();
+ QString askForAdbPath();
+
+ AdbManager* adbManager = nullptr;
+ DbAndroidConnectionFactory* connectionFactory = nullptr;
+ bool adbValid = false;
+
+ static_char* SELECT_ADB_URL = "select_adb://";
+
+ CFG_LOCAL_PERSISTABLE(DbAndroidConfig, cfg)
+
+ private slots:
+ void handleValidAdb(bool showMessage);
+ void handleInvalidAdb();
+ void statusFieldLinkClicked(const QString& link);
+ void deviceListChanged();
+
+ signals:
+ void adbReady(bool showMessage);
+ void invalidAdb();
+};
+
+#endif // DBANDROID_H
diff --git a/Plugins/DbAndroid/dbandroid.json b/Plugins/DbAndroid/dbandroid.json
new file mode 100644
index 0000000..0808bb4
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroid.json
@@ -0,0 +1,9 @@
+{
+ "type": "DbPlugin",
+ "title": "Android SQLite",
+ "description": "Provides support for remote SQLite databases on Android devices.",
+ "version": 10100,
+ "author": "SalSoft",
+ "minAppVersion": 30006,
+ "gui": true
+}
diff --git a/Plugins/DbAndroid/dbandroid.qrc b/Plugins/DbAndroid/dbandroid.qrc
new file mode 100644
index 0000000..7646d2b
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroid.qrc
@@ -0,0 +1 @@
+<RCC/>
diff --git a/Plugins/DbAndroid/dbandroid_global.h b/Plugins/DbAndroid/dbandroid_global.h
new file mode 100644
index 0000000..aa128c1
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroid_global.h
@@ -0,0 +1,12 @@
+#ifndef DBANDROID_GLOBAL_H
+#define DBANDROID_GLOBAL_H
+
+#include <QtCore/qglobal.h>
+
+#if defined(DBANDROID_LIBRARY)
+# define DBANDROIDSHARED_EXPORT Q_DECL_EXPORT
+#else
+# define DBANDROIDSHARED_EXPORT Q_DECL_IMPORT
+#endif
+
+#endif // DBANDROID_GLOBAL_H
diff --git a/Plugins/DbAndroid/dbandroidconnection.cpp b/Plugins/DbAndroid/dbandroidconnection.cpp
new file mode 100644
index 0000000..1d5ba05
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroidconnection.cpp
@@ -0,0 +1,14 @@
+#include "dbandroidconnection.h"
+#include <QDebug>
+
+QByteArray DbAndroidConnection::convertBlob(const QString& value)
+{
+ if (!value.startsWith("X'", Qt::CaseInsensitive) || !value.endsWith("'"))
+ {
+ qCritical() << "Invalid BLOB value from Android. Doesn't match BLOB pattern:" << value;
+ return QByteArray();
+ }
+
+ return QByteArray::fromHex(value.mid(2, value.length() - 3).toLatin1());
+}
+
diff --git a/Plugins/DbAndroid/dbandroidconnection.h b/Plugins/DbAndroid/dbandroidconnection.h
new file mode 100644
index 0000000..11e9be0
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroidconnection.h
@@ -0,0 +1,44 @@
+#ifndef DBANDROIDCONNECTION_H
+#define DBANDROIDCONNECTION_H
+
+#include "dbandroidurl.h"
+#include <QObject>
+#include <QStringList>
+#include <QHash>
+
+class DbAndroidConnection : public QObject
+{
+ Q_OBJECT
+
+ public:
+ struct ExecutionResult
+ {
+ bool wasError = false;
+ int errorCode = 0;
+ QString errorMsg;
+ QStringList resultColumns;
+ QList<QVariantHash> resultDataMap;
+ QList<QVariantList> resultDataList;
+ };
+
+ DbAndroidConnection(QObject* parent = 0) : QObject(parent) {}
+ virtual ~DbAndroidConnection() {}
+
+ virtual bool connectToAndroid(const DbAndroidUrl& url) = 0;
+ virtual void disconnectFromAndroid() = 0;
+ virtual bool isConnected() const = 0;
+ virtual QString getDbName() const = 0;
+ virtual QStringList getDbList() = 0;
+ virtual QStringList getAppList() = 0;
+ virtual bool isAppOkay() const = 0;
+ virtual bool deleteDatabase(const QString& dbName) = 0;
+ virtual ExecutionResult executeQuery(const QString& query) = 0;
+
+ protected:
+ static QByteArray convertBlob(const QString& value);
+
+ signals:
+ void disconnected();
+};
+
+#endif // DBANDROIDCONNECTION_H
diff --git a/Plugins/DbAndroid/dbandroidconnectionfactory.cpp b/Plugins/DbAndroid/dbandroidconnectionfactory.cpp
new file mode 100644
index 0000000..22377b4
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroidconnectionfactory.cpp
@@ -0,0 +1,28 @@
+#include "dbandroidconnectionfactory.h"
+#include "dbandroidjsonconnection.h"
+#include "dbandroidshellconnection.h"
+
+DbAndroidConnectionFactory::DbAndroidConnectionFactory(DbAndroid* plugin) :
+ plugin(plugin)
+{
+}
+
+DbAndroidConnection* DbAndroidConnectionFactory::create(const QString& url, QObject* parent)
+{
+ return create(DbAndroidUrl(url), parent);
+}
+
+DbAndroidConnection* DbAndroidConnectionFactory::create(const DbAndroidUrl& url, QObject* parent)
+{
+ switch (url.getMode())
+ {
+ case DbAndroidMode::SHELL:
+ return new DbAndroidShellConnection(plugin, parent);
+ case DbAndroidMode::NETWORK:
+ case DbAndroidMode::USB:
+ return new DbAndroidJsonConnection(plugin, parent);
+ case DbAndroidMode::null:
+ break;
+ }
+ return nullptr;
+}
diff --git a/Plugins/DbAndroid/dbandroidconnectionfactory.h b/Plugins/DbAndroid/dbandroidconnectionfactory.h
new file mode 100644
index 0000000..0e46c9f
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroidconnectionfactory.h
@@ -0,0 +1,21 @@
+#ifndef DBANDROIDCONNECTIONFACTORY_H
+#define DBANDROIDCONNECTIONFACTORY_H
+
+#include "dbandroidurl.h"
+
+class DbAndroidConnection;
+class DbAndroid;
+
+class DbAndroidConnectionFactory
+{
+ public:
+ explicit DbAndroidConnectionFactory(DbAndroid* plugin);
+
+ DbAndroidConnection* create(const QString& url, QObject* parent = nullptr);
+ DbAndroidConnection* create(const DbAndroidUrl& url, QObject* parent = nullptr);
+
+ private:
+ DbAndroid* plugin = nullptr;
+};
+
+#endif // DBANDROIDCONNECTIONFACTORY_H
diff --git a/Plugins/DbAndroid/dbandroidinstance.cpp b/Plugins/DbAndroid/dbandroidinstance.cpp
new file mode 100644
index 0000000..b60203e
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroidinstance.cpp
@@ -0,0 +1,143 @@
+#include "dbandroidconnection.h"
+#include "dbandroidinstance.h"
+#include "sqlqueryandroid.h"
+#include "db/sqlerrorcodes.h"
+#include "common/unused.h"
+#include "dbandroid.h"
+#include "dbandroidjsonconnection.h"
+#include "dbandroidconnectionfactory.h"
+#include "dbandroidurl.h"
+#include "schemaresolver.h"
+#include "services/notifymanager.h"
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QDebug>
+
+DbAndroidInstance::DbAndroidInstance(DbAndroid* plugin, const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions) :
+ AbstractDb(name, path, connOptions), plugin(plugin)
+{
+ this->connOptions[SchemaResolver::USE_SCHEMA_CACHING] = true;
+}
+
+DbAndroidInstance::~DbAndroidInstance()
+{
+ closeInternal();
+}
+
+SqlQueryPtr DbAndroidInstance::prepare(const QString& query)
+{
+ return SqlQueryPtr(new SqlQueryAndroid(this, connection, query));
+}
+
+QString DbAndroidInstance::getTypeLabel()
+{
+ return plugin->getLabel();
+}
+
+bool DbAndroidInstance::deregisterFunction(const QString& name, int argCount)
+{
+ // Unsupported by native Android driver
+ UNUSED(name);
+ UNUSED(argCount);
+ return true;
+}
+
+bool DbAndroidInstance::registerScalarFunction(const QString& name, int argCount)
+{
+ // Unsupported by native Android driver
+ UNUSED(name);
+ UNUSED(argCount);
+ return true;
+}
+
+bool DbAndroidInstance::registerAggregateFunction(const QString& name, int argCount)
+{
+ // Unsupported by native Android driver
+ UNUSED(name);
+ UNUSED(argCount);
+ return true;
+}
+
+bool DbAndroidInstance::initAfterCreated()
+{
+ version = 3;
+ return AbstractDb::initAfterCreated();
+}
+
+bool DbAndroidInstance::isOpenInternal()
+{
+ return (connection && connection->isConnected());
+}
+
+void DbAndroidInstance::interruptExecution()
+{
+ // Unsupported by native Android driver
+}
+
+QString DbAndroidInstance::getErrorTextInternal()
+{
+ return errorText;
+}
+
+int DbAndroidInstance::getErrorCodeInternal()
+{
+ return errorCode;
+}
+
+bool DbAndroidInstance::openInternal()
+{
+ connection = createConnection();
+ bool res = connection->connectToAndroid(DbAndroidUrl(path));
+ if (!res)
+ {
+ safe_delete(connection);
+ }
+ else
+ {
+ connect(connection, SIGNAL(disconnected()), this, SLOT(handleDisconnected()));
+ }
+
+ return res;
+}
+
+bool DbAndroidInstance::closeInternal()
+{
+ if (!connection)
+ return false;
+
+ disconnect(connection, SIGNAL(disconnected()), this, SLOT(handleDisconnected()));
+ connection->disconnectFromAndroid();
+ safe_delete(connection);
+ return true;
+}
+
+bool DbAndroidInstance::registerCollationInternal(const QString& name)
+{
+ // Unsupported by native Android driver
+ UNUSED(name);
+ return true;
+}
+
+bool DbAndroidInstance::deregisterCollationInternal(const QString& name)
+{
+ // Unsupported by native Android driver
+ UNUSED(name);
+ return true;
+}
+
+DbAndroidConnection* DbAndroidInstance::createConnection()
+{
+ DbAndroidUrl url(path);
+ if (!url.isValid(false))
+ return nullptr;
+
+ return plugin->getConnectionFactory()->create(url, this);
+}
+
+void DbAndroidInstance::handleDisconnected()
+{
+ safe_delete(connection);
+ notifyWarn(tr("Connection with Android database '%1' lost.").arg(getName()));
+ emit disconnected();
+}
diff --git a/Plugins/DbAndroid/dbandroidinstance.h b/Plugins/DbAndroid/dbandroidinstance.h
new file mode 100644
index 0000000..453b370
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroidinstance.h
@@ -0,0 +1,51 @@
+#ifndef DBANDROIDINSTANCE_H
+#define DBANDROIDINSTANCE_H
+
+#include "db/abstractdb.h"
+#include <QObject>
+#include <functional>
+#include <QCache>
+
+class DbAndroidConnection;
+class DbAndroid;
+
+class DbAndroidInstance : public AbstractDb
+{
+ Q_OBJECT
+
+ public:
+ typedef std::function<void(const QStringList&)> AsyncDbListResponseHandler;
+
+ DbAndroidInstance(DbAndroid* plugin, const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions);
+ ~DbAndroidInstance();
+
+ SqlQueryPtr prepare(const QString& query);
+ QString getTypeLabel();
+ bool deregisterFunction(const QString& name, int argCount);
+ bool registerScalarFunction(const QString& name, int argCount);
+ bool registerAggregateFunction(const QString& name, int argCount);
+ bool initAfterCreated();
+
+ protected:
+ bool isOpenInternal();
+ void interruptExecution();
+ QString getErrorTextInternal();
+ int getErrorCodeInternal();
+ bool openInternal();
+ bool closeInternal();
+ bool registerCollationInternal(const QString& name);
+ bool deregisterCollationInternal(const QString& name);
+
+ private:
+ DbAndroidConnection* createConnection();
+
+ DbAndroid* plugin = nullptr;
+ DbAndroidConnection* connection = nullptr;
+ int errorCode = 0;
+ QString errorText;
+
+ private slots:
+ void handleDisconnected();
+};
+
+#endif // DBANDROIDINSTANCE_H
diff --git a/Plugins/DbAndroid/dbandroidjsonconnection.cpp b/Plugins/DbAndroid/dbandroidjsonconnection.cpp
new file mode 100644
index 0000000..05d6469
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroidjsonconnection.cpp
@@ -0,0 +1,430 @@
+#include "dbandroidjsonconnection.h"
+#include "dbandroid.h"
+#include "adbmanager.h"
+#include "services/notifymanager.h"
+#include "common/blockingsocket.h"
+#include "db/sqlerrorcodes.h"
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QtConcurrent/QtConcurrent>
+
+DbAndroidJsonConnection::DbAndroidJsonConnection(DbAndroid* plugin, QObject *parent) :
+ DbAndroidConnection(parent), plugin(plugin)
+{
+ socket = new BlockingSocket(this);
+ adbManager = plugin->getAdbManager();
+ connect(socket, SIGNAL(disconnected()), this, SLOT(handlePossibleDisconnection()));
+}
+
+DbAndroidJsonConnection::~DbAndroidJsonConnection()
+{
+ cleanUp();
+}
+
+bool DbAndroidJsonConnection::connectToAndroid(const DbAndroidUrl& url)
+{
+ if (isConnected())
+ {
+ qWarning() << "Already connected while calling DbAndroidConnection::connect().";
+ return false;
+ }
+
+ dbUrl = url;
+ mode = url.getMode();
+
+ switch (mode)
+ {
+ case DbAndroidMode::NETWORK:
+ return connectToNetwork();
+ case DbAndroidMode::USB:
+ return connectToDevice();
+ case DbAndroidMode::SHELL:
+ qCritical() << "SHELL mode encountered in DbAndroidJsonConnection";
+ break;
+ case DbAndroidMode::null:
+ qCritical() << "Null mode encountered in DbAndroidJsonConnection";
+ break;
+ }
+
+ qCritical() << "Invalid Android db mode while connecting:" << static_cast<int>(mode);
+ return false;
+}
+
+void DbAndroidJsonConnection::disconnectFromAndroid()
+{
+ socket->disconnectFromHost();
+ connectedState = false;
+}
+
+bool DbAndroidJsonConnection::isConnected() const
+{
+ if (!socket)
+ return false;
+
+ return connectedState;
+}
+
+QByteArray DbAndroidJsonConnection::send(const QByteArray& data)
+{
+ QByteArray bytes = sizeToBytes(data.size());
+ bytes.append(data);
+ return sendBytes(bytes);
+}
+
+QString DbAndroidJsonConnection::getDbName() const
+{
+ return dbUrl.getDbName();
+}
+
+QByteArray DbAndroidJsonConnection::sendBytes(const QByteArray& data)
+{
+ //qDebug() << "Sending" << data;
+ bool success = socket->send(data);
+ if (!success)
+ {
+ qCritical() << "Error writing bytes to Android socket:" << socket->getErrorText();
+ return QByteArray();
+ }
+
+ QByteArray sizeBytes = socket->read(4, 5000, &success);
+ if (!success)
+ {
+ qCritical() << "Error reading response size from Android socket:" << socket->getErrorText();
+ return QByteArray();
+ }
+
+ qint32 size = bytesToSize(sizeBytes);
+ QByteArray responseBytes = socket->read(size, 5000, &success);
+ if (!success)
+ {
+ qCritical() << "Error reading response from Android socket:" << socket->getErrorText();
+ return QByteArray();
+ }
+ //qDebug() << "Received" << responseBytes;
+ return responseBytes;
+}
+
+void DbAndroidJsonConnection::handleSocketError()
+{
+ qWarning() << "Blocking socket error in Android connection:" << socket->getErrorText();
+ handlePossibleDisconnection();
+}
+
+void DbAndroidJsonConnection::handlePossibleDisconnection()
+{
+ if (connectedState && !socket->isConnected())
+ {
+ connectedState = false;
+ emit disconnected();
+ }
+}
+
+QByteArray DbAndroidJsonConnection::sizeToBytes(qint32 size)
+{
+ QByteArray bytes;
+ for (int i = 0; i < 4; i++)
+ bytes.append((size >> (8*i)) & 0xff);
+
+ return bytes;
+}
+
+qint32 DbAndroidJsonConnection::bytesToSize(const QByteArray& bytes)
+{
+ int size = (((unsigned char)bytes[3]) << 24) |
+ (((unsigned char)bytes[2]) << 16) |
+ (((unsigned char)bytes[1]) << 8) |
+ ((unsigned char)bytes[0]);
+
+ return size;
+}
+
+QVariant DbAndroidJsonConnection::convertJsonValue(const QJsonValue& value)
+{
+ if (value.isArray())
+ {
+ // BLOB
+ QJsonArray blobContainer = value.toArray();
+ if (blobContainer.size() < 1)
+ {
+ qCritical() << "Invalid blob value from Android - empty array.";
+ return QByteArray();
+ }
+
+ return convertBlob(blobContainer.first().toString());
+ }
+
+ // Regular value
+ return value.toVariant();
+}
+
+bool DbAndroidJsonConnection::connectToNetwork()
+{
+ if (!dbUrl.isHostValid())
+ return false;
+
+ return connectToTcp(dbUrl.getHost(), dbUrl.getPort());
+}
+
+bool DbAndroidJsonConnection::connectToDevice()
+{
+ if (!plugin->isAdbValid())
+ return false;
+
+ if (!plugin->getAdbManager()->getDevices().contains(dbUrl.getDevice()))
+ {
+ notifyWarn(tr("Cannot connect to device %1, because it's not visible to your computer.").arg(dbUrl.getDevice()));
+ return false;
+ }
+
+ int localPort = plugin->getAdbManager()->makeForwardFor(dbUrl.getDevice(), dbUrl.getPort());
+ if (localPort < 0)
+ {
+ notifyError(tr("Failed to create port forwarding for device %1 for port %2.")
+ .arg(dbUrl.getDevice(), QString::number(dbUrl.getPort())));
+ return false;
+ }
+
+ return connectToTcp("127.0.0.1", localPort);
+}
+
+bool DbAndroidJsonConnection::connectToTcp(const QString& ip, int port)
+{
+ bool success = socket->connectToHost(ip, port);
+ if (!success)
+ {
+ qWarning() << "Could not connect to network host for Android DB:" << ip << ":" << port << ", details:" << socket->getErrorText();
+ notifyWarn(tr("Could not connect to network host: %1:%2").arg(ip, QString::number(port)));
+ return false;
+ }
+
+ connectedState = true;
+
+ // Evaluate the connection and send the security token
+ static_qstring(pingCmd, "{ping:\"%1\"}");
+ QDate date = QDateTime::currentDateTime().date();
+ QString tokenString = "06fn43" + QString::number(date.dayOfYear()) + "3ig7ws" + QString::number(date.year()) + "53";
+ QByteArray md5 = QCryptographicHash::hash(tokenString.toUtf8(), QCryptographicHash::Md5);
+ QByteArray token = md5.toBase64();
+
+ QByteArray response = send(pingCmd.arg(QString::fromLatin1(token)).toUtf8());
+ if (response != PING_RESPONSE_OK)
+ {
+ qWarning() << "Connection to" << ip << ":" << port << "has failed, because response to PING was:" << response;
+ notifyWarn(tr("Cannot connect to SQLiteStudio service on Android device. The remote service is unavailable or invalid."));
+ handleConnectionFailed();
+ return false;
+ }
+
+ // Authenticate
+ QString pass = dbUrl.getPassword();
+ if (!pass.isEmpty())
+ {
+ static_qstring(passPharse, "{auth:\"%1\"}");
+ response = send(passPharse.arg(pass.replace("\"", "\\\"")).toUtf8());
+ if (response != PASS_RESPONSE_OK)
+ {
+ notifyWarn(tr("Cannot connect to %1:%2, because password is invalid.").arg(ip, QString::number(port)));
+ handleConnectionFailed();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void DbAndroidJsonConnection::handleConnectionFailed()
+{
+ connectedState = false;
+ socket->disconnectFromHost();
+}
+
+void DbAndroidJsonConnection::cleanUp()
+{
+ disconnectFromAndroid();
+ safe_delete(socket);
+}
+
+QStringList DbAndroidJsonConnection::getDbList()
+{
+ if (!isConnected())
+ {
+ qWarning() << "Called DbAndroidJsonConnection::getDbList() on closed connection.";
+ return QStringList();
+ }
+
+ QByteArray result = send(LIST_CMD);
+ return handleDbListResult(result);
+}
+
+QStringList DbAndroidJsonConnection::getAppList()
+{
+ return QStringList();
+}
+
+bool DbAndroidJsonConnection::isAppOkay() const
+{
+ return true;
+}
+
+QStringList DbAndroidJsonConnection::handleDbListResult(const QByteArray& results)
+{
+ QJsonParseError jsonError;
+ QJsonDocument jsonResponse = QJsonDocument::fromJson(results, &jsonError);
+ if (jsonError.error != QJsonParseError::NoError)
+ {
+ qCritical() << "Error while parsing response from Android:" << jsonError.errorString();
+ return QStringList();
+ }
+
+ QJsonObject responseObject = jsonResponse.object();
+ if (responseObject.contains("generic_error"))
+ {
+ qCritical() << "Generic error from Android:" << responseObject["generic_error"].toInt();
+ return QStringList();
+ }
+
+ if (!responseObject.contains("list"))
+ {
+ qCritical() << "Missing 'list' in response from Android.";
+ return QStringList();
+ }
+
+ QStringList dbNames;
+ for (const QVariant& name : responseObject["list"].toArray().toVariantList())
+ dbNames << name.toString();
+
+ return dbNames;
+}
+
+bool DbAndroidJsonConnection::deleteDatabase(const QString& dbName)
+{
+ if (!isConnected())
+ {
+ qWarning() << "Called DbAndroidConnection::deleteDatabase() on closed database.";
+ return false;
+ }
+
+ QByteArray result = send(QString(DELETE_DB_CMD).arg(dbName).toUtf8());
+ return handleStdResult(result);
+}
+
+DbAndroidConnection::ExecutionResult DbAndroidJsonConnection::executeQuery(const QString& query)
+{
+ DbAndroidConnection::ExecutionResult executionResults;
+
+ QJsonDocument json = wrapQueryInJson(query);
+ QByteArray responseBytes = send(json.toJson(QJsonDocument::Compact));
+
+ QJsonParseError jsonError;
+ QJsonDocument jsonResponse = QJsonDocument::fromJson(responseBytes, &jsonError);
+ if (jsonError.error != QJsonParseError::NoError)
+ {
+ executionResults.wasError = true;
+ executionResults.errorMsg = tr("Error while parsing response from Android: %1").arg(jsonError.errorString());
+ return executionResults;
+ }
+
+ QJsonObject responseObject = jsonResponse.object();
+ if (responseObject.contains("generic_error"))
+ {
+ executionResults.wasError = true;
+ executionResults.errorMsg = tr("Generic error from Android: %1").arg(responseObject["generic_error"].toInt());
+ return executionResults;
+ }
+
+ if (responseObject.contains("error_code"))
+ {
+ executionResults.errorCode = responseObject["error_code"].toInt();
+ executionResults.errorMsg = responseObject["error_message"].toString();
+ return executionResults;
+ }
+
+ if (!responseObject.contains("columns"))
+ {
+ executionResults.wasError = true;
+ executionResults.errorMsg = tr("Missing 'columns' in response from Android.");
+ return executionResults;
+ }
+
+ if (!responseObject.contains("data"))
+ {
+ executionResults.wasError = true;
+ executionResults.errorMsg = tr("Missing 'columns' in response from Android.");
+ return executionResults;
+ }
+
+ for (const QVariant& col : responseObject["columns"].toArray().toVariantList())
+ executionResults.resultColumns << col.toString();
+
+ QJsonArray jsonRows = responseObject["data"].toArray();
+ QJsonObject jsonRow;
+ QJsonValue jsonValue;
+ QVariantHash rowAsMap;
+ QVariantList rowAsList;
+ QVariant cellValue;
+ for (int i = 0, total = jsonRows.size(); i < total; ++i)
+ {
+ jsonRow = jsonRows[i].toObject();
+ for (const QString& colName : executionResults.resultColumns)
+ {
+ if (!jsonRow.contains(colName))
+ {
+ executionResults.wasError = true;
+ executionResults.errorMsg = tr("Response from Android has missing data for column '%1' in row %2.").arg(colName, QString::number(i+1));
+ return executionResults;
+ }
+
+ jsonValue = jsonRow[colName];
+ cellValue = convertJsonValue(jsonValue);
+ rowAsMap[colName] = cellValue;
+ rowAsList << cellValue;
+ }
+
+ executionResults.resultDataMap << rowAsMap;
+ executionResults.resultDataList << rowAsList;
+
+ rowAsMap.clear();
+ rowAsList.clear();
+ }
+
+ return executionResults;
+}
+
+QJsonDocument DbAndroidJsonConnection::wrapQueryInJson(const QString& query)
+{
+ QJsonDocument doc;
+
+ QJsonObject rootObj;
+ rootObj["cmd"] = "QUERY";
+ rootObj["db"] = dbUrl.getDbName();
+ rootObj["query"] = query;
+
+ doc.setObject(rootObj);
+ return doc;
+}
+
+bool DbAndroidJsonConnection::handleStdResult(const QByteArray& results)
+{
+ QJsonParseError jsonError;
+ QJsonDocument jsonResponse = QJsonDocument::fromJson(results, &jsonError);
+ if (jsonError.error != QJsonParseError::NoError)
+ {
+ qCritical() << "Error while parsing response from Android:" << jsonError.errorString();
+ return false;
+ }
+
+ QJsonObject responseObject = jsonResponse.object();
+ if (responseObject.contains("generic_error"))
+ {
+ qCritical() << "Generic error from Android:" << responseObject["generic_error"].toInt();
+ return false;
+ }
+
+ if (!responseObject.contains("result"))
+ {
+ qCritical() << "Missing 'result' in response from Android.";
+ return false;
+ }
+
+ return (responseObject["result"].toString() == "ok");
+}
diff --git a/Plugins/DbAndroid/dbandroidjsonconnection.h b/Plugins/DbAndroid/dbandroidjsonconnection.h
new file mode 100644
index 0000000..ef0c943
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroidjsonconnection.h
@@ -0,0 +1,65 @@
+#ifndef DBANDROIDJSONCONNECTION_H
+#define DBANDROIDJSONCONNECTION_H
+
+#include "dbandroidmode.h"
+#include "common/global.h"
+#include "common/expiringcache.h"
+#include "dbandroidconnection.h"
+#include <QObject>
+
+class DbAndroid;
+class AdbManager;
+class BlockingSocket;
+
+class DbAndroidJsonConnection : public DbAndroidConnection
+{
+ Q_OBJECT
+
+ public:
+ DbAndroidJsonConnection(DbAndroid* plugin, QObject *parent = 0);
+ ~DbAndroidJsonConnection();
+
+ bool connectToAndroid(const DbAndroidUrl& url);
+ void disconnectFromAndroid();
+ bool isConnected() const;
+ QByteArray send(const QByteArray& data);
+ QString getDbName() const;
+ QStringList getDbList();
+ QStringList getAppList();
+ bool isAppOkay() const;
+ bool deleteDatabase(const QString& dbName);
+ ExecutionResult executeQuery(const QString& query);
+
+ private:
+ QJsonDocument wrapQueryInJson(const QString& query);
+ bool connectToNetwork();
+ bool connectToDevice();
+ bool connectToTcp(const QString& ip, int port);
+ void cleanUp();
+ QByteArray sendBytes(const QByteArray& data);
+ void handleSocketError();
+ void handleConnectionFailed();
+ QStringList handleDbListResult(const QByteArray& results);
+ bool handleStdResult(const QByteArray& results);
+
+ static QByteArray sizeToBytes(qint32 size);
+ static qint32 bytesToSize(const QByteArray& bytes);
+ static QVariant convertJsonValue(const QJsonValue& value);
+
+ DbAndroid* plugin = nullptr;
+ AdbManager* adbManager = nullptr;
+ BlockingSocket* socket = nullptr;
+ DbAndroidUrl dbUrl;
+ DbAndroidMode mode = DbAndroidMode::NETWORK;
+ bool connectedState = false;
+
+ static_char* PASS_RESPONSE_OK = "{\"result\":\"ok\"}";
+ static_char* PING_RESPONSE_OK = "{\"result\":\"pong\"}";
+ static_char* LIST_CMD = "{cmd:\"LIST\"}";
+ static_char* DELETE_DB_CMD = "{cmd:\"DELETE_DB\",db:\"%1\"}";
+
+ private slots:
+ void handlePossibleDisconnection();
+};
+
+#endif // DBANDROIDJSONCONNECTION_H
diff --git a/Plugins/DbAndroid/dbandroidmode.h b/Plugins/DbAndroid/dbandroidmode.h
new file mode 100644
index 0000000..523fdd8
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroidmode.h
@@ -0,0 +1,13 @@
+#ifndef DBANDROIDMODE
+#define DBANDROIDMODE
+
+enum class DbAndroidMode
+{
+ USB,
+ NETWORK,
+ SHELL,
+ null
+};
+
+#endif // DBANDROIDMODE
+
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();
+}
diff --git a/Plugins/DbAndroid/dbandroidpathdialog.h b/Plugins/DbAndroid/dbandroidpathdialog.h
new file mode 100644
index 0000000..4c4496f
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroidpathdialog.h
@@ -0,0 +1,80 @@
+#ifndef DBANDROIDPATHDIALOG_H
+#define DBANDROIDPATHDIALOG_H
+
+#include "dbandroidurl.h"
+#include <QDialog>
+
+namespace Ui {
+ class DbAndroidPathDialog;
+}
+
+class DbAndroid;
+class QTimer;
+class WidgetCover;
+class DbAndroidInstance;
+
+class DbAndroidPathDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ DbAndroidPathDialog(const DbAndroid* plugin, QWidget *parent = 0);
+ ~DbAndroidPathDialog();
+ void setUrl(const QString& url);
+ void setUrl(const DbAndroidUrl& url);
+ const DbAndroidUrl& getUrl() const;
+
+ private:
+ void init();
+ void updateUrl();
+ void loadUrl();
+ void asyncDbUpdate(const QString& connectionUrl, DbAndroidMode enforcedMode);
+ void asyncAppUpdate(const QString& connectionUrl, DbAndroidMode enforcedMode);
+ void refreshDevices();
+ DbAndroidMode getSelectedMode() const;
+ void setDbListUpdatesEnabled(bool enabled);
+
+ const DbAndroid* plugin = nullptr;
+ DbAndroidUrl dbUrl;
+ Ui::DbAndroidPathDialog *ui;
+ QTimer* dbListUpdateTimer = nullptr;
+ QTimer* appListUpdateTimer = nullptr;
+ WidgetCover* dbListCover = nullptr;
+ WidgetCover* appListCover = nullptr;
+ bool updatingDbList = false;
+ bool updatingAppList = false;
+ bool suspendDbListUpdates = false;
+ bool suspendAppListUpdates = false;
+ QStringList fullAppList;
+
+ private slots:
+ void scheduleDbListUpdate();
+ void scheduleAppListUpdate();
+ void updateState();
+ void refreshDbList();
+ void refreshAppList();
+ void updateDeviceList();
+ void updateValidations();
+ void handleUpdateDbList(const QStringList& dbList);
+ void handleUpdateAppList(const QStringList& apps);
+ void handleFinishedAsyncDbListUpdate(bool appOkay);
+ void handleFinishedAsyncAppListUpdate();
+ void handleDbCreationUpdate(bool canCreateDatabases);
+ void createNewDatabase();
+ void deleteSelectedDatabase();
+ void modeChanged(bool checked);
+ void applyAppFilter(const QString& value);
+
+ public slots:
+ void accept();
+
+ signals:
+ void callForValidations();
+ void callForDbCreationUpdate(bool canCreateDatabases);
+ void asyncDbListUpdatingFinished(bool appOkay);
+ void asyncAppListUpdatingFinished();
+ void callForDbListUpdate(const QStringList& newList);
+ void callForAppListUpdate(const QStringList& newList);
+};
+
+#endif // DBANDROIDPATHDIALOG_H
diff --git a/Plugins/DbAndroid/dbandroidpathdialog.ui b/Plugins/DbAndroid/dbandroidpathdialog.ui
new file mode 100644
index 0000000..3643a9c
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroidpathdialog.ui
@@ -0,0 +1,244 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DbAndroidPathDialog</class>
+ <widget class="QDialog" name="DbAndroidPathDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>402</width>
+ <height>546</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>400</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Android database URL</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Connection method</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QRadioButton" name="usbRadio">
+ <property name="text">
+ <string>USB cable - port forwarding</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="shellRadio">
+ <property name="text">
+ <string>USB cable - sqlite3 command</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="ipRadio">
+ <property name="text">
+ <string>Network (IP address)</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="deviceGroup">
+ <property name="title">
+ <string>Device</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QComboBox" name="deviceCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="ipGroup">
+ <property name="title">
+ <string>IP address</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QLineEdit" name="ipEdit">
+ <property name="maxLength">
+ <number>15</number>
+ </property>
+ <property name="placeholderText">
+ <string notr="true">???.???.???.???</string>
+ </property>
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="portGroup">
+ <property name="title">
+ <string>Port</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <item>
+ <widget class="QSpinBox" name="portSpin">
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ <property name="value">
+ <number>12121</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="passwordGroup">
+ <property name="title">
+ <string>Remote access password</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_8">
+ <item>
+ <widget class="QLineEdit" name="passwordEdit">
+ <property name="toolTip">
+ <string>&lt;p&gt;This is password configured in the SQLiteStudio service being embeded in the Android application.&lt;/p&gt;</string>
+ </property>
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="appGroup">
+ <property name="title">
+ <string>Application</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QComboBox" name="appCombo"/>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="appFilterEdit">
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="placeholderText">
+ <string>Filter</string>
+ </property>
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="databaseGroup">
+ <property name="title">
+ <string>Database</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QComboBox" name="databaseCombo"/>
+ </item>
+ <item>
+ <widget class="QToolButton" name="createDatabaseButton">
+ <property name="toolTip">
+ <string>Create a new database directly on the device.</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="deleteDatabaseButton">
+ <property name="toolTip">
+ <string>Delete currently selected database from the device. The currently selected database is the one picked in the list on the left of this button.</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>DbAndroidPathDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>DbAndroidPathDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/Plugins/DbAndroid/dbandroidshellconnection.cpp b/Plugins/DbAndroid/dbandroidshellconnection.cpp
new file mode 100644
index 0000000..1a76f8d
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroidshellconnection.cpp
@@ -0,0 +1,363 @@
+#include "dbandroidshellconnection.h"
+#include "adbmanager.h"
+#include "dbandroid.h"
+#include "services/notifymanager.h"
+#include "common/utils_sql.h"
+#include "csvserializer.h"
+#include <QMutexLocker>
+
+const CsvFormat DbAndroidShellConnection::CSV_FORMAT = CsvFormat(",", "\r\n", true, true);
+
+DbAndroidShellConnection::DbAndroidShellConnection(DbAndroid* plugin, QObject* parent) :
+ DbAndroidConnection(parent), plugin(plugin)
+{
+ this->adbManager = plugin->getAdbManager();
+ connect(adbManager, SIGNAL(deviceListChanged(QStringList)), this, SLOT(checkForDisconnection(QStringList)));
+}
+
+DbAndroidShellConnection::~DbAndroidShellConnection()
+{
+
+}
+
+bool DbAndroidShellConnection::connectToAndroid(const DbAndroidUrl& url)
+{
+ if (url.getMode() != DbAndroidMode::SHELL)
+ return false;
+
+ if (!adbManager->getDevices().contains(url.getDevice()))
+ {
+ notifyWarn(tr("Cannot connect to device %1, because it's not visible to your computer.").arg(url.getDevice()));
+ return false;
+ }
+
+ // Check if application is correct
+ if (url.getApplication().isEmpty())
+ {
+ qCritical() << "Tried to connect to an empty application in DbAndroidShellConnection::connectToAndroid()";
+ return false;
+ }
+
+ QString stdOut;
+ bool res = adbManager->exec(QStringList({"shell", "run-as", url.getApplication(), "echo", "1"}), &stdOut);
+ if (!res)
+ {
+ notifyWarn(tr("Cannot connect to device %1, because the application %2 doesn't seem to be installed on the device.").arg(url.getDevice(), url.getApplication()));
+ return false;
+ }
+
+ QMutexLocker lock(&appOkMutex);
+ appOkay = true;
+ if (stdOut.startsWith("run-as:"))
+ {
+ appOkay = false;
+ qWarning() << "Cannot connect to device" << url.getDevice() << "/" << url.getApplication() << "\nDetails:\n" << stdOut.trimmed();
+ notifyWarn(tr("Cannot connect to device %1, because the application %2 is not debuggable.")
+ .arg(url.getDevice(), url.getApplication()));
+ return false;
+ }
+
+ // Check if sqlite3 is available
+ res = adbManager->exec(QStringList({"shell", "run-as", url.getApplication(), "sqlite3", "--version"}));
+ if (!res)
+ {
+ notifyWarn(tr("Cannot connect to device %1, because '%2' command doesn't seem to be available on the device.").arg(url.getDevice(), "sqlite3"));
+ return false;
+ }
+
+ // Check if databases directory exists
+ res = adbManager->exec(QStringList({"shell", "run-as", url.getApplication(), "ls", "databases"}));
+ if (!res)
+ {
+ // Doesn't exist. Create if possible.
+ res = adbManager->exec(QStringList({"shell", "run-as", url.getApplication(), "mkdir", "databases"}));
+ if (!res)
+ {
+ notifyWarn(tr("Cannot connect to device %1, because '%2' database cannot be accessed on the device.").arg(url.getDevice(), "sqlite3"));
+ return false;
+ }
+ }
+
+ // Try to connect to target database.
+ connectionUrl = url;
+ connected = true;
+
+ ExecutionResult response = executeQuery("select sqlite_version()");
+ if (response.wasError)
+ {
+ disconnectFromAndroid();
+ notifyWarn(tr("Cannot connect to device %1, because '%2' database cannot be accessed on the device. Details: %3")
+ .arg(url.getDevice(), "sqlite3", response.errorMsg));
+ return false;
+ }
+
+ return true;
+}
+
+void DbAndroidShellConnection::disconnectFromAndroid()
+{
+ connectionUrl = DbAndroidUrl();
+ connected = false;
+}
+
+bool DbAndroidShellConnection::isConnected() const
+{
+ return connected;
+}
+
+QString DbAndroidShellConnection::getDbName() const
+{
+ return connectionUrl.getDbName();
+}
+
+QStringList DbAndroidShellConnection::getDbList()
+{
+ QMutexLocker lock(&appOkMutex);
+ appOkay = true;
+ QString out;
+ bool res = adbManager->exec(QStringList({"shell", "run-as", connectionUrl.getApplication(), "ls", "databases"}), &out);
+ if (!res)
+ return QStringList();
+
+ if (out.startsWith("run-as:")) // means error
+ {
+ appOkay = false;
+ notifyWarn(tr("Cannot get list of databases for application %1. Details: %2").arg(connectionUrl.getApplication(), out.trimmed()));
+ qWarning() << "DbAndroidShellConnection::getDbList():" << out;
+ return QStringList();
+ }
+
+ QStringList finalList;
+ for (const QString& dbName : out.trimmed().split("\n", QString::SkipEmptyParts))
+ {
+ if (dbName.trimmed().endsWith("-journal"))
+ continue;
+
+ finalList << dbName.trimmed();
+ }
+
+ return finalList;
+}
+
+QStringList DbAndroidShellConnection::getAppList()
+{
+ QString out;
+ bool res = adbManager->exec(QStringList({"shell", "pm list packages -3"}), &out);
+ if (!res)
+ return QStringList();
+
+ QStringList appList;
+ for (const QString& line : out.trimmed().split("\n", QString::SkipEmptyParts))
+ appList << line.mid(8).trimmed(); // skip "package:" prefix
+
+ return appList;
+}
+
+bool DbAndroidShellConnection::isAppOkay() const
+{
+ QMutexLocker lock(&appOkMutex);
+ return appOkay;
+}
+
+bool DbAndroidShellConnection::deleteDatabase(const QString& dbName)
+{
+ return adbManager->exec(QStringList({"shell", "run-as", connectionUrl.getApplication(), "rm", "-f", "databases/" + dbName, "databases/" + dbName + "-journal"}));
+}
+
+DbAndroidConnection::ExecutionResult DbAndroidShellConnection::executeQuery(const QString& query)
+{
+ const static QStringList stdArguments = QStringList({"shell", "run-as", "", "sqlite3", "-csv", "-separator", ",", "-batch", "-header"});
+
+ // Prepare usual arguments
+ QStringList args = stdArguments;
+ args.replace(2, connectionUrl.getApplication());
+ args << "databases/" + connectionUrl.getDbName();
+ args << AdbManager::encode(query);
+
+ // In case of SELECT we want to union typeof() for all columns first, then original query
+ bool isSelect = false;
+ getQueryAccessMode(query, Dialect::Sqlite3, &isSelect);
+ QStringList columnNames;
+ bool firstHalfForTypes = false;
+ if (isSelect)
+ {
+ columnNames = findColumns(args, query);
+ if (columnNames.size() > 0)
+ {
+ firstHalfForTypes = true;
+ args.removeLast();
+ args << appendTypeQueryPart(query, columnNames);
+ }
+ }
+
+ // Execute query and handle results
+ DbAndroidConnection::ExecutionResult results;
+ QByteArray out;
+ QByteArray err;
+ bool res = adbManager->execBytes(args, &out, &err);
+ if (!res)
+ {
+ results.wasError = true;
+ results.errorMsg = tr("Could not execute query on database '%1': %2").arg(connectionUrl.getDbName(), AdbManager::decode(err));
+ return results;
+ }
+
+ if (out.startsWith("run-as:")) // means error
+ {
+ results.wasError = true;
+ results.errorMsg = tr("Could not execute query on database '%1': %2").arg(connectionUrl.getDbName(), AdbManager::decode(out).trimmed());
+ return results;
+ }
+
+
+ QList<QList<QByteArray>> deserialized = CsvSerializer::deserialize(out, CSV_FORMAT);
+ if (deserialized.size() == 0)
+ return results; // no results
+
+ extractResultData(deserialized, firstHalfForTypes, results);
+ return results;
+}
+
+QStringList DbAndroidShellConnection::findColumns(const QStringList& originalArgs, const QString& query)
+{
+ static_qstring(colQueryTpl, "SELECT * FROM (%1) LIMIT 1");
+
+ QStringList tmpArgs = originalArgs;
+ QString tmpQuery = query.trimmed();
+ if (tmpQuery.endsWith(";"))
+ tmpQuery.chop(1);
+
+ tmpQuery = colQueryTpl.arg(tmpQuery);
+
+ tmpArgs.removeLast();
+ tmpArgs << tmpQuery;
+
+ QString out;
+ QString err;
+ bool res = adbManager->exec(tmpArgs, &out, &err);
+ if (!res)
+ {
+ qCritical() << "Error querying columns in DbAndroidShellConnection::findColumns(): " << out << "\n" << err;
+ return QStringList();
+ }
+
+ QList<QStringList> deserialized = CsvSerializer::deserialize(out, CSV_FORMAT);
+ if (deserialized.size() < 1)
+ {
+ // There will be no results.
+ return QStringList();
+ }
+
+ return deserialized.first();
+}
+
+QString DbAndroidShellConnection::appendTypeQueryPart(const QString& query, const QStringList& columnNames)
+{
+ static_qstring(typeTpl, "typeof(%1)");
+ static_qstring(hexTpl, "hex(%1) AS %1");
+ static_qstring(finalQueryTpl, "SELECT %3 FROM (%2) UNION ALL SELECT %1 FROM (%2)");
+
+ QString tmpQuery = query.trimmed();
+ if (tmpQuery.endsWith(";"))
+ tmpQuery.chop(1);
+
+ QStringList hexColumns;
+ QStringList typeColumns;
+ QString wrappedCol;
+ for (const QString& colName : columnNames)
+ {
+ wrappedCol = wrapObjIfNeeded(colName, Dialect::Sqlite3);
+ typeColumns << typeTpl.arg(wrappedCol);
+ hexColumns << hexTpl.arg(wrappedCol);
+ }
+
+ return finalQueryTpl.arg(typeColumns.join(", "), tmpQuery, hexColumns.join(", "));
+}
+
+void DbAndroidShellConnection::extractResultData(const QList<QList<QByteArray>>& deserialized, bool firstHalfForTypes, DbAndroidConnection::ExecutionResult& results)
+{
+ for (const QByteArray& cell : deserialized.first())
+ results.resultColumns << AdbManager::decode(cell);
+
+ QList<QList<QByteArray>> data = deserialized.mid(1); // first row are column names
+ QList<QList<QByteArray>> types;
+ if (firstHalfForTypes)
+ {
+ types = data.mid(data.size() / 2);
+ data = data.mid(0, data.size() / 2);
+
+ QVariantList rowDataList;
+ QVariantHash rowDataMap;
+ QList<QByteArray> rowData;
+ QList<QByteArray> rowTypes;
+ QVariant value;
+ for (int rowIdx = 0, totalRows = data.size(); rowIdx < totalRows; ++rowIdx)
+ {
+ rowData = data[rowIdx];
+ rowTypes = types[rowIdx];
+
+ rowDataList.clear();
+ rowDataMap.clear();
+ for (int i = 0, total = rowData.size(); i < total; ++i)
+ {
+ value = valueFromString(rowData[i], rowTypes[i]);
+ rowDataList << value;
+ rowDataMap[results.resultColumns[i]] = value;
+ }
+ results.resultDataList << rowDataList;
+ results.resultDataMap << rowDataMap;
+ }
+ }
+ else
+ {
+ QVariantList rowDataList;
+ QVariantHash rowDataMap;
+ for (const QList<QByteArray>& row : data)
+ {
+ rowDataList.clear();
+ rowDataMap.clear();
+ for (int i = 0, total = row.size(); i < total; ++i)
+ {
+ rowDataList << AdbManager::decode(row[i]);
+ rowDataMap[results.resultColumns[i]] = row[i];
+ }
+ results.resultDataList << rowDataList;
+ results.resultDataMap << rowDataMap;
+ }
+ }
+}
+
+QVariant DbAndroidShellConnection::valueFromString(const QByteArray& bytes, const QByteArray& type)
+{
+ static const QStringList types = QStringList({"null", "integer", "real", "text", "blob"});
+
+ DataType dataType = static_cast<DataType>(types.indexOf(AdbManager::decode(type)));
+ QByteArray decodedBytes = QByteArray::fromHex(bytes);
+ switch (dataType)
+ {
+ case DataType::BLOB:
+ return decodedBytes;
+ case DataType::INTEGER:
+ return QString::fromLatin1(decodedBytes).toLongLong();
+ case DataType::REAL:
+ return QString::fromLatin1(decodedBytes).toDouble();
+ case DataType::TEXT:
+ return QString::fromUtf8(decodedBytes);
+ case DataType::_NULL:
+ break;
+ case DataType::UNKNOWN:
+ qCritical() << "Unknown type passed to DbAndroidShellConnection::valueFromString():" << type;
+ break;
+ }
+ return QVariant(QString::null);
+}
+
+void DbAndroidShellConnection::checkForDisconnection(const QStringList& devices)
+{
+ if (connected && !devices.contains(connectionUrl.getDevice()))
+ {
+ disconnectFromAndroid();
+ emit disconnected();
+ }
+}
+
diff --git a/Plugins/DbAndroid/dbandroidshellconnection.h b/Plugins/DbAndroid/dbandroidshellconnection.h
new file mode 100644
index 0000000..1c0ae0f
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroidshellconnection.h
@@ -0,0 +1,59 @@
+#ifndef DBANDROIDSHELLCONNECTION_H
+#define DBANDROIDSHELLCONNECTION_H
+
+#include "dbandroidconnection.h"
+#include "csvformat.h"
+
+#include <QMutex>
+
+class DbAndroid;
+class AdbManager;
+
+class DbAndroidShellConnection : public DbAndroidConnection
+{
+ Q_OBJECT
+
+ public:
+ DbAndroidShellConnection(DbAndroid* plugin, QObject *parent = 0);
+ ~DbAndroidShellConnection();
+
+ bool connectToAndroid(const DbAndroidUrl& url);
+ void disconnectFromAndroid();
+ bool isConnected() const;
+ QString getDbName() const;
+ QStringList getDbList();
+ QStringList getAppList();
+ bool isAppOkay() const;
+ bool deleteDatabase(const QString& dbName);
+ ExecutionResult executeQuery(const QString& query);
+
+ private:
+ enum class DataType
+ {
+ UNKNOWN = -1,
+ _NULL = 0,
+ INTEGER = 1,
+ REAL = 2,
+ TEXT = 3,
+ BLOB = 4
+ };
+
+ QStringList findColumns(const QStringList& originalArgs, const QString& query);
+ QString appendTypeQueryPart(const QString& query, const QStringList& columnNames);
+ void extractResultData(const QList<QList<QByteArray> >& deserialized, bool firstHalfForTypes, ExecutionResult& results);
+ QVariant valueFromString(const QByteArray& bytes, const QByteArray& type);
+
+ DbAndroid* plugin = nullptr;
+ AdbManager* adbManager = nullptr;
+ bool connected = false;
+ DbAndroidUrl connectionUrl;
+ bool appOkay = false;
+ mutable QMutex appOkMutex;
+
+ static const CsvFormat CSV_FORMAT;
+
+ private slots:
+ void checkForDisconnection(const QStringList& devices);
+};
+
+#endif // DBANDROIDSHELLCONNECTION_H
diff --git a/Plugins/DbAndroid/dbandroidurl.cpp b/Plugins/DbAndroid/dbandroidurl.cpp
new file mode 100644
index 0000000..766d810
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroidurl.cpp
@@ -0,0 +1,217 @@
+#include "dbandroidurl.h"
+#include "common/ipvalidator.h"
+#include <QStringList>
+
+DbAndroidUrl::DbAndroidUrl()
+{
+}
+
+DbAndroidUrl::DbAndroidUrl(DbAndroidMode enforcedMode) :
+ enforcedMode(enforcedMode)
+{
+}
+
+DbAndroidUrl::DbAndroidUrl(const DbAndroidUrl& other) :
+ enforcedMode(other.enforcedMode), host(other.host), device(other.device), port(other.port), dbName(other.dbName), password(other.password),
+ application(other.application)
+{
+}
+
+DbAndroidUrl::DbAndroidUrl(const QString& path, bool obfuscatedPassword)
+{
+ parse(path, obfuscatedPassword);
+}
+
+DbAndroidUrl::~DbAndroidUrl()
+{
+}
+
+QString DbAndroidUrl::toUrlString(bool obfuscatedPassword) const
+{
+ return toUrl(obfuscatedPassword).toString();
+}
+
+QUrl DbAndroidUrl::toUrl(bool obfuscatedPassword) const
+{
+ QUrl url;
+ url.setScheme(SCHEME);
+ url.setHost(host);
+ url.setUserName(device);
+ url.setPort(port);
+ url.setPassword(getPassword(obfuscatedPassword));
+ url.setPath("/" + (application.isEmpty() ? "!" : application) + "/" + dbName);
+ return url;
+}
+
+QString DbAndroidUrl::getDisplayName() const
+{
+ if (!device.isNull())
+ return device;
+
+ return host;
+}
+
+void DbAndroidUrl::parse(const QString& path, bool obfuscatedPassword)
+{
+ QUrl url(path);
+ if (url.scheme() != SCHEME)
+ return;
+
+ host = url.host();
+ device = url.userName();
+ port = url.port();
+
+ QString urlPath = url.path();
+ if (urlPath.startsWith("/"))
+ urlPath = urlPath.mid(1);
+
+ QStringList pathParts = urlPath.split("/");
+
+ application = QString();
+ if (pathParts.first() != "!")
+ application = pathParts.first();
+
+ dbName = QStringList(pathParts.mid(1)).join("/");
+ if (!url.password().isEmpty())
+ setPassword(url.password(), obfuscatedPassword);
+ else
+ setPassword(QString());
+}
+
+QString DbAndroidUrl::getApplication() const
+{
+ return application;
+}
+
+void DbAndroidUrl::setApplication(const QString& value)
+{
+ application = value;
+}
+
+QString DbAndroidUrl::getDevice() const
+{
+ return device;
+}
+
+void DbAndroidUrl::setDevice(const QString& value)
+{
+ device = value;
+}
+
+QString DbAndroidUrl::getHost() const
+{
+ return host;
+}
+
+void DbAndroidUrl::setHost(const QString& value)
+{
+ host = value;
+}
+
+
+QString DbAndroidUrl::getPassword(bool obfuscated) const
+{
+ if (obfuscated)
+ return QString::fromLatin1(password.toUtf8().toHex().toBase64());
+
+ return password;
+}
+
+void DbAndroidUrl::setPassword(const QString& value, bool obfuscated)
+{
+ if (obfuscated)
+ {
+ password = QString::fromUtf8(QByteArray::fromHex(QByteArray::fromBase64(value.toLatin1())));
+ return;
+ }
+
+ password = value;
+}
+
+
+QString DbAndroidUrl::getDbName() const
+{
+ return dbName;
+}
+
+void DbAndroidUrl::setDbName(const QString& value)
+{
+ dbName = value;
+}
+
+DbAndroidMode DbAndroidUrl::getMode() const
+{
+ if (enforcedMode != DbAndroidMode::null)
+ return enforcedMode;
+
+ if (!application.isEmpty())
+ return DbAndroidMode::SHELL;
+
+ return host.isEmpty() ? DbAndroidMode::USB : DbAndroidMode::NETWORK;
+}
+
+void DbAndroidUrl::setEnforcedMode(DbAndroidMode mode)
+{
+ enforcedMode = mode;
+}
+
+bool DbAndroidUrl::isValid(bool validateConnectionIrrelevantParts) const
+{
+ if (isNull())
+ return false;
+
+ if (validateConnectionIrrelevantParts && dbName.isEmpty())
+ return false;
+
+ switch (getMode())
+ {
+ case DbAndroidMode::NETWORK:
+ {
+ if (!isHostValid())
+ return false;
+
+ if (port <= 0)
+ return false;
+
+ break;
+ }
+ case DbAndroidMode::USB:
+ {
+ if (port <= 0)
+ return false;
+
+ break;
+ }
+ case DbAndroidMode::SHELL:
+ {
+ if (validateConnectionIrrelevantParts && application.isEmpty())
+ return false;
+
+ break;
+ }
+ case DbAndroidMode::null:
+ return false;
+ }
+
+ return true;
+}
+
+bool DbAndroidUrl::isHostValid() const
+{
+ return IpValidator::check(host);
+}
+
+bool DbAndroidUrl::isNull() const
+{
+ return host.isEmpty() && device.isEmpty();
+}
+
+int DbAndroidUrl::getPort() const
+{
+ return port;
+}
+
+void DbAndroidUrl::setPort(int value)
+{
+ port = value;
+}
diff --git a/Plugins/DbAndroid/dbandroidurl.h b/Plugins/DbAndroid/dbandroidurl.h
new file mode 100644
index 0000000..594b21c
--- /dev/null
+++ b/Plugins/DbAndroid/dbandroidurl.h
@@ -0,0 +1,59 @@
+#ifndef DBANDROIDURL_H
+#define DBANDROIDURL_H
+
+#include "dbandroidmode.h"
+#include <QString>
+#include <QUrl>
+
+class DbAndroidUrl
+{
+ public:
+ DbAndroidUrl();
+ DbAndroidUrl(const DbAndroidUrl& other);
+ explicit DbAndroidUrl(DbAndroidMode enforcedMode);
+ explicit DbAndroidUrl(const QString& path, bool obfuscatedPassword = true);
+ ~DbAndroidUrl();
+
+ QString toUrlString(bool obfuscatedPassword = true) const;
+ QUrl toUrl(bool obfuscatedPassword = true) const;
+ QString getDisplayName() const;
+
+ int getPort() const;
+ void setPort(int value);
+
+ QString getDbName() const;
+ void setDbName(const QString& value);
+
+ DbAndroidMode getMode() const;
+ void setEnforcedMode(DbAndroidMode mode);
+ bool isValid(bool validateConnectionIrrelevantParts = true) const;
+ bool isHostValid() const;
+ bool isNull() const;
+
+ QString getPassword(bool obfuscated = false) const;
+ void setPassword(const QString& value, bool obfuscated = false);
+
+ QString getHost() const;
+ void setHost(const QString& value);
+
+ QString getDevice() const;
+ void setDevice(const QString& value);
+
+ QString getApplication() const;
+ void setApplication(const QString& value);
+
+ private:
+ void parse(const QString& path, bool obfuscatedPassword = false);
+
+ static const constexpr char* SCHEME = "android";
+
+ DbAndroidMode enforcedMode = DbAndroidMode::null;
+ QString host;
+ QString device;
+ int port = -1;
+ QString dbName;
+ QString password;
+ QString application;
+};
+
+#endif // DBANDROIDURL_H
diff --git a/Plugins/DbAndroid/sqlqueryandroid.cpp b/Plugins/DbAndroid/sqlqueryandroid.cpp
new file mode 100644
index 0000000..08fe5cc
--- /dev/null
+++ b/Plugins/DbAndroid/sqlqueryandroid.cpp
@@ -0,0 +1,160 @@
+#include "dbandroidconnection.h"
+#include "sqlqueryandroid.h"
+#include "sqlresultrowandroid.h"
+#include "parser/lexer.h"
+#include "db/sqlerrorcodes.h"
+#include "common/utils_sql.h"
+#include "log.h"
+#include "dbandroidinstance.h"
+#include <QDebug>
+
+SqlQueryAndroid::SqlQueryAndroid(DbAndroidInstance* db, DbAndroidConnection* connection, const QString& query) :
+ db(db), connection(connection), queryString(query)
+{
+ tokenizedQuery = Lexer::tokenize(query, Dialect::Sqlite3);
+}
+
+SqlQueryAndroid::~SqlQueryAndroid()
+{
+}
+
+QString SqlQueryAndroid::getErrorText()
+{
+ return errorText;
+}
+
+int SqlQueryAndroid::getErrorCode()
+{
+ return errorCode;
+}
+
+QStringList SqlQueryAndroid::getColumnNames()
+{
+ return resultColumns;
+}
+
+int SqlQueryAndroid::columnCount()
+{
+ return resultColumns.size();
+}
+
+void SqlQueryAndroid::rewind()
+{
+ currentRow = -1;
+}
+
+SqlResultsRowPtr SqlQueryAndroid::nextInternal()
+{
+ if (resultDataList.size() == 0)
+ return SqlResultsRowPtr();
+
+ currentRow++;
+ SqlResultRowAndroid* resultRow = new SqlResultRowAndroid(resultDataMap[currentRow], resultDataList[currentRow]);
+ return SqlResultsRowPtr(resultRow);
+}
+
+bool SqlQueryAndroid::hasNextInternal()
+{
+ return (currentRow + 1 < resultDataList.size());
+}
+
+bool SqlQueryAndroid::execInternal(const QList<QVariant>& args)
+{
+ resetResponse();
+ logSql(db, queryString, args, flags);
+
+ int argIdx = 0;
+ QString query;
+ for (const TokenPtr& token : tokenizedQuery)
+ {
+ if (token->type != Token::BIND_PARAM)
+ {
+ query += token->value;
+ continue;
+ }
+
+ query += convertArg(args[argIdx++]);
+ }
+
+ return executeAndHandleResponse(query);
+}
+
+bool SqlQueryAndroid::execInternal(const QHash<QString, QVariant>& args)
+{
+ resetResponse();
+ logSql(db, queryString, args, flags);
+
+ QString argName;
+ QString query;
+ for (const TokenPtr& token : tokenizedQuery)
+ {
+ if (token->type != Token::BIND_PARAM)
+ {
+ query += token->value;
+ continue;
+ }
+
+ argName = token->value;
+ if (!args.contains(argName))
+ {
+ errorCode = SqlErrorCode::OTHER_EXECUTION_ERROR;
+ errorText = QObject::tr("Cannot bind argument '%1' of the query, because it's value is missing.").arg(argName);
+ return false;
+ }
+
+ query += convertArg(args[argName]);
+ }
+
+ return executeAndHandleResponse(query);
+}
+
+QString SqlQueryAndroid::convertArg(const QVariant& value)
+{
+ if (value.isNull() || !value.isValid())
+ return "NULL";
+
+ switch (value.type())
+ {
+ case QVariant::Int:
+ case QVariant::UInt:
+ case QVariant::LongLong:
+ case QVariant::ULongLong:
+ case QVariant::Double:
+ return value.toString();
+ case QVariant::String:
+ return "'" + value.toString().replace("'", "''") + "'";
+ case QVariant::ByteArray:
+ return "x'" + value.toByteArray().toHex() + "'";
+ default:
+ break;
+ }
+
+ qCritical() << "Unhandled argument type in SqlQueryAndroid::convertArg():" << value.type();
+ return "";
+}
+
+bool SqlQueryAndroid::executeAndHandleResponse(const QString& query)
+{
+ DbAndroidConnection::ExecutionResult results = connection->executeQuery(query);
+ if (results.wasError)
+ {
+ errorCode = (results.errorCode != 0) ? results.errorCode : SqlErrorCode::OTHER_EXECUTION_ERROR;
+ errorText = results.errorMsg;
+ return false;
+ }
+
+ resultColumns = results.resultColumns;
+ resultDataMap = results.resultDataMap;
+ resultDataList = results.resultDataList;
+ return true;
+}
+
+void SqlQueryAndroid::resetResponse()
+{
+ resultColumns.clear();
+ resultDataMap.clear();
+ resultDataList.clear();
+ currentRow = -1;
+ errorCode = 0;
+ errorText = QString();
+}
diff --git a/Plugins/DbAndroid/sqlqueryandroid.h b/Plugins/DbAndroid/sqlqueryandroid.h
new file mode 100644
index 0000000..3944c21
--- /dev/null
+++ b/Plugins/DbAndroid/sqlqueryandroid.h
@@ -0,0 +1,47 @@
+#ifndef SQLQUERYANDROID_H
+#define SQLQUERYANDROID_H
+
+#include "db/sqlquery.h"
+#include "parser/token.h"
+#include <QJsonDocument>
+
+class DbAndroidConnection;
+class DbAndroidInstance;
+
+class SqlQueryAndroid : public SqlQuery
+{
+ public:
+ SqlQueryAndroid(DbAndroidInstance* db, DbAndroidConnection* connection, const QString& query);
+ ~SqlQueryAndroid();
+
+ QString getErrorText();
+ int getErrorCode();
+ QStringList getColumnNames();
+ int columnCount();
+ void rewind();
+
+ protected:
+ SqlResultsRowPtr nextInternal();
+ bool hasNextInternal();
+ bool execInternal(const QList<QVariant>& args);
+ bool execInternal(const QHash<QString, QVariant>& args);
+
+ private:
+ bool executeAndHandleResponse(const QString& query);
+ void resetResponse();
+
+ static QString convertArg(const QVariant& value);
+
+ DbAndroidInstance* db = nullptr;
+ DbAndroidConnection* connection = nullptr;
+ QString queryString;
+ TokenList tokenizedQuery;
+ int errorCode = 0;
+ QString errorText;
+ QStringList resultColumns;
+ QList<QVariantHash> resultDataMap;
+ QList<QVariantList> resultDataList;
+ int currentRow = -1;
+};
+
+#endif // SQLQUERYANDROID_H
diff --git a/Plugins/DbAndroid/sqlresultrowandroid.cpp b/Plugins/DbAndroid/sqlresultrowandroid.cpp
new file mode 100644
index 0000000..cc08fa5
--- /dev/null
+++ b/Plugins/DbAndroid/sqlresultrowandroid.cpp
@@ -0,0 +1,12 @@
+#include "sqlresultrowandroid.h"
+
+SqlResultRowAndroid::SqlResultRowAndroid(const QVariantHash& resultMap, const QVariantList& resultList)
+{
+ valuesMap = resultMap;
+ values = resultList;
+}
+
+SqlResultRowAndroid::~SqlResultRowAndroid()
+{
+}
+
diff --git a/Plugins/DbAndroid/sqlresultrowandroid.h b/Plugins/DbAndroid/sqlresultrowandroid.h
new file mode 100644
index 0000000..5acc95a
--- /dev/null
+++ b/Plugins/DbAndroid/sqlresultrowandroid.h
@@ -0,0 +1,13 @@
+#ifndef SQLRESULTROWANDROID_H
+#define SQLRESULTROWANDROID_H
+
+#include "db/sqlresultsrow.h"
+
+class SqlResultRowAndroid : public SqlResultsRow
+{
+ public:
+ SqlResultRowAndroid(const QVariantHash& resultMap, const QVariantList& resultList);
+ ~SqlResultRowAndroid();
+};
+
+#endif // SQLRESULTROWANDROID_H
diff --git a/Plugins/DbSqlite2/DbSqlite2.pro b/Plugins/DbSqlite2/DbSqlite2.pro
index de31058..b254ca5 100644
--- a/Plugins/DbSqlite2/DbSqlite2.pro
+++ b/Plugins/DbSqlite2/DbSqlite2.pro
@@ -35,3 +35,4 @@ OTHER_FILES += \
+
diff --git a/Plugins/HtmlExport/HtmlExport.pro b/Plugins/HtmlExport/HtmlExport.pro
index 8f3578e..3ef30d0 100644
--- a/Plugins/HtmlExport/HtmlExport.pro
+++ b/Plugins/HtmlExport/HtmlExport.pro
@@ -29,7 +29,8 @@ FORMS += \
htmlexport.ui
-TRANSLATIONS += HtmlExport_zh_CN.ts \
+TRANSLATIONS += HtmlExport_it.ts \
+ HtmlExport_zh_CN.ts \
HtmlExport_sk.ts \
HtmlExport_de.ts \
HtmlExport_ru.ts \
@@ -49,3 +50,4 @@ TRANSLATIONS += HtmlExport_zh_CN.ts \
+
diff --git a/Plugins/HtmlExport/HtmlExport_it.ts b/Plugins/HtmlExport/HtmlExport_it.ts
new file mode 100644
index 0000000..208900c
--- /dev/null
+++ b/Plugins/HtmlExport/HtmlExport_it.ts
@@ -0,0 +1,173 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="it_IT">
+<context>
+ <name>HtmlExport</name>
+ <message>
+ <location filename="htmlexport.cpp" line="46"/>
+ <source>SQL query results</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="78"/>
+ <location filename="htmlexport.cpp" line="155"/>
+ <source>no type</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="115"/>
+ <location filename="htmlexport.cpp" line="243"/>
+ <source>Exported table: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="127"/>
+ <location filename="htmlexport.cpp" line="255"/>
+ <source>Table: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="255"/>
+ <source>virtual</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="308"/>
+ <source>Exported database: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="324"/>
+ <source>Index: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="330"/>
+ <source>For table:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="337"/>
+ <source>Unique:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="338"/>
+ <source>Yes</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="338"/>
+ <source>No</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="344"/>
+ <source>Column</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="345"/>
+ <source>Collating</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="346"/>
+ <source>Sort order</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="377"/>
+ <source>Trigger: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="383"/>
+ <source>Activated:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="391"/>
+ <source>Action:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="398"/>
+ <source>On view:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="400"/>
+ <source>On table:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="411"/>
+ <source>Activate condition:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="418"/>
+ <source>Code executed:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="452"/>
+ <source>View: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.cpp" line="477"/>
+ <source>Document generated by SQLiteStudio v%1 on %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>HtmlExportConfig</name>
+ <message>
+ <location filename="htmlexport.ui" line="36"/>
+ <source>Maximum number of characters per cell:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.ui" line="43"/>
+ <source>Include data types in first row</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.ui" line="53"/>
+ <source>Column names as first row</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.ui" line="63"/>
+ <source>Row numbers as first column</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.ui" line="73"/>
+ <source>Output format</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.ui" line="79"/>
+ <source>Format document (new lines, indentation)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.ui" line="92"/>
+ <source>Compress (everything in one line)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.ui" line="108"/>
+ <source>&lt;p&gt;When enabled, HTML characters such as &amp;lt;, &amp;gt; and &amp;amp; are not escaped in exported values. This allows you for example to export hyper-link enabled documents, but it also may result in incorrect HTML document (unmatched pairs of &amp;lt; and &amp;gt; characters). Be warned.&lt;/p&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="htmlexport.ui" line="111"/>
+ <source>Don&apos;t escape HTML characters</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/Plugins/JsonExport/JsonExport.pro b/Plugins/JsonExport/JsonExport.pro
index 6f8ae18..47fcb05 100644
--- a/Plugins/JsonExport/JsonExport.pro
+++ b/Plugins/JsonExport/JsonExport.pro
@@ -28,7 +28,8 @@ RESOURCES += \
jsonexport.qrc
-TRANSLATIONS += JsonExport_zh_CN.ts \
+TRANSLATIONS += JsonExport_it.ts \
+ JsonExport_zh_CN.ts \
JsonExport_sk.ts \
JsonExport_de.ts \
JsonExport_ru.ts \
@@ -48,3 +49,4 @@ TRANSLATIONS += JsonExport_zh_CN.ts \
+
diff --git a/Plugins/JsonExport/JsonExport_it.ts b/Plugins/JsonExport/JsonExport_it.ts
new file mode 100644
index 0000000..74b0169
--- /dev/null
+++ b/Plugins/JsonExport/JsonExport_it.ts
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="it_IT">
+<context>
+ <name>JsonExportConfig</name>
+ <message>
+ <location filename="jsonexport.ui" line="20"/>
+ <source>Output format</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="jsonexport.ui" line="26"/>
+ <source>Format document (new lines, indentation)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="jsonexport.ui" line="42"/>
+ <source>Compress (everything in one line)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/Plugins/JsonExport/JsonExport_zh_CN.ts b/Plugins/JsonExport/JsonExport_zh_CN.ts
index be101c4..82b3b1d 100644
--- a/Plugins/JsonExport/JsonExport_zh_CN.ts
+++ b/Plugins/JsonExport/JsonExport_zh_CN.ts
@@ -6,17 +6,17 @@
<message>
<location filename="jsonexport.ui" line="20"/>
<source>Output format</source>
- <translation type="unfinished"></translation>
+ <translation>输出格式</translation>
</message>
<message>
<location filename="jsonexport.ui" line="26"/>
<source>Format document (new lines, indentation)</source>
- <translation type="unfinished"></translation>
+ <translation>格式化的文本(多行,缩进)</translation>
</message>
<message>
<location filename="jsonexport.ui" line="42"/>
<source>Compress (everything in one line)</source>
- <translation type="unfinished"></translation>
+ <translation>压缩(产生单行文件)</translation>
</message>
</context>
</TS>
diff --git a/Plugins/PdfExport/PdfExport.pro b/Plugins/PdfExport/PdfExport.pro
index ff86d24..95848c1 100644
--- a/Plugins/PdfExport/PdfExport.pro
+++ b/Plugins/PdfExport/PdfExport.pro
@@ -26,7 +26,8 @@ RESOURCES += \
pdfexport.qrc
-TRANSLATIONS += PdfExport_zh_CN.ts \
+TRANSLATIONS += PdfExport_it.ts \
+ PdfExport_zh_CN.ts \
PdfExport_sk.ts \
PdfExport_de.ts \
PdfExport_ru.ts \
@@ -46,3 +47,4 @@ TRANSLATIONS += PdfExport_zh_CN.ts \
+
diff --git a/Plugins/PdfExport/PdfExport_de.ts b/Plugins/PdfExport/PdfExport_de.ts
index 7e5d8e6..7498c43 100644
--- a/Plugins/PdfExport/PdfExport_de.ts
+++ b/Plugins/PdfExport/PdfExport_de.ts
@@ -6,164 +6,165 @@
<message>
<location filename="pdfexport.cpp" line="28"/>
<source>SQLiteStudio v%1</source>
- <translation type="unfinished"></translation>
+ <translation>SQLiteStudio v%1</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="60"/>
<source>SQL query results</source>
- <translation type="unfinished"></translation>
+ <translation>SQL Abfragenergebnis</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="89"/>
<location filename="pdfexport.cpp" line="143"/>
<source>Exported table: %1</source>
- <translation type="unfinished"></translation>
+ <translation>Exportierte Tabelle: %1</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="92"/>
<location filename="pdfexport.cpp" line="158"/>
<source>Table: %1</source>
- <translation type="unfinished"></translation>
+ <translation>Tabelle: %1</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="94"/>
<location filename="pdfexport.cpp" line="230"/>
<source>Column</source>
- <translation type="unfinished"></translation>
+ <translation>Spalte</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="94"/>
<source>Data type</source>
- <translation type="unfinished"></translation>
+ <translation>Datentyp</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="94"/>
<source>Constraints</source>
- <translation type="unfinished"></translation>
+ <translation>Bedingungen</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="125"/>
<source>Global table constraints</source>
- <translation type="unfinished"></translation>
+ <translation>Globale Tabellenbedingungen</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="214"/>
<source>Exported database: %1</source>
- <translation type="unfinished"></translation>
+ <translation>Exportierte Datenbank: %1</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="222"/>
<source>Index: %1</source>
- <translation type="unfinished"></translation>
+ <translation>Index: %1</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="224"/>
<source>Property</source>
<comment>index header</comment>
- <translation type="unfinished"></translation>
+ <translation>Eigenschaft</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="224"/>
<source>Value</source>
<comment>index header</comment>
- <translation type="unfinished"></translation>
+ <translation>Wert</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="227"/>
<source>Indexed table</source>
- <translation type="unfinished"></translation>
+ <translation>Indexierte Tabelle</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="228"/>
<source>Unique index</source>
- <translation type="unfinished"></translation>
+ <translatorcomment>Singularer Index? Fuer Unique finde ich keine richtige Übersetzung.</translatorcomment>
+ <translation>Index (duplikatfrei)</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="228"/>
<source>Yes</source>
- <translation type="unfinished"></translation>
+ <translation>Ja</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="228"/>
<source>No</source>
- <translation type="unfinished"></translation>
+ <translation>Nein</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="230"/>
<source>Collation</source>
- <translation type="unfinished"></translation>
+ <translation>Kollation</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="230"/>
<source>Sort order</source>
- <translation type="unfinished"></translation>
+ <translation>Sortierreihenfolge</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="246"/>
<source>Partial index condition</source>
- <translation type="unfinished"></translation>
+ <translation>Teilindexbedingung</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="260"/>
<source>Trigger: %1</source>
- <translation type="unfinished"></translation>
+ <translation>Trigger: %1</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="262"/>
<source>Property</source>
<comment>trigger header</comment>
- <translation type="unfinished"></translation>
+ <translation>Eigenschaft</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="262"/>
<source>Value</source>
<comment>trigger header</comment>
- <translation type="unfinished"></translation>
+ <translation>Wert</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="264"/>
<source>Activation time</source>
- <translation type="unfinished"></translation>
+ <translation>Ausführungszeit</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="267"/>
<source>For action</source>
- <translation type="unfinished"></translation>
+ <translation>Für Aktion</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="271"/>
<source>On view</source>
- <translation type="unfinished"></translation>
+ <translation>auf Index</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="273"/>
<source>On table</source>
- <translation type="unfinished"></translation>
+ <translation>auf Tabelle</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="278"/>
<source>Activation condition</source>
- <translation type="unfinished"></translation>
+ <translation>Ausführungskondition</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="284"/>
<source>Code executed</source>
- <translation type="unfinished"></translation>
+ <translation>Code ausgeführt</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="296"/>
<source>View: %1</source>
- <translation type="unfinished"></translation>
+ <translation>View: %1</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="297"/>
<source>Query:</source>
- <translation type="unfinished"></translation>
+ <translation>Abfrage:</translation>
</message>
<message>
<location filename="pdfexport.cpp" line="649"/>
<source>Document generated with SQLiteStudio v%1</source>
- <translation type="unfinished"></translation>
+ <translation>Dokument mit SQLiteStudio v%1 generiert</translation>
</message>
</context>
<context>
@@ -171,32 +172,33 @@
<message>
<location filename="pdfexport.ui" line="20"/>
<source>Size and layout</source>
- <translation type="unfinished"></translation>
+ <translation>Format und Layout</translation>
</message>
<message>
<location filename="pdfexport.ui" line="36"/>
<source>Page size:</source>
- <translation type="unfinished"></translation>
+ <translation>Format:
+</translation>
</message>
<message>
<location filename="pdfexport.ui" line="63"/>
<source>Right margin:</source>
- <translation type="unfinished"></translation>
+ <translation>Seitenabstand Rechts:</translation>
</message>
<message>
<location filename="pdfexport.ui" line="70"/>
<source>Left margin:</source>
- <translation type="unfinished"></translation>
+ <translation>Seitenabstand Links:</translation>
</message>
<message>
<location filename="pdfexport.ui" line="77"/>
<source>Cell padding:</source>
- <translation type="unfinished"></translation>
+ <translation>Zellenabstand:</translation>
</message>
<message>
<location filename="pdfexport.ui" line="84"/>
<source>Limit characters in single cell:</source>
- <translation type="unfinished"></translation>
+ <translation>Maximale Anzahl an Zeichen pro Zelle:</translation>
</message>
<message>
<location filename="pdfexport.ui" line="91"/>
@@ -205,52 +207,52 @@
<location filename="pdfexport.ui" line="132"/>
<location filename="pdfexport.ui" line="156"/>
<source>mm</source>
- <translation type="unfinished"></translation>
+ <translation>mm</translation>
</message>
<message>
<location filename="pdfexport.ui" line="139"/>
<source>Bottom margin:</source>
- <translation type="unfinished"></translation>
+ <translation>Seitenabstand Unten:</translation>
</message>
<message>
<location filename="pdfexport.ui" line="163"/>
<source>Top margin:</source>
- <translation type="unfinished"></translation>
+ <translation>Seitenabstand oben:</translation>
</message>
<message>
<location filename="pdfexport.ui" line="183"/>
<source>Font</source>
- <translation type="unfinished"></translation>
+ <translation>Schriftart</translation>
</message>
<message>
<location filename="pdfexport.ui" line="206"/>
<source>Colors</source>
- <translation type="unfinished"></translation>
+ <translation>Farben</translation>
</message>
<message>
<location filename="pdfexport.ui" line="212"/>
<source>Headers background:</source>
- <translation type="unfinished"></translation>
+ <translation>Hintergrundfarbe Kopfzeile:</translation>
</message>
<message>
<location filename="pdfexport.ui" line="235"/>
<source>NULL value color:</source>
- <translation type="unfinished"></translation>
+ <translation>NULL Wert Farbe:</translation>
</message>
<message>
<location filename="pdfexport.ui" line="261"/>
<source>Other settings</source>
- <translation type="unfinished"></translation>
+ <translation>Sonstige Einstellungen</translation>
</message>
<message>
<location filename="pdfexport.ui" line="267"/>
<source>Print row numbers for data</source>
- <translation type="unfinished"></translation>
+ <translation>Zeilennummerierung drucken</translation>
</message>
<message>
<location filename="pdfexport.ui" line="277"/>
<source>Print page numbers</source>
- <translation type="unfinished"></translation>
+ <translation>Seitenzahl drucken</translation>
</message>
</context>
</TS>
diff --git a/Plugins/PdfExport/PdfExport_it.ts b/Plugins/PdfExport/PdfExport_it.ts
new file mode 100644
index 0000000..913432b
--- /dev/null
+++ b/Plugins/PdfExport/PdfExport_it.ts
@@ -0,0 +1,256 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="it_IT">
+<context>
+ <name>PdfExport</name>
+ <message>
+ <location filename="pdfexport.cpp" line="28"/>
+ <source>SQLiteStudio v%1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="60"/>
+ <source>SQL query results</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="89"/>
+ <location filename="pdfexport.cpp" line="143"/>
+ <source>Exported table: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="92"/>
+ <location filename="pdfexport.cpp" line="158"/>
+ <source>Table: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="94"/>
+ <location filename="pdfexport.cpp" line="230"/>
+ <source>Column</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="94"/>
+ <source>Data type</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="94"/>
+ <source>Constraints</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="125"/>
+ <source>Global table constraints</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="214"/>
+ <source>Exported database: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="222"/>
+ <source>Index: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="224"/>
+ <source>Property</source>
+ <comment>index header</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="224"/>
+ <source>Value</source>
+ <comment>index header</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="227"/>
+ <source>Indexed table</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="228"/>
+ <source>Unique index</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="228"/>
+ <source>Yes</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="228"/>
+ <source>No</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="230"/>
+ <source>Collation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="230"/>
+ <source>Sort order</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="246"/>
+ <source>Partial index condition</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="260"/>
+ <source>Trigger: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="262"/>
+ <source>Property</source>
+ <comment>trigger header</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="262"/>
+ <source>Value</source>
+ <comment>trigger header</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="264"/>
+ <source>Activation time</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="267"/>
+ <source>For action</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="271"/>
+ <source>On view</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="273"/>
+ <source>On table</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="278"/>
+ <source>Activation condition</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="284"/>
+ <source>Code executed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="296"/>
+ <source>View: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="297"/>
+ <source>Query:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.cpp" line="649"/>
+ <source>Document generated with SQLiteStudio v%1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>PdfExportConfig</name>
+ <message>
+ <location filename="pdfexport.ui" line="20"/>
+ <source>Size and layout</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.ui" line="36"/>
+ <source>Page size:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.ui" line="63"/>
+ <source>Right margin:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.ui" line="70"/>
+ <source>Left margin:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.ui" line="77"/>
+ <source>Cell padding:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.ui" line="84"/>
+ <source>Limit characters in single cell:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.ui" line="91"/>
+ <location filename="pdfexport.ui" line="108"/>
+ <location filename="pdfexport.ui" line="115"/>
+ <location filename="pdfexport.ui" line="132"/>
+ <location filename="pdfexport.ui" line="156"/>
+ <source>mm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.ui" line="139"/>
+ <source>Bottom margin:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.ui" line="163"/>
+ <source>Top margin:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.ui" line="183"/>
+ <source>Font</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.ui" line="206"/>
+ <source>Colors</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.ui" line="212"/>
+ <source>Headers background:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.ui" line="235"/>
+ <source>NULL value color:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.ui" line="261"/>
+ <source>Other settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.ui" line="267"/>
+ <source>Print row numbers for data</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="pdfexport.ui" line="277"/>
+ <source>Print page numbers</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/Plugins/Plugins.pro b/Plugins/Plugins.pro
index 805521a..81b2337 100644
--- a/Plugins/Plugins.pro
+++ b/Plugins/Plugins.pro
@@ -16,4 +16,5 @@ SUBDIRS += \
Printing \
SqlEnterpriseFormatter \
ConfigMigration \
- ScriptingTcl
+ ScriptingTcl \
+ DbAndroid
diff --git a/Plugins/Printing/Printing.pro b/Plugins/Printing/Printing.pro
index c1afb9f..5006d60 100644
--- a/Plugins/Printing/Printing.pro
+++ b/Plugins/Printing/Printing.pro
@@ -35,7 +35,8 @@ RESOURCES += \
printing.qrc
-TRANSLATIONS += Printing_zh_CN.ts \
+TRANSLATIONS += Printing_it.ts \
+ Printing_zh_CN.ts \
Printing_sk.ts \
Printing_de.ts \
Printing_ru.ts \
@@ -55,3 +56,4 @@ TRANSLATIONS += Printing_zh_CN.ts \
+
diff --git a/Plugins/Printing/Printing_de.ts b/Plugins/Printing/Printing_de.ts
index 6a165a2..fb45beb 100644
--- a/Plugins/Printing/Printing_de.ts
+++ b/Plugins/Printing/Printing_de.ts
@@ -6,27 +6,27 @@
<message>
<location filename="printing.cpp" line="35"/>
<source>Print data</source>
- <translation type="unfinished"></translation>
+ <translation>Daten drucken</translation>
</message>
<message>
<location filename="printing.cpp" line="37"/>
<source>Print query</source>
- <translation type="unfinished"></translation>
+ <translation>Abfrage drucken</translation>
</message>
<message>
<location filename="printing.cpp" line="75"/>
<source>No data to print.</source>
- <translation type="unfinished"></translation>
+ <translation>Keine Daten, die gedruckt werden müssen.</translation>
</message>
<message>
<location filename="printing.cpp" line="84"/>
<source>Printing data.</source>
- <translation type="unfinished"></translation>
+ <translation>Drucke Daten.</translation>
</message>
<message>
<location filename="printing.cpp" line="110"/>
<source>Printing query.</source>
- <translation type="unfinished"></translation>
+ <translation>Drucke Abfrage.</translation>
</message>
</context>
<context>
@@ -34,7 +34,7 @@
<message>
<location filename="printingexport.cpp" line="34"/>
<source>Printing</source>
- <translation type="unfinished"></translation>
+ <translation>Drucken</translation>
</message>
</context>
</TS>
diff --git a/Plugins/Printing/Printing_it.ts b/Plugins/Printing/Printing_it.ts
new file mode 100644
index 0000000..0a21af2
--- /dev/null
+++ b/Plugins/Printing/Printing_it.ts
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="it_IT">
+<context>
+ <name>Printing</name>
+ <message>
+ <location filename="printing.cpp" line="35"/>
+ <source>Print data</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="printing.cpp" line="37"/>
+ <source>Print query</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="printing.cpp" line="75"/>
+ <source>No data to print.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="printing.cpp" line="84"/>
+ <source>Printing data.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="printing.cpp" line="110"/>
+ <source>Printing query.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>PrintingExport</name>
+ <message>
+ <location filename="printingexport.cpp" line="34"/>
+ <source>Printing</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/Plugins/Printing/Printing_zh_CN.ts b/Plugins/Printing/Printing_zh_CN.ts
index 0b1b255..e3a7a2d 100644
--- a/Plugins/Printing/Printing_zh_CN.ts
+++ b/Plugins/Printing/Printing_zh_CN.ts
@@ -6,27 +6,27 @@
<message>
<location filename="printing.cpp" line="35"/>
<source>Print data</source>
- <translation type="unfinished"></translation>
+ <translation>打印数据</translation>
</message>
<message>
<location filename="printing.cpp" line="37"/>
<source>Print query</source>
- <translation type="unfinished"></translation>
+ <translation>打印 query</translation>
</message>
<message>
<location filename="printing.cpp" line="75"/>
<source>No data to print.</source>
- <translation type="unfinished"></translation>
+ <translation>没有数据可打印。</translation>
</message>
<message>
<location filename="printing.cpp" line="84"/>
<source>Printing data.</source>
- <translation type="unfinished"></translation>
+ <translation>打印数据中。</translation>
</message>
<message>
<location filename="printing.cpp" line="110"/>
<source>Printing query.</source>
- <translation type="unfinished"></translation>
+ <translation>打印query中。</translation>
</message>
</context>
<context>
@@ -34,7 +34,7 @@
<message>
<location filename="printingexport.cpp" line="34"/>
<source>Printing</source>
- <translation type="unfinished"></translation>
+ <translation>打印</translation>
</message>
</context>
</TS>
diff --git a/Plugins/RegExpImport/RegExpImport.pro b/Plugins/RegExpImport/RegExpImport.pro
index aa790b2..8ef5298 100644
--- a/Plugins/RegExpImport/RegExpImport.pro
+++ b/Plugins/RegExpImport/RegExpImport.pro
@@ -28,7 +28,8 @@ RESOURCES += \
regexpimport.qrc
-TRANSLATIONS += RegExpImport_zh_CN.ts \
+TRANSLATIONS += RegExpImport_it.ts \
+ RegExpImport_zh_CN.ts \
RegExpImport_sk.ts \
RegExpImport_de.ts \
RegExpImport_ru.ts \
@@ -48,3 +49,4 @@ TRANSLATIONS += RegExpImport_zh_CN.ts \
+
diff --git a/Plugins/RegExpImport/RegExpImport_it.ts b/Plugins/RegExpImport/RegExpImport_it.ts
new file mode 100644
index 0000000..00869f1
--- /dev/null
+++ b/Plugins/RegExpImport/RegExpImport_it.ts
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="it_IT">
+<context>
+ <name>RegExpImport</name>
+ <message>
+ <location filename="regexpimport.cpp" line="37"/>
+ <source>Text files (*.txt);;All files (*)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="regexpimport.cpp" line="53"/>
+ <source>Cannot read file %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="regexpimport.cpp" line="161"/>
+ <source>Enter the regular expression pattern.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="regexpimport.cpp" line="169"/>
+ <source>Invalid pattern: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="regexpimport.cpp" line="189"/>
+ <source>Requested capture index %1 is out of range.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="regexpimport.cpp" line="196"/>
+ <source>&lt;p&gt;Requested capture group name &apos;%1&apos;, but it&apos;s not defined in the pattern: &lt;pre&gt;%2&lt;/pre&gt;&lt;/p&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>RegExpImportConfig</name>
+ <message>
+ <location filename="regexpimport.ui" line="20"/>
+ <source>Capture groups</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="regexpimport.ui" line="26"/>
+ <source>Treat all RegExp capture groups as columns</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="regexpimport.ui" line="39"/>
+ <source>Import only following groups:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="regexpimport.ui" line="52"/>
+ <source>&lt;p&gt;Enter comma separated list of capture group indexes. The 0 index refers to the entire matched string.&lt;/p&gt;
+&lt;p&gt;If you used named groups in the pattern, you can use names instead of indexes. You can mix indexes and names in this list.&lt;/p&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="regexpimport.ui" line="56"/>
+ <source>Example: 1, 3, 4</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="regexpimport.ui" line="69"/>
+ <source>Pattern:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="regexpimport.ui" line="76"/>
+ <source>&lt;p&gt;Use Regular Expression groups to enclose parts of the expression that you want to import. If you want to use a group, that you don&apos;t want to import, then use &quot;import only following groups&quot; option below.
+
+You can use named groups and refer to them in group list below. To name a group use: &lt;pre&gt;(?&amp;lt;myGroupName&amp;gt;\s+\d+\s+)&lt;/pre&gt;&lt;/p&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="regexpimport.ui" line="81"/>
+ <source>Example: (\d+)\s+((\d+)\w+)\s+(\w+)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/Plugins/ScriptingTcl/ScriptingTcl.pro b/Plugins/ScriptingTcl/ScriptingTcl.pro
index fc5cf5b..d888a32 100644
--- a/Plugins/ScriptingTcl/ScriptingTcl.pro
+++ b/Plugins/ScriptingTcl/ScriptingTcl.pro
@@ -38,6 +38,10 @@ linux: {
TCL_CONFIG = $$TCL_CONFIG_DIR/tclConfig.sh
message("Looking for $$TCL_CONFIG")
!exists($$TCL_CONFIG) {
+ TCL_CONFIG = $$TCL_CONFIG_DIR/../tclConfig.sh
+ message("Looking for $$TCL_CONFIG")
+ }
+ !exists($$TCL_CONFIG) {
# Debian case
DEBIAN_ARCH_PATH=$$system(dpkg-architecture -qDEB_HOST_MULTIARCH)
TCL_CONFIG = /usr/lib/$$DEBIAN_ARCH_PATH/tcl$$TCL_VERSION/tclConfig.sh
@@ -138,7 +142,8 @@ RESOURCES += \
scriptingtcl.qrc
-TRANSLATIONS += ScriptingTcl_zh_CN.ts \
+TRANSLATIONS += ScriptingTcl_it.ts \
+ ScriptingTcl_zh_CN.ts \
ScriptingTcl_sk.ts \
ScriptingTcl_de.ts \
ScriptingTcl_ru.ts \
@@ -158,3 +163,4 @@ TRANSLATIONS += ScriptingTcl_zh_CN.ts \
+
diff --git a/Plugins/ScriptingTcl/ScriptingTcl_it.ts b/Plugins/ScriptingTcl/ScriptingTcl_it.ts
new file mode 100644
index 0000000..dc735d6
--- /dev/null
+++ b/Plugins/ScriptingTcl/ScriptingTcl_it.ts
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="it_IT">
+<context>
+ <name>ScriptingTcl</name>
+ <message>
+ <location filename="scriptingtcl.cpp" line="411"/>
+ <source>No database available in current context, while called Tcl&apos;s &apos;%1&apos; command.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="scriptingtcl.cpp" line="433"/>
+ <source>Invalid &apos;%1&apos; command sytax. Should be: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="scriptingtcl.cpp" line="554"/>
+ <source>Error from Tcl&apos;s&apos; &apos;%1&apos; command: %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter.pro b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter.pro
index 965767b..5af5bfa 100644
--- a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter.pro
+++ b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter.pro
@@ -100,7 +100,8 @@ RESOURCES += \
sqlenterpriseformatter.qrc
-TRANSLATIONS += SqlEnterpriseFormatter_zh_CN.ts \
+TRANSLATIONS += SqlEnterpriseFormatter_it.ts \
+ SqlEnterpriseFormatter_zh_CN.ts \
SqlEnterpriseFormatter_sk.ts \
SqlEnterpriseFormatter_de.ts \
SqlEnterpriseFormatter_ru.ts \
@@ -120,3 +121,4 @@ TRANSLATIONS += SqlEnterpriseFormatter_zh_CN.ts \
+
diff --git a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_de.ts b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_de.ts
index a83f5df..37751d0 100644
--- a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_de.ts
+++ b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_de.ts
@@ -4,8 +4,8 @@
<context>
<name>QObject</name>
<message>
- <location filename="sqlenterpriseformatter.cpp" line="82"/>
- <location filename="sqlenterpriseformatter.cpp" line="89"/>
+ <location filename="sqlenterpriseformatter.cpp" line="91"/>
+ <location filename="sqlenterpriseformatter.cpp" line="98"/>
<source>name</source>
<comment>example name wrapper</comment>
<translation type="unfinished"></translation>
@@ -200,7 +200,32 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="sqlenterpriseformatter.ui" line="638"/>
+ <location filename="sqlenterpriseformatter.ui" line="631"/>
+ <source>Comments</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="637"/>
+ <source>Preferred comment marker (where possible):</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="647"/>
+ <source>SqlEnterpriseFormatter.CommentMarkers</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="654"/>
+ <source>Move all comments to the line end</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="664"/>
+ <source>Line up comments at the line end</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="695"/>
<source>Preview</source>
<translation type="unfinished"></translation>
</message>
diff --git a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_es.ts b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_es.ts
index 4d873e5..b0b5eed 100644
--- a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_es.ts
+++ b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_es.ts
@@ -4,8 +4,8 @@
<context>
<name>QObject</name>
<message>
- <location filename="sqlenterpriseformatter.cpp" line="82"/>
- <location filename="sqlenterpriseformatter.cpp" line="89"/>
+ <location filename="sqlenterpriseformatter.cpp" line="91"/>
+ <location filename="sqlenterpriseformatter.cpp" line="98"/>
<source>name</source>
<comment>example name wrapper</comment>
<translation type="unfinished"></translation>
@@ -200,7 +200,32 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="sqlenterpriseformatter.ui" line="638"/>
+ <location filename="sqlenterpriseformatter.ui" line="631"/>
+ <source>Comments</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="637"/>
+ <source>Preferred comment marker (where possible):</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="647"/>
+ <source>SqlEnterpriseFormatter.CommentMarkers</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="654"/>
+ <source>Move all comments to the line end</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="664"/>
+ <source>Line up comments at the line end</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="695"/>
<source>Preview</source>
<translation type="unfinished"></translation>
</message>
diff --git a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_fr.ts b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_fr.ts
index 8a9178b..d0e62f7 100644
--- a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_fr.ts
+++ b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_fr.ts
@@ -4,8 +4,8 @@
<context>
<name>QObject</name>
<message>
- <location filename="sqlenterpriseformatter.cpp" line="82"/>
- <location filename="sqlenterpriseformatter.cpp" line="89"/>
+ <location filename="sqlenterpriseformatter.cpp" line="91"/>
+ <location filename="sqlenterpriseformatter.cpp" line="98"/>
<source>name</source>
<comment>example name wrapper</comment>
<translation>Nom</translation>
@@ -200,7 +200,32 @@
<translation>Mots clé en majuscule</translation>
</message>
<message>
- <location filename="sqlenterpriseformatter.ui" line="638"/>
+ <location filename="sqlenterpriseformatter.ui" line="631"/>
+ <source>Comments</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="637"/>
+ <source>Preferred comment marker (where possible):</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="647"/>
+ <source>SqlEnterpriseFormatter.CommentMarkers</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="654"/>
+ <source>Move all comments to the line end</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="664"/>
+ <source>Line up comments at the line end</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="695"/>
<source>Preview</source>
<translation>Aperçu</translation>
</message>
diff --git a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_it.ts b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_it.ts
new file mode 100644
index 0000000..9e721a4
--- /dev/null
+++ b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_it.ts
@@ -0,0 +1,233 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="it_IT">
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="sqlenterpriseformatter.cpp" line="91"/>
+ <location filename="sqlenterpriseformatter.cpp" line="98"/>
+ <source>name</source>
+ <comment>example name wrapper</comment>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>SqlEnterpriseFormatter</name>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="46"/>
+ <source>Indentation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="58"/>
+ <source>Line up keywords in multi-line queries</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="68"/>
+ <source>Indent contents of parenthesis block</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="91"/>
+ <source>Tab size:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="106"/>
+ <source>New lines</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="160"/>
+ <source>Before opening parenthesis in column definitions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="170"/>
+ <source>After opening parenthesis in column definitions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="180"/>
+ <source>Before closing parenthesis in column definitions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="190"/>
+ <source>After closing parenthesis in column definitions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="200"/>
+ <source>Before opening parenthesis in expressions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="210"/>
+ <source>After opening parenthesis in expressions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="220"/>
+ <source>Before closing parenthesis in expressions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="230"/>
+ <source>After closing parenthesis in expressions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="240"/>
+ <source>After JOIN keywords in FROM clause</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="250"/>
+ <source>Put each column constraint in CREATE TABLE into new line</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="260"/>
+ <source>After comma</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="270"/>
+ <source>After comma in expressions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="280"/>
+ <source>After semicolon</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="290"/>
+ <location filename="sqlenterpriseformatter.ui" line="512"/>
+ <source>Never before semicolon</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="338"/>
+ <source>White spaces</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="392"/>
+ <source>Before comma in lists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="402"/>
+ <source>After comma in lists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="412"/>
+ <source>Before opening parenthesis</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="422"/>
+ <source>After opening parenthesis</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="432"/>
+ <source>Before closing parenthesis</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="442"/>
+ <source>After closing parenthesis</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="452"/>
+ <source>No space between SQL function name and opening parenthesis</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="462"/>
+ <source>Before dot operator (in path to database object)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="472"/>
+ <source>After dot operator (in path to database object)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="482"/>
+ <source>Before mathematical operator</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="492"/>
+ <source>After mathematical operator</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="502"/>
+ <source>Never before comma</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="558"/>
+ <source>Names</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="583"/>
+ <source>Preferred name wrapper</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="600"/>
+ <source>Always use name wrapping</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="610"/>
+ <source>Uppercase data type names</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="620"/>
+ <source>Uppercase keywords</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="631"/>
+ <source>Comments</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="637"/>
+ <source>Preferred comment marker (where possible):</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="647"/>
+ <source>SqlEnterpriseFormatter.CommentMarkers</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="654"/>
+ <source>Move all comments to the line end</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="664"/>
+ <source>Line up comments at the line end</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="695"/>
+ <source>Preview</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_pl.ts b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_pl.ts
index 5cfbf32..2c03a46 100644
--- a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_pl.ts
+++ b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_pl.ts
@@ -4,8 +4,8 @@
<context>
<name>QObject</name>
<message>
- <location filename="sqlenterpriseformatter.cpp" line="82"/>
- <location filename="sqlenterpriseformatter.cpp" line="89"/>
+ <location filename="sqlenterpriseformatter.cpp" line="91"/>
+ <location filename="sqlenterpriseformatter.cpp" line="98"/>
<source>name</source>
<comment>example name wrapper</comment>
<translation>nazwa</translation>
@@ -201,7 +201,32 @@
<translation>Zmieniaj litery słów kluczowych na duże</translation>
</message>
<message>
- <location filename="sqlenterpriseformatter.ui" line="638"/>
+ <location filename="sqlenterpriseformatter.ui" line="631"/>
+ <source>Comments</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="637"/>
+ <source>Preferred comment marker (where possible):</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="647"/>
+ <source>SqlEnterpriseFormatter.CommentMarkers</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="654"/>
+ <source>Move all comments to the line end</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="664"/>
+ <source>Line up comments at the line end</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="695"/>
<source>Preview</source>
<translation>Podgląd</translation>
</message>
diff --git a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_pt_BR.ts b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_pt_BR.ts
index b89804a..a408ffc 100644
--- a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_pt_BR.ts
+++ b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_pt_BR.ts
@@ -4,8 +4,8 @@
<context>
<name>QObject</name>
<message>
- <location filename="sqlenterpriseformatter.cpp" line="82"/>
- <location filename="sqlenterpriseformatter.cpp" line="89"/>
+ <location filename="sqlenterpriseformatter.cpp" line="91"/>
+ <location filename="sqlenterpriseformatter.cpp" line="98"/>
<source>name</source>
<comment>example name wrapper</comment>
<translation type="unfinished"></translation>
@@ -200,7 +200,32 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="sqlenterpriseformatter.ui" line="638"/>
+ <location filename="sqlenterpriseformatter.ui" line="631"/>
+ <source>Comments</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="637"/>
+ <source>Preferred comment marker (where possible):</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="647"/>
+ <source>SqlEnterpriseFormatter.CommentMarkers</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="654"/>
+ <source>Move all comments to the line end</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="664"/>
+ <source>Line up comments at the line end</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="695"/>
<source>Preview</source>
<translation type="unfinished"></translation>
</message>
diff --git a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_ru.ts b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_ru.ts
index a5656d8..4e5e98d 100644
--- a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_ru.ts
+++ b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_ru.ts
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1" language="ru_RU">
+<TS version="2.0" language="ru_RU">
<context>
<name>QObject</name>
<message>
- <location filename="sqlenterpriseformatter.cpp" line="82"/>
- <location filename="sqlenterpriseformatter.cpp" line="89"/>
+ <location filename="sqlenterpriseformatter.cpp" line="91"/>
+ <location filename="sqlenterpriseformatter.cpp" line="98"/>
<source>name</source>
<comment>example name wrapper</comment>
<translation>имя</translation>
@@ -200,7 +200,32 @@
<translation>Приводить ключевые слова к верхнему регистру</translation>
</message>
<message>
- <location filename="sqlenterpriseformatter.ui" line="638"/>
+ <location filename="sqlenterpriseformatter.ui" line="631"/>
+ <source>Comments</source>
+ <translation>Комментарии</translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="637"/>
+ <source>Preferred comment marker (where possible):</source>
+ <translation>Предпочитаемый символ комментирования (где применимо):</translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="647"/>
+ <source>SqlEnterpriseFormatter.CommentMarkers</source>
+ <translation>SqlEnterpriseFormatter.CommentMarkers</translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="654"/>
+ <source>Move all comments to the line end</source>
+ <translation>Перемещать все комментарии в конец строки</translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="664"/>
+ <source>Line up comments at the line end</source>
+ <translation>Выравнивать комментарии в конце строки</translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="695"/>
<source>Preview</source>
<translation>Предпросмотр</translation>
</message>
diff --git a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_sk.ts b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_sk.ts
index 407cc6e..37a42a4 100644
--- a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_sk.ts
+++ b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_sk.ts
@@ -4,8 +4,8 @@
<context>
<name>QObject</name>
<message>
- <location filename="sqlenterpriseformatter.cpp" line="82"/>
- <location filename="sqlenterpriseformatter.cpp" line="89"/>
+ <location filename="sqlenterpriseformatter.cpp" line="91"/>
+ <location filename="sqlenterpriseformatter.cpp" line="98"/>
<source>name</source>
<comment>example name wrapper</comment>
<translation type="unfinished"></translation>
@@ -200,7 +200,32 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="sqlenterpriseformatter.ui" line="638"/>
+ <location filename="sqlenterpriseformatter.ui" line="631"/>
+ <source>Comments</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="637"/>
+ <source>Preferred comment marker (where possible):</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="647"/>
+ <source>SqlEnterpriseFormatter.CommentMarkers</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="654"/>
+ <source>Move all comments to the line end</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="664"/>
+ <source>Line up comments at the line end</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="695"/>
<source>Preview</source>
<translation type="unfinished"></translation>
</message>
diff --git a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_zh_CN.ts b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_zh_CN.ts
index 3c80603..d1a361b 100644
--- a/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_zh_CN.ts
+++ b/Plugins/SqlEnterpriseFormatter/SqlEnterpriseFormatter_zh_CN.ts
@@ -4,8 +4,8 @@
<context>
<name>QObject</name>
<message>
- <location filename="sqlenterpriseformatter.cpp" line="82"/>
- <location filename="sqlenterpriseformatter.cpp" line="89"/>
+ <location filename="sqlenterpriseformatter.cpp" line="91"/>
+ <location filename="sqlenterpriseformatter.cpp" line="98"/>
<source>name</source>
<comment>example name wrapper</comment>
<translation type="unfinished"></translation>
@@ -200,7 +200,32 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="sqlenterpriseformatter.ui" line="638"/>
+ <location filename="sqlenterpriseformatter.ui" line="631"/>
+ <source>Comments</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="637"/>
+ <source>Preferred comment marker (where possible):</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="647"/>
+ <source>SqlEnterpriseFormatter.CommentMarkers</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="654"/>
+ <source>Move all comments to the line end</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="664"/>
+ <source>Line up comments at the line end</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlenterpriseformatter.ui" line="695"/>
<source>Preview</source>
<translation type="unfinished"></translation>
</message>
diff --git a/Plugins/SqlEnterpriseFormatter/formatempty.cpp b/Plugins/SqlEnterpriseFormatter/formatempty.cpp
index 976694e..d0696e0 100644
--- a/Plugins/SqlEnterpriseFormatter/formatempty.cpp
+++ b/Plugins/SqlEnterpriseFormatter/formatempty.cpp
@@ -1,8 +1,9 @@
#include "formatempty.h"
+#include "common/unused.h"
-FormatEmpty::FormatEmpty(SqliteEmptyQuery* eq) :
- eq(eq)
+FormatEmpty::FormatEmpty(SqliteEmptyQuery* eq)
{
+ UNUSED(eq);
}
void FormatEmpty::formatInternal()
diff --git a/Plugins/SqlEnterpriseFormatter/formatempty.h b/Plugins/SqlEnterpriseFormatter/formatempty.h
index 3279925..2fa3d01 100644
--- a/Plugins/SqlEnterpriseFormatter/formatempty.h
+++ b/Plugins/SqlEnterpriseFormatter/formatempty.h
@@ -12,9 +12,6 @@ class FormatEmpty : public FormatStatement
protected:
void formatInternal();
-
- private:
- SqliteEmptyQuery* eq = nullptr;
};
#endif // FORMATEMPTY_H
diff --git a/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.cpp b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.cpp
index 8f75960..a08f95c 100644
--- a/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.cpp
+++ b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.cpp
@@ -3,6 +3,7 @@
#include "common/unused.h"
#include "common/global.h"
#include <QDebug>
+#include <parser/lexer.h>
#include <parser/parser.h>
SqlEnterpriseFormatter::SqlEnterpriseFormatter()
@@ -11,6 +12,8 @@ SqlEnterpriseFormatter::SqlEnterpriseFormatter()
QString SqlEnterpriseFormatter::format(SqliteQueryPtr query)
{
+ QList<Comment*> comments = collectComments(query->tokens);
+
int wrapperIdx = cfg.SqlEnterpriseFormatter.Wrappers.get().indexOf(cfg.SqlEnterpriseFormatter.PrefferedWrapper.get());
NameWrapper wrapper = getAllNameWrappers()[wrapperIdx];
@@ -24,7 +27,11 @@ QString SqlEnterpriseFormatter::format(SqliteQueryPtr query)
QString formatted = formatStmt->format();
delete formatStmt;
- return formatted;
+ QString formattedWithComments = applyComments(formatted, comments, query->dialect);
+ for (Comment* c : comments)
+ delete c;
+
+ return formattedWithComments;
}
bool SqlEnterpriseFormatter::init()
@@ -34,7 +41,9 @@ bool SqlEnterpriseFormatter::init()
static_qstring(query1, "SELECT (2 + 4) AND (3 + 5), 4 NOT IN (SELECT t1.'some[_]name' + t2.[some'name2] FROM xyz t1 JOIN zxc t2 ON (t1.aaa = t2.aaa)) "
"FROM a, (SELECT id FROM table2);");
static_qstring(query2, "INSERT INTO table1 (id, value1, value2) VALUES (1, (2 + 5), (SELECT id FROM table2));");
- static_qstring(query3, "CREATE TABLE tab (id INTEGER PRIMARY KEY, value1 VARCHAR(6), value2 NUMBER(8,2) NOT NULL DEFAULT 1.0);");
+ static_qstring(query3, "CREATE TABLE tab (id INTEGER PRIMARY KEY, /*a primary key column*/ value1 VARCHAR(6), "
+ "value2 /*column with constraints*/ NUMBER(8,2) NOT NULL DEFAULT 1.0"
+ ");");
static_qstring(query4, "CREATE UNIQUE INDEX IF NOT EXISTS dbName.idx1 ON [messages column] (id COLLATE x ASC, lang DESC, description);");
Parser parser(Dialect::Sqlite3);
@@ -110,3 +119,196 @@ void SqlEnterpriseFormatter::configDialogClosed()
{
disconnect(&cfg.SqlEnterpriseFormatter, SIGNAL(changed(CfgEntry*)), this, SLOT(configModified(CfgEntry*)));
}
+
+QList<SqlEnterpriseFormatter::Comment *> SqlEnterpriseFormatter::collectComments(const TokenList &tokens)
+{
+ QList<Comment*> results;
+
+ QList<TokenList> tokensInLines = tokensByLines(tokens);
+ Comment* prevCommentInThisLine = nullptr;
+ Comment* cmt = nullptr;
+ bool tokensBefore = false;
+ int pos = 0;
+ int line = 0;
+ for (const TokenList& tokensInLine : tokensInLines)
+ {
+ tokensBefore = true;
+ prevCommentInThisLine = nullptr;
+ for (const TokenPtr& token : tokensInLine)
+ {
+ if (token->type == Token::Type::SPACE)
+ continue;
+
+ if (prevCommentInThisLine)
+ prevCommentInThisLine->tokensAfter = true;
+
+ if (token->type == Token::Type::COMMENT)
+ {
+ cmt = new Comment;
+ cmt->tokensBefore = tokensBefore;
+ cmt->position = pos;
+ cmt->multiline = token->value.startsWith("/*");
+ if (cmt->multiline)
+ cmt->contents = token->value.mid(2, token->value.length() - 4).trimmed();
+ else
+ cmt->contents = token->value.mid(2).trimmed();
+
+ results << cmt;
+ prevCommentInThisLine = cmt;
+ continue;
+ }
+
+ tokensBefore = true;
+ pos++;
+ }
+ line++;
+ }
+
+ return results;
+}
+
+QList<TokenList> SqlEnterpriseFormatter::tokensByLines(const TokenList &tokens, bool includeSpaces)
+{
+ QList<TokenList> tokensInLines;
+ TokenList tokensInLine;
+ for (const TokenPtr& token : tokens)
+ {
+ if (includeSpaces || token->type != Token::Type::SPACE)
+ tokensInLine << token;
+
+ if (token->type == Token::Type::SPACE && token->value.contains('\n'))
+ {
+ tokensInLines << tokensInLine;
+ tokensInLine.clear();
+ }
+ }
+ if (tokensInLine.size() > 0)
+ tokensInLines << tokensInLine;
+
+ return tokensInLines;
+}
+
+TokenList SqlEnterpriseFormatter::adjustCommentsToEnd(const TokenList &inputTokens)
+{
+ QList<TokenList> tokensInLines = tokensByLines(inputTokens, true);
+ TokenList newTokens;
+ TokenList commentTokensForLine;
+ TokenPtr newLineToken;
+ for (const TokenList& tokensInLine : tokensInLines)
+ {
+ commentTokensForLine.clear();
+ newLineToken.clear();
+ for (const TokenPtr& token : tokensInLine)
+ {
+ if (token->type == Token::Type::COMMENT)
+ {
+ wrapComment(token, true);
+ //token->value = " " + endLineCommentTpl.arg(token->value);
+ commentTokensForLine << token;
+ }
+ else if (token->type == Token::Type::SPACE && token->value.contains("\n"))
+ newLineToken = token;
+ else
+ newTokens << token;
+ }
+
+ newTokens += commentTokensForLine;
+ if (newLineToken)
+ newTokens << newLineToken;
+ }
+ return newTokens;
+}
+
+TokenList SqlEnterpriseFormatter::wrapOnlyComments(const TokenList &inputTokens)
+{
+ QList<TokenList> tokensInLines = tokensByLines(inputTokens, true);
+ TokenList newTokens;
+ bool lineEnd = true;
+ for (const TokenList& tokensInLine : reverse(tokensInLines))
+ {
+ lineEnd = true;
+ for (const TokenPtr& token : reverse(tokensInLine))
+ {
+ if (!token->isWhitespace())
+ lineEnd = false;
+
+ if (token->type == Token::Type::COMMENT)
+ wrapComment(token, lineEnd);
+
+ newTokens << token;
+ }
+ }
+ return reverse(newTokens);
+}
+
+TokenList SqlEnterpriseFormatter::optimizeInnerComments(const TokenList &inputTokens)
+{
+ // TODO
+ return inputTokens;
+}
+
+TokenList SqlEnterpriseFormatter::optimizeEndLineComments(const TokenList &inputTokens)
+{
+ // TODO
+ return inputTokens;
+}
+
+void SqlEnterpriseFormatter::indentMultiLineComments(const TokenList &inputTokens)
+{
+ // TODO
+}
+
+void SqlEnterpriseFormatter::wrapComment(const TokenPtr &token, bool isAtLineEnd)
+{
+ static_qstring(multiCommentTpl, "/* %1 */");
+ static_qstring(endLineCommentTpl, "-- %1");
+
+ bool isMultiLine = token->value.contains("\n");
+ if (isAtLineEnd && !isMultiLine && cfg.SqlEnterpriseFormatter.PreferredCommentMarker.get() == "--")
+ token->value = endLineCommentTpl.arg(token->value);
+ else
+ token->value = multiCommentTpl.arg(token->value);
+}
+
+QString SqlEnterpriseFormatter::applyComments(const QString& formatted, QList<SqlEnterpriseFormatter::Comment*> comments, Dialect dialect)
+{
+ if (comments.size() == 0)
+ return formatted;
+
+ int currentCommentPosition = comments.first()->position;
+
+ TokenList allTokens = Lexer::tokenize(formatted, dialect);
+ TokenList newTokens;
+ int currentTokenPosition = 0;
+ for (const TokenPtr& token : allTokens)
+ {
+ if (currentTokenPosition == currentCommentPosition)
+ {
+ newTokens << TokenPtr::create(Token::Type::COMMENT, comments.first()->contents);
+ comments.removeFirst();
+ if (comments.size() > 0)
+ currentCommentPosition = comments.first()->position;
+ else
+ currentCommentPosition = -1;
+ }
+
+ newTokens << token;
+ if (token->type != Token::Type::SPACE)
+ currentTokenPosition++;
+ }
+
+ // Any remaining comments
+ for (Comment* cmt : comments)
+ newTokens << TokenPtr::create(Token::Type::COMMENT, cmt->contents);
+
+ if (cfg.SqlEnterpriseFormatter.MoveAllCommentsToLineEnd.get())
+ newTokens = adjustCommentsToEnd(newTokens);
+ else
+ newTokens = wrapOnlyComments(newTokens);
+
+ newTokens = optimizeInnerComments(newTokens);
+ newTokens = optimizeEndLineComments(newTokens);
+ indentMultiLineComments(newTokens);
+
+ return newTokens.detokenize();
+}
diff --git a/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.h b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.h
index 2701745..1f9b6d8 100644
--- a/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.h
+++ b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.h
@@ -53,6 +53,10 @@ CFG_CATEGORIES(SqlEnterpriseFormatterConfig,
CFG_ENTRY(QString, PrefferedWrapper, getNameWrapperStr(NameWrapper::BRACKET))
CFG_ENTRY(QStringList, Wrappers, getNameWrapperStrings(), false)
CFG_ENTRY(QString, PreviewCode, QString(), false)
+ CFG_ENTRY(bool, MoveAllCommentsToLineEnd, false)
+ CFG_ENTRY(bool, LineUpCommentsAtLineEnd, true)
+ CFG_ENTRY(QString, PreferredCommentMarker, "--")
+ CFG_ENTRY(QStringList, CommentMarkers, QStringList({"--", "/* */"}))
)
)
@@ -73,6 +77,25 @@ class SQLENTERPRISEFORMATTERSHARED_EXPORT SqlEnterpriseFormatter : public Generi
void configDialogClosed();
private:
+ struct Comment
+ {
+ int position = 0;
+ QString contents;
+ bool tokensBefore = false;
+ bool tokensAfter = false;
+ bool multiline = false;
+ };
+
+ QList<Comment*> collectComments(const TokenList& tokens);
+ QString applyComments(const QString& formatted, QList<Comment *> comments, Dialect dialect);
+ QList<TokenList> tokensByLines(const TokenList& tokens, bool includeSpaces = false);
+ TokenList adjustCommentsToEnd(const TokenList& inputTokens);
+ TokenList wrapOnlyComments(const TokenList& inputTokens);
+ TokenList optimizeInnerComments(const TokenList& inputTokens);
+ TokenList optimizeEndLineComments(const TokenList& inputTokens);
+ void indentMultiLineComments(const TokenList& inputTokens);
+ void wrapComment(const TokenPtr& token, bool isAtLineEnd);
+
QList<SqliteQueryPtr> previewQueries;
CFG_LOCAL_PERSISTABLE(SqlEnterpriseFormatterConfig, cfg)
diff --git a/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.ui b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.ui
index 2ebfbdf..b21de64 100644
--- a/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.ui
+++ b/Plugins/SqlEnterpriseFormatter/sqlenterpriseformatter.ui
@@ -39,7 +39,7 @@
</sizepolicy>
</property>
<property name="currentIndex">
- <number>1</number>
+ <number>0</number>
</property>
<widget class="QWidget" name="indentTab">
<attribute name="title">
@@ -133,9 +133,9 @@
<property name="geometry">
<rect>
<x>0</x>
- <y>-166</y>
- <width>578</width>
- <height>350</height>
+ <y>0</y>
+ <width>390</width>
+ <height>299</height>
</rect>
</property>
<property name="styleSheet">
@@ -366,8 +366,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>424</width>
- <height>325</height>
+ <width>418</width>
+ <height>278</height>
</rect>
</property>
<property name="styleSheet">
@@ -626,6 +626,63 @@
</item>
</layout>
</widget>
+ <widget class="QWidget" name="commentsTab">
+ <attribute name="title">
+ <string>Comments</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Preferred comment marker (where possible):</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="ConfigComboBox" name="comboBox_2">
+ <property name="cfg" stdset="0">
+ <string notr="true">SqlEnterpriseFormatter.PreferredCommentMarker</string>
+ </property>
+ <property name="modelName" stdset="0">
+ <string>SqlEnterpriseFormatter.CommentMarkers</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QCheckBox" name="checkBox_33">
+ <property name="text">
+ <string>Move all comments to the line end</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string notr="true">SqlEnterpriseFormatter.MoveAllCommentsToLineEnd</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <widget class="QCheckBox" name="checkBox_34">
+ <property name="text">
+ <string>Line up comments at the line end</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string notr="true">SqlEnterpriseFormatter.LineUpCommentsAtLineEnd</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <spacer name="verticalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
</widget>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
diff --git a/Plugins/SqlExport/SqlExport.pro b/Plugins/SqlExport/SqlExport.pro
index 30e4632..eb18a7c 100644
--- a/Plugins/SqlExport/SqlExport.pro
+++ b/Plugins/SqlExport/SqlExport.pro
@@ -29,7 +29,8 @@ RESOURCES += \
sqlexport.qrc
-TRANSLATIONS += SqlExport_zh_CN.ts \
+TRANSLATIONS += SqlExport_it.ts \
+ SqlExport_zh_CN.ts \
SqlExport_sk.ts \
SqlExport_de.ts \
SqlExport_ru.ts \
@@ -49,3 +50,4 @@ TRANSLATIONS += SqlExport_zh_CN.ts \
+
diff --git a/Plugins/SqlExport/SqlExport_it.ts b/Plugins/SqlExport/SqlExport_it.ts
new file mode 100644
index 0000000..7f5368b
--- /dev/null
+++ b/Plugins/SqlExport/SqlExport_it.ts
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="it_IT">
+<context>
+ <name>SqlExport</name>
+ <message>
+ <location filename="sqlexport.cpp" line="57"/>
+ <source>-- Results of query:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlexport.cpp" line="122"/>
+ <source>-- Table: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlexport.cpp" line="168"/>
+ <source>-- Index: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlexport.cpp" line="185"/>
+ <source>-- Trigger: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlexport.cpp" line="202"/>
+ <source>-- View: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlexport.cpp" line="216"/>
+ <source>-- File generated with SQLiteStudio v%1 on %2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlexport.cpp" line="220"/>
+ <source>-- Text encoding used: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="sqlexport.cpp" line="312"/>
+ <source>Table name for INSERT statements is mandatory.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>sqlExportCommonConfig</name>
+ <message>
+ <location filename="SqlExportCommon.ui" line="20"/>
+ <source>Generate &quot;DROP IF EXISTS&quot; statement before &quot;CREATE&quot; statement</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="SqlExportCommon.ui" line="30"/>
+ <source>Format DDL statements only (excludes &quot;INSERT&quot; statements)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="SqlExportCommon.ui" line="40"/>
+ <source>Use SQL formatter to format exported SQL statements</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>sqlExportQueryConfig</name>
+ <message>
+ <location filename="SqlExportQuery.ui" line="20"/>
+ <source>Use SQL formatter to format exported SQL statements</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="SqlExportQuery.ui" line="30"/>
+ <source>Table name to use for INSERT statements:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="SqlExportQuery.ui" line="37"/>
+ <source>Generate &quot;CREATE TABLE&quot; statement at the begining</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="SqlExportQuery.ui" line="47"/>
+ <source>Include the query in comments</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="SqlExportQuery.ui" line="57"/>
+ <source>Generate &quot;DROP IF EXISTS&quot; statement before &quot;CREATE&quot; statement</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="SqlExportQuery.ui" line="74"/>
+ <source>Format DDL statements only (excludes &quot;INSERT&quot; statements)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/Plugins/SqlExport/SqlExport_zh_CN.ts b/Plugins/SqlExport/SqlExport_zh_CN.ts
index d29cae8..c6c72de 100644
--- a/Plugins/SqlExport/SqlExport_zh_CN.ts
+++ b/Plugins/SqlExport/SqlExport_zh_CN.ts
@@ -6,37 +6,37 @@
<message>
<location filename="sqlexport.cpp" line="57"/>
<source>-- Results of query:</source>
- <translation type="unfinished"></translation>
+ <translation>-- 执行结果:</translation>
</message>
<message>
<location filename="sqlexport.cpp" line="122"/>
<source>-- Table: %1</source>
- <translation type="unfinished"></translation>
+ <translation>-- 表:%1</translation>
</message>
<message>
<location filename="sqlexport.cpp" line="168"/>
<source>-- Index: %1</source>
- <translation type="unfinished"></translation>
+ <translation>-- 索引:%1</translation>
</message>
<message>
<location filename="sqlexport.cpp" line="185"/>
<source>-- Trigger: %1</source>
- <translation type="unfinished"></translation>
+ <translation>-- 触发器:%1</translation>
</message>
<message>
<location filename="sqlexport.cpp" line="202"/>
<source>-- View: %1</source>
- <translation type="unfinished"></translation>
+ <translation>-- 视图:%1</translation>
</message>
<message>
<location filename="sqlexport.cpp" line="216"/>
<source>-- File generated with SQLiteStudio v%1 on %2</source>
- <translation type="unfinished"></translation>
+ <translation>-- 由SQLiteStudio v%1 产生的文件 %2</translation>
</message>
<message>
<location filename="sqlexport.cpp" line="220"/>
<source>-- Text encoding used: %1</source>
- <translation type="unfinished"></translation>
+ <translation>-- 文本编码:%1</translation>
</message>
<message>
<location filename="sqlexport.cpp" line="312"/>
diff --git a/Plugins/SqlFormatterSimple/SqlFormatterSimple.pro b/Plugins/SqlFormatterSimple/SqlFormatterSimple.pro
index 61ec27a..7329f93 100644
--- a/Plugins/SqlFormatterSimple/SqlFormatterSimple.pro
+++ b/Plugins/SqlFormatterSimple/SqlFormatterSimple.pro
@@ -28,7 +28,8 @@ RESOURCES += \
sqlformattersimple.qrc
-TRANSLATIONS += SqlFormatterSimple_zh_CN.ts \
+TRANSLATIONS += SqlFormatterSimple_it.ts \
+ SqlFormatterSimple_zh_CN.ts \
SqlFormatterSimple_sk.ts \
SqlFormatterSimple_de.ts \
SqlFormatterSimple_ru.ts \
@@ -48,3 +49,4 @@ TRANSLATIONS += SqlFormatterSimple_zh_CN.ts \
+
diff --git a/Plugins/SqlFormatterSimple/SqlFormatterSimple_it.ts b/Plugins/SqlFormatterSimple/SqlFormatterSimple_it.ts
new file mode 100644
index 0000000..91208f1
--- /dev/null
+++ b/Plugins/SqlFormatterSimple/SqlFormatterSimple_it.ts
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="it_IT">
+<context>
+ <name>SqlFormatterSimplePlugin</name>
+ <message>
+ <location filename="SqlFormatterSimple.ui" line="20"/>
+ <source>Upper case keywords</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="SqlFormatterSimple.ui" line="30"/>
+ <source>Reduce multiple whitespaces to single whitespace</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/Plugins/SqlFormatterSimple/SqlFormatterSimple_zh_CN.ts b/Plugins/SqlFormatterSimple/SqlFormatterSimple_zh_CN.ts
index aaa7f79..40c7f1e 100644
--- a/Plugins/SqlFormatterSimple/SqlFormatterSimple_zh_CN.ts
+++ b/Plugins/SqlFormatterSimple/SqlFormatterSimple_zh_CN.ts
@@ -6,12 +6,12 @@
<message>
<location filename="SqlFormatterSimple.ui" line="20"/>
<source>Upper case keywords</source>
- <translation type="unfinished"></translation>
+ <translation>大写关键字</translation>
</message>
<message>
<location filename="SqlFormatterSimple.ui" line="30"/>
<source>Reduce multiple whitespaces to single whitespace</source>
- <translation type="unfinished"></translation>
+ <translation>将多个空白转换为一个空白</translation>
</message>
</context>
</TS>
diff --git a/Plugins/XmlExport/XmlExport.pro b/Plugins/XmlExport/XmlExport.pro
index e22c320..bf34076 100644
--- a/Plugins/XmlExport/XmlExport.pro
+++ b/Plugins/XmlExport/XmlExport.pro
@@ -27,7 +27,8 @@ RESOURCES += \
xmlexport.qrc
-TRANSLATIONS += XmlExport_zh_CN.ts \
+TRANSLATIONS += XmlExport_it.ts \
+ XmlExport_zh_CN.ts \
XmlExport_sk.ts \
XmlExport_de.ts \
XmlExport_ru.ts \
@@ -47,3 +48,4 @@ TRANSLATIONS += XmlExport_zh_CN.ts \
+
diff --git a/Plugins/XmlExport/XmlExport_it.ts b/Plugins/XmlExport/XmlExport_it.ts
new file mode 100644
index 0000000..58265f6
--- /dev/null
+++ b/Plugins/XmlExport/XmlExport_it.ts
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="it_IT">
+<context>
+ <name>XmlExport</name>
+ <message>
+ <location filename="xmlexport.cpp" line="38"/>
+ <source>Enter the namespace to use (for example: http://my.namespace.org)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>XmlExportConfig</name>
+ <message>
+ <location filename="XmlExport.ui" line="20"/>
+ <source>Output format</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="XmlExport.ui" line="26"/>
+ <source>Format document (new lines, indentation)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="XmlExport.ui" line="42"/>
+ <source>Compress (everything in one line)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="XmlExport.ui" line="58"/>
+ <source>Special characters escaping</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="XmlExport.ui" line="64"/>
+ <source>&lt;p&gt;Ampersands will be used for shorter values and CDATA will be used for larger values. This applies only to values that require character escaping. Other values will be exported as they are.&lt;/p&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="XmlExport.ui" line="67"/>
+ <source>Use CDATA and ampersands</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="XmlExport.ui" line="83"/>
+ <source>&lt;p&gt;Every value requiring character escepe will be enclosed in CDATA block.&lt;/p&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="XmlExport.ui" line="86"/>
+ <source>Always use CDATA</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="XmlExport.ui" line="99"/>
+ <source>&lt;p&gt;Every character that require esceping will be replaced with its ampersand escape sequence. No CDATA blocks will be used.&lt;/p&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="XmlExport.ui" line="102"/>
+ <source>Always use ampersand</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="XmlExport.ui" line="118"/>
+ <source>Define XML namespace</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>