File indexing completed on 2025-01-19 03:53:49

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-11-14
0007  * Description : database settings widget
0008  *
0009  * SPDX-FileCopyrightText: 2009-2010 by Holger Foerster <Hamsi2k at freenet dot de>
0010  * SPDX-FileCopyrightText: 2010-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "dbsettingswidget_p.h"
0017 
0018 namespace Digikam
0019 {
0020 
0021 DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* const parent)
0022     : QWidget(parent),
0023       d      (new Private)
0024 {
0025     setupMainArea();
0026 }
0027 
0028 DatabaseSettingsWidget::~DatabaseSettingsWidget()
0029 {
0030     delete d;
0031 }
0032 
0033 void DatabaseSettingsWidget::setupMainArea()
0034 {
0035     QVBoxLayout* const layout       = new QVBoxLayout();
0036     setLayout(layout);
0037 
0038     // --------------------------------------------------------
0039 
0040     const int spacing = qMin(QApplication::style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing),
0041                              QApplication::style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing));
0042 
0043     QGroupBox* const dbConfigBox    = new QGroupBox(i18n("Database Configuration"), this);
0044     QVBoxLayout* const vlay         = new QVBoxLayout(dbConfigBox);
0045 
0046     DHBox* const typeHbox           = new DHBox();
0047     QLabel* const databaseTypeLabel = new QLabel(typeHbox);
0048     d->dbType                       = new QComboBox(typeHbox);
0049     databaseTypeLabel->setText(i18n("Type:"));
0050 
0051     // --------- fill with default values ---------------------
0052 
0053     int dbTypeIdx               = 0;
0054     d->dbType->addItem(i18n("SQLite"),                        SQlite);
0055     d->dbTypeMap[SQlite]        = dbTypeIdx++;
0056 
0057 #ifdef HAVE_MYSQLSUPPORT
0058 
0059 #   ifdef HAVE_INTERNALMYSQL
0060 
0061     d->dbType->addItem(i18n("Mysql Internal"), MysqlInternal);
0062     d->dbTypeMap[MysqlInternal] = dbTypeIdx++;
0063 
0064 #   endif
0065 
0066     d->dbType->addItem(i18n("MySQL Server"),   MysqlServer);
0067     d->dbTypeMap[MysqlServer]   = dbTypeIdx++;
0068 
0069 #endif
0070 
0071     QString tip = i18n("<p>Select here the type of database backend.</p>"
0072                        "<p><b>SQlite</b> backend is for local database storage with a small or medium collection sizes. "
0073                        "It is the default and recommended backend for collections with less than 100K items.</p>");
0074 
0075 
0076 #ifdef HAVE_MYSQLSUPPORT
0077 
0078 #   ifdef HAVE_INTERNALMYSQL
0079 
0080     tip.append(i18n("<p><b>MySQL Internal</b> backend is for local database storage with huge collection sizes. "
0081                     "This backend is recommend for local collections with more than 100K items.</p>"));
0082 
0083 #   endif
0084 
0085     tip.append(i18n("<p><b>MySQL Server</b> backend is a more robust solution especially for remote and shared database storage. "
0086                     "It is also more efficient to manage huge collection sizes with more than 100K items.</p>"));
0087 
0088 #endif
0089 
0090     d->dbType->setToolTip(tip);
0091 
0092     // --------------------------------------------------------
0093 
0094     d->dbPathLabel = new QLabel(i18n("<p>Set here the location where the database files will be stored on your system. "
0095                                      "There are four databases: "
0096                                      "one for all collections properties, "
0097                                      "one to store compressed thumbnails, "
0098                                      "one to store faces recognition metadata, "
0099                                      "and one to store similarity fingerprints.<br/>"
0100                                      "Write access is required to be able to edit image properties.</p>"
0101                                      "<p>Databases are digiKam core engines. Take care to use a place hosted by fast "
0102                                      "hardware (eg. SSD or NVMe) with enough free space especially for thumbnails database.</p>"
0103                                      "<p>Note: a remote file system such as NFS, cannot be used here. "
0104                                      "For performance reasons, it is also recommended not to use network storage media.</p>"
0105                                      "<p></p>"), dbConfigBox);
0106     d->dbPathLabel->setWordWrap(true);
0107     d->dbPathEdit  = new DFileSelector(dbConfigBox);
0108     d->dbPathEdit->setFileDlgMode(QFileDialog::Directory);
0109     d->dbPathEdit->setFileDlgOptions(QFileDialog::ShowDirsOnly);
0110 
0111     // --------------------------------------------------------
0112 
0113     d->walModeCheck = new QCheckBox(i18n("Enable WAL mode for the databases"), dbConfigBox);
0114     d->walModeCheck->setToolTip(i18n("The WAL (Write-Ahead Log) mode is significantly "
0115                                      "faster in most scenarios on supported systems."));
0116 
0117     d->walLabel     = new QLabel(i18n("Write-Ahead Log is a mode to use a roll-forward journal that records transactions "
0118                                       "that have been committed but not yet applied to the databases. It uses an auxiliary "
0119                                       "journalized file to host structures for recovery transactions during a crash. The changes "
0120                                       "are first recorded in the log, before the changes are written to the database. This made "
0121                                       "database requests atomic and robust in extensive and critical use cases."),
0122                                  dbConfigBox);
0123     d->walLabel->setWordWrap(true);
0124 
0125     // --------------------------------------------------------
0126 
0127     d->mysqlCmdBox = new DVBox(dbConfigBox);
0128     d->mysqlCmdBox->layout()->setContentsMargins(QMargins());
0129 
0130     new DLineWidget(Qt::Horizontal, d->mysqlCmdBox);
0131     QLabel* const mysqlBinariesLabel  = new QLabel(i18n("<p>Here you can configure locations where MySQL binary tools are located. "
0132                                                         "digiKam will try to find these binaries automatically if they are "
0133                                                         "already installed on your computer.</p>"),
0134                                                    d->mysqlCmdBox);
0135     mysqlBinariesLabel->setWordWrap(true);
0136 
0137     QGroupBox* const binaryBox        = new QGroupBox(d->mysqlCmdBox);
0138     QGridLayout* const binaryLayout   = new QGridLayout;
0139     binaryBox->setLayout(binaryLayout);
0140     binaryBox->setTitle(i18nc("@title:group", "MySQL Binaries"));
0141     d->dbBinariesWidget               = new DBinarySearch(binaryBox);
0142     d->dbBinariesWidget->header()->setSectionHidden(2, true);
0143 
0144     d->dbBinariesWidget->addBinary(d->mysqlServerBin);
0145     d->dbBinariesWidget->addBinary(d->mysqlAdminBin);
0146     d->dbBinariesWidget->addBinary(d->mysqlUpgradeBin);
0147     d->dbBinariesWidget->addBinary(d->mysqlInitBin);
0148 
0149 #ifdef Q_OS_LINUX
0150 
0151     d->dbBinariesWidget->addDirectory(QLatin1String("/usr/bin"));
0152     d->dbBinariesWidget->addDirectory(QLatin1String("/usr/sbin"));
0153 
0154 #endif
0155 
0156 #ifdef Q_OS_MACOS
0157 
0158     // Std Macports install
0159     d->dbBinariesWidget->addDirectory(QLatin1String("/opt/local/bin"));
0160     d->dbBinariesWidget->addDirectory(QLatin1String("/opt/local/sbin"));
0161     d->dbBinariesWidget->addDirectory(QLatin1String("/opt/local/lib/mariadb/bin"));
0162 
0163     // digiKam Bundle PKG install
0164     d->dbBinariesWidget->addDirectory(macOSBundlePrefix() + QLatin1String("lib/mariadb/bin"));
0165 
0166 #endif
0167 
0168 #ifdef Q_OS_WIN
0169 
0170     d->dbBinariesWidget->addDirectory(QLatin1String("C:/Program Files/MariaDB 10.5/bin"));
0171     d->dbBinariesWidget->addDirectory(QLatin1String("C:/Program Files (x86)/MariaDB 10.5/bin"));
0172 
0173 #endif
0174 
0175     d->dbBinariesWidget->allBinariesFound();
0176 
0177     // --------------------------------------------------------
0178 
0179     d->tab                                           = new QTabWidget(this);
0180 
0181     QLabel* const hostNameLabel                      = new QLabel(i18n("Host Name:"));
0182     d->hostName                                      = new QLineEdit();
0183     d->hostName->setPlaceholderText(i18n("Set the host computer name"));
0184     d->hostName->setToolTip(i18n("This is the computer name running MySQL server.\n"
0185                                  "This can be \"localhost\" for a local server, "
0186                                  "or the network computer\n"
0187                                  "name (or IP address) in case of remote computer."));
0188 
0189     QLabel* const connectOptsLabel                   = new QLabel(i18n("<a href=\"https://doc.qt.io/qt-5/"
0190                                                                        "qsqldatabase.html#setConnectOptions\">Connect options:</a>"));
0191     connectOptsLabel->setOpenExternalLinks(true);
0192     d->connectOpts                                   = new QLineEdit();
0193     d->connectOpts->setPlaceholderText(i18n("Set the database connection options"));
0194     d->connectOpts->setToolTip(i18n("Set the MySQL server connection options.\n"
0195                                     "For advanced users only."));
0196 
0197     QLabel* const userNameLabel                      = new QLabel(i18n("User:"));
0198     d->userName                                      = new QLineEdit();
0199     d->userName->setPlaceholderText(i18n("Set the database account name"));
0200     d->userName->setToolTip(i18n("Set the MySQL server account name used\n"
0201                                  "by digiKam to be connected to the server.\n"
0202                                  "This account must be available on the remote\n"
0203                                  "MySQL server when database have been created."));
0204 
0205     QLabel* const passwordLabel                      = new QLabel(i18n("Password:"));
0206     d->password                                      = new QLineEdit();
0207     d->password->setToolTip(i18n("Set the MySQL server account password used\n"
0208                                  "by digiKam to be connected to the server.\n"
0209                                  "You can left this field empty to use an account set without password."));
0210     d->password->setEchoMode(QLineEdit::Password);
0211 
0212     DHBox* const phbox                               = new DHBox();
0213     QLabel* const hostPortLabel                      = new QLabel(i18n("Host Port:"));
0214     d->hostPort                                      = new QSpinBox(phbox);
0215     d->hostPort->setToolTip(i18n("Set the host computer port.\nUsually, MySQL server use port number 3306 by default"));
0216     d->hostPort->setMaximum(65535);
0217     d->hostPort->setValue(3306);
0218     QWidget* const space                             = new QWidget(phbox);
0219     phbox->setStretchFactor(space, 10);
0220     QPushButton* const checkDBConnectBtn             = new QPushButton(i18n("Check Connection"), phbox);
0221     checkDBConnectBtn->setToolTip(i18n("Run a basic database connection to see if current MySQL server settings is suitable."));
0222 
0223     // Only accept printable Ascii char for database names.
0224 
0225     QRegularExpression asciiRx(QLatin1String("[\x20-\x7F]+$"));
0226     QValidator* const asciiValidator = new QRegularExpressionValidator(asciiRx, this);
0227 
0228     QLabel* const dbNameCoreLabel                    = new QLabel(i18n("Core Db Name:"));
0229     d->dbNameCore                                    = new QLineEdit();
0230     d->dbNameCore->setPlaceholderText(i18n("Set the core database name"));
0231     d->dbNameCore->setToolTip(i18n("The core database is lead digiKam container used to store\n"
0232                                    "albums, items, and searches metadata."));
0233     d->dbNameCore->setValidator(asciiValidator);
0234 
0235     d->dbThumbsLabel                                 = new QLabel(i18n("Thumbs Db Name:"));
0236     d->dbNameThumbs                                  = new DFileSelector();
0237     d->dbNameThumbs->setFileDlgMode(QFileDialog::Directory);
0238     d->dbNameThumbs->setFileDlgOptions(QFileDialog::ShowDirsOnly);
0239     d->dbNameThumbs->lineEdit()->setPlaceholderText(i18n("Set the thumbnails database name or folder"));
0240     d->dbNameThumbs->setToolTip(i18n("The thumbnails database is used by digiKam to host\n"
0241                                      "image thumbs with wavelets compression images.\n"
0242                                      "This one can use quickly a lots of space,\n"
0243                                      "especially if you have huge collections.\n"
0244                                      "Choose a local folder to use a SQLite database."));
0245 
0246     QLabel* const dbNameFaceLabel                    = new QLabel(i18n("Face Db Name:"));
0247     d->dbNameFace                                    = new QLineEdit();
0248     d->dbNameFace->setPlaceholderText(i18n("Set the face database name"));
0249     d->dbNameFace->setToolTip(i18n("The face database is used by digiKam to host image histograms\n"
0250                                    "dedicated to faces recognition process.\n"
0251                                    "This one can use quickly a lots of space, especially\n"
0252                                    "if you a lots of image with people faces detected and tagged."));
0253     d->dbNameFace->setValidator(asciiValidator);
0254 
0255     QLabel* const dbNameSimilarityLabel              = new QLabel(i18n("Similarity Db Name:"));
0256     d->dbNameSimilarity                              = new QLineEdit();
0257     d->dbNameSimilarity->setPlaceholderText(i18n("Set the similarity database name"));
0258     d->dbNameSimilarity->setToolTip(i18n("The similarity database is used by digiKam to host\n"
0259                                          "image Haar matrix data for the similarity search."));
0260     d->dbNameSimilarity->setValidator(asciiValidator);
0261 
0262     QPushButton* const defaultValuesBtn              = new QPushButton(i18n("Default Settings"));
0263     defaultValuesBtn->setToolTip(i18n("Reset database names settings to common default values."));
0264 
0265     d->expertSettings                                = new QGroupBox();
0266     d->expertSettings->setFlat(true);
0267     QFormLayout* const expertSettinglayout           = new QFormLayout();
0268     d->expertSettings->setLayout(expertSettinglayout);
0269 
0270     expertSettinglayout->addRow(hostNameLabel,         d->hostName);
0271     expertSettinglayout->addRow(userNameLabel,         d->userName);
0272     expertSettinglayout->addRow(passwordLabel,         d->password);
0273     expertSettinglayout->addRow(connectOptsLabel,      d->connectOpts);
0274     expertSettinglayout->addRow(hostPortLabel,         phbox);
0275     expertSettinglayout->addRow(new DLineWidget(Qt::Horizontal, d->expertSettings));
0276     expertSettinglayout->addRow(dbNameCoreLabel,       d->dbNameCore);
0277     expertSettinglayout->addRow(d->dbThumbsLabel,      d->dbNameThumbs);
0278     expertSettinglayout->addRow(dbNameFaceLabel,       d->dbNameFace);
0279     expertSettinglayout->addRow(dbNameSimilarityLabel, d->dbNameSimilarity);
0280     expertSettinglayout->addRow(new QWidget(),         defaultValuesBtn);
0281 
0282     d->tab->addTab(d->expertSettings, i18n("Remote Server Settings"));
0283 
0284     // --------------------------------------------------------
0285 
0286     d->dbNoticeBox           = new QGroupBox(i18n("Database Server Instructions"), this);
0287     QVBoxLayout* const vlay2 = new QVBoxLayout(d->dbNoticeBox);
0288     QLabel* const notice     = new QLabel(i18n("<p>digiKam expects that database is already created with a dedicated user account. "
0289                                                "This user name <i>digikam</i> will require full access to the database.<br/>"
0290                                                "If your database is not already set up, you can use the following SQL commands "
0291                                                "(after replacing the <b><i>password</i></b> with the correct one).</p>"),
0292                                           d->dbNoticeBox);
0293     notice->setWordWrap(true);
0294 
0295     d->sqlInit                  = new QTextBrowser(d->dbNoticeBox);
0296     d->sqlInit->setOpenExternalLinks(false);
0297     d->sqlInit->setOpenLinks(false);
0298     d->sqlInit->setReadOnly(false);
0299 
0300     QTextBrowser* const notice2 = new QTextBrowser(this);
0301     notice2->setText(i18n("<p>Note: with a Linux server, a database can be initialized following the commands below:</p>"
0302                            "<p># su</p>"
0303                            "<p># systemctl restart mysqld</p>"
0304                            "<p># mysql -u root</p>"
0305                            "<p>...</p>"
0306                            "<p>Enter SQL code to Mysql prompt in order to init digiKam databases with grant privileges (see behind)</p>"
0307                            "<p>...</p>"
0308                            "<p>quit</p>"
0309                            "<p>NOTE: If you have problems with a MySQL server on Ubuntu based Linux system, "
0310                            "use the addition command in the mysql prompt to be able to create MySQL triggers.<br>"
0311                            "SET GLOBAL log_bin_trust_function_creators=1;</p>"
0312                            "<p>NOTE: If you have an enormous collection, you should start MySQL server with "
0313                            "mysql --max_allowed_packet=128M OR in my.ini or ~/.my.cnf, change the settings</p>"));
0314 
0315     notice2->setOpenExternalLinks(false);
0316     notice2->setOpenLinks(false);
0317     notice2->setReadOnly(true);
0318 
0319     vlay2->addWidget(notice,     0);
0320     vlay2->addWidget(d->sqlInit, 10);
0321     vlay2->addWidget(notice2,    20);
0322     vlay2->setContentsMargins(spacing, spacing, spacing, spacing);
0323     vlay2->setSpacing(spacing);
0324 
0325     d->tab->addTab(d->dbNoticeBox, i18n("Requirements"));
0326 
0327     // --------------------------------------------------------
0328 
0329     d->dbDetailsBox          = new QGroupBox(i18n("Database Server Technical Details"), this);
0330     QVBoxLayout* const vlay3 = new QVBoxLayout(d->dbDetailsBox);
0331     QLabel* const details    = new QLabel(i18n("<p>Use this configuration view to set all information "
0332                                                "to be connected to a remote "
0333                                                "<a href=\"https://en.wikipedia.org/wiki/MySQL\">Mysql database server</a> "
0334                                                "(or <a href=\"https://en.wikipedia.org/wiki/MariaDB\">MariaDB</a>) "
0335                                                "through a network. "
0336                                                "As with Sqlite or MySQL internal server, 3 databases will be stored "
0337                                                "on the remote server: one for all collections properties, "
0338                                                "one to store compressed thumbnails, and one to store faces "
0339                                                "recognition metadata.</p>"
0340                                                "<p>Unlike Sqlite or MySQL internal server, you can customize the "
0341                                                "database names to simplify your backups.</p>"
0342                                                "<p>Databases are digiKam core engines. To prevent performance issues, "
0343                                                "take a care to use a fast network link between the client and the server "
0344                                                "computers. It is also recommended to host database files on "
0345                                                "fast hardware (as <a href=\"https://en.wikipedia.org/wiki/Solid-state_drive\">SSD</a>) "
0346                                                "with enough free space, especially for thumbnails database, even if data are compressed using wavelets image format <a href=\"https://en.wikipedia.org/wiki/Progressive_Graphics_File\">"
0347                                                "PGF</a>.</p>"
0348                                                "<p>The databases must be created previously on the remote server by the administrator. "
0349                                                "Look in <b>Requirements</b> tab for details.</p>"),
0350                                           d->dbDetailsBox);
0351     details->setWordWrap(true);
0352 
0353     vlay3->addWidget(details);
0354     vlay3->setContentsMargins(spacing, spacing, spacing, spacing);
0355     vlay3->setSpacing(spacing);
0356 
0357     d->tab->addTab(d->dbDetailsBox, i18n("Documentation"));
0358 
0359     // --------------------------------------------------------
0360 
0361     vlay->addWidget(typeHbox);
0362     vlay->addWidget(new DLineWidget(Qt::Horizontal));
0363     vlay->addWidget(d->dbPathLabel);
0364     vlay->addWidget(d->dbPathEdit);
0365     vlay->addWidget(d->mysqlCmdBox);
0366     vlay->addWidget(d->tab);
0367     vlay->addWidget(d->walModeCheck);
0368     vlay->addWidget(d->walLabel);
0369     vlay->addStretch(10);
0370     vlay->setContentsMargins(spacing, spacing, spacing, spacing);
0371     vlay->setSpacing(spacing);
0372 
0373     // --------------------------------------------------------
0374 
0375     layout->setContentsMargins(QMargins());
0376     layout->setSpacing(spacing);
0377     layout->addWidget(dbConfigBox);
0378 
0379     // --------------------------------------------------------
0380 
0381     connect(d->dbType, SIGNAL(currentIndexChanged(int)),
0382             this, SLOT(slotHandleDBTypeIndexChanged(int)));
0383 
0384     connect(checkDBConnectBtn, SIGNAL(clicked()),
0385             this, SLOT(slotCheckMysqlServerConnection()));
0386 
0387     connect(defaultValuesBtn, SIGNAL(clicked()),
0388             this, SLOT(slotResetMysqlServerDBNames()));
0389 
0390     connect(d->dbNameCore, SIGNAL(textChanged(QString)),
0391             this, SLOT(slotUpdateSqlInit()));
0392 
0393     connect(d->dbNameThumbs->lineEdit(), SIGNAL(textChanged(QString)),
0394             this, SLOT(slotUpdateSqlInit()));
0395 
0396     connect(d->dbNameFace, SIGNAL(textChanged(QString)),
0397             this, SLOT(slotUpdateSqlInit()));
0398 
0399     connect(d->dbNameSimilarity, SIGNAL(textChanged(QString)),
0400             this, SLOT(slotUpdateSqlInit()));
0401 
0402     connect(d->userName, SIGNAL(textChanged(QString)),
0403             this, SLOT(slotUpdateSqlInit()));
0404 
0405     slotHandleDBTypeIndexChanged(d->dbType->currentIndex());
0406 }
0407 
0408 int DatabaseSettingsWidget::databaseType() const
0409 {
0410     return d->dbType->currentData().toInt();
0411 }
0412 
0413 QString DatabaseSettingsWidget::databasePath() const
0414 {
0415     return d->dbPathEdit->fileDlgPath();
0416 }
0417 
0418 void DatabaseSettingsWidget::setDatabasePath(const QString& path)
0419 {
0420     d->dbPathEdit->setFileDlgPath(path);
0421 }
0422 
0423 DbEngineParameters DatabaseSettingsWidget::orgDatabasePrm() const
0424 {
0425     return d->orgPrms;
0426 }
0427 
0428 QString DatabaseSettingsWidget::databaseBackend() const
0429 {
0430     switch (databaseType())
0431     {
0432         case MysqlInternal:
0433         case MysqlServer:
0434         {
0435             return DbEngineParameters::MySQLDatabaseType();
0436         }
0437 
0438         default: // SQlite
0439         {
0440             return DbEngineParameters::SQLiteDatabaseType();
0441         }
0442     }
0443 }
0444 
0445 void DatabaseSettingsWidget::slotResetMysqlServerDBNames()
0446 {
0447     d->dbNameCore->setText(QLatin1String("digikam"));
0448     d->dbNameThumbs->setFileDlgPath(QLatin1String("digikam"));
0449     d->dbNameFace->setText(QLatin1String("digikam"));
0450     d->dbNameSimilarity->setText(QLatin1String("digikam"));
0451 }
0452 
0453 void DatabaseSettingsWidget::slotHandleDBTypeIndexChanged(int index)
0454 {
0455     int dbType = d->dbType->itemData(index).toInt();
0456     setDatabaseInputFields(dbType);
0457     handleInternalServer(dbType);
0458     slotUpdateSqlInit();
0459 }
0460 
0461 void DatabaseSettingsWidget::setDatabaseInputFields(int index)
0462 {
0463     switch (index)
0464     {
0465         case SQlite:
0466         {
0467             d->dbPathLabel->setVisible(true);
0468             d->dbPathEdit->setVisible(true);
0469             d->walModeCheck->setVisible(true);
0470             d->walLabel->setVisible(true);
0471             d->mysqlCmdBox->setVisible(false);
0472             d->tab->setVisible(false);
0473 
0474             connect(d->dbPathEdit->lineEdit(), SIGNAL(textChanged(QString)),
0475                     this, SLOT(slotDatabasePathEditedDelayed()));
0476 
0477             break;
0478         }
0479 
0480         case MysqlInternal:
0481         {
0482             d->dbPathLabel->setVisible(true);
0483             d->dbPathEdit->setVisible(true);
0484             d->walModeCheck->setVisible(false);
0485             d->walLabel->setVisible(false);
0486             d->mysqlCmdBox->setVisible(true);
0487             d->tab->setVisible(false);
0488 
0489             connect(d->dbPathEdit->lineEdit(), SIGNAL(textChanged(QString)),
0490                     this, SLOT(slotDatabasePathEditedDelayed()));
0491 
0492             break;
0493         }
0494 
0495         default: // MysqlServer
0496         {
0497             d->dbPathLabel->setVisible(false);
0498             d->dbPathEdit->setVisible(false);
0499             d->walModeCheck->setVisible(false);
0500             d->walLabel->setVisible(false);
0501             d->mysqlCmdBox->setVisible(false);
0502             d->tab->setVisible(true);
0503 
0504             disconnect(d->dbPathEdit->lineEdit(), SIGNAL(textChanged(QString)),
0505                        this, SLOT(slotDatabasePathEditedDelayed()));
0506             break;
0507         }
0508     }
0509 }
0510 
0511 void DatabaseSettingsWidget::handleInternalServer(int index)
0512 {
0513     bool internal = (index == MysqlInternal);
0514 
0515     d->hostName->setDisabled(internal);
0516     d->hostPort->setDisabled(internal);
0517     d->dbNameCore->setDisabled(internal);
0518     d->dbNameThumbs->setDisabled(internal);
0519     d->dbNameFace->setDisabled(internal);
0520     d->dbNameSimilarity->setDisabled(internal);
0521     d->userName->setDisabled(internal);
0522     d->password->setDisabled(internal);
0523     d->connectOpts->setDisabled(internal);
0524 }
0525 
0526 void DatabaseSettingsWidget::slotUpdateSqlInit()
0527 {
0528     QString sql = QString::fromLatin1("CREATE USER \'%1\'@\'%2\' IDENTIFIED BY \'<b>password</b>\';<br>")
0529                                       .arg(d->userName->text())
0530                                       .arg(d->hostName->text());
0531 
0532     sql += QString::fromLatin1("GRANT ALL ON *.* TO \'%1\'@\'%2\' IDENTIFIED BY \'<b>password</b>\';<br>")
0533                                .arg(d->userName->text())
0534                                .arg(d->hostName->text());
0535 
0536     sql += QString::fromLatin1("CREATE DATABASE `%1`;<br>"
0537                                "GRANT ALL PRIVILEGES ON `%2`.* TO \'%3\'@\'%4\';<br>")
0538                                .arg(d->dbNameCore->text())
0539                                .arg(d->dbNameCore->text())
0540                                .arg(d->userName->text())
0541                                .arg(d->hostName->text());
0542 
0543     if (isNotEqualToThumbName(d->dbNameCore->text()))
0544     {
0545         sql += QString::fromLatin1("CREATE DATABASE `%1`;<br>"
0546                                    "GRANT ALL PRIVILEGES ON `%2`.* TO \'%3\'@\'%4\';<br>")
0547                                    .arg(d->dbNameThumbs->fileDlgPath())
0548                                    .arg(d->dbNameThumbs->fileDlgPath())
0549                                    .arg(d->userName->text())
0550                                    .arg(d->hostName->text());
0551     }
0552 
0553     if (isNotEqualToThumbName(d->dbNameFace->text())   &&
0554         (d->dbNameFace->text() != d->dbNameCore->text()))
0555     {
0556         sql += QString::fromLatin1("CREATE DATABASE `%1`;<br>"
0557                                    "GRANT ALL PRIVILEGES ON `%2`.* TO \'%3\'@\'%4\';<br>")
0558                                    .arg(d->dbNameFace->text())
0559                                    .arg(d->dbNameFace->text())
0560                                    .arg(d->userName->text())
0561                                    .arg(d->hostName->text());
0562     }
0563 
0564     if (isNotEqualToThumbName(d->dbNameSimilarity->text())     &&
0565         (d->dbNameSimilarity->text() != d->dbNameCore->text()) &&
0566         (d->dbNameSimilarity->text() != d->dbNameFace->text()))
0567     {
0568         sql += QString::fromLatin1("CREATE DATABASE `%1`;<br>"
0569                                    "GRANT ALL PRIVILEGES ON `%2`.* TO \'%3\'@\'%4\';<br>")
0570                                    .arg(d->dbNameSimilarity->text())
0571                                    .arg(d->dbNameSimilarity->text())
0572                                    .arg(d->userName->text())
0573                                    .arg(d->hostName->text());
0574     }
0575 
0576     sql += QLatin1String("FLUSH PRIVILEGES;<br>");
0577 
0578     d->sqlInit->setText(sql);
0579 
0580     QFileInfo thumbDB(d->dbNameThumbs->fileDlgPath());
0581 
0582     if (thumbDB.exists() && thumbDB.isDir())
0583     {
0584         d->dbThumbsLabel->setText(i18n("Thumbs Db Folder:"));
0585     }
0586     else
0587     {
0588         d->dbThumbsLabel->setText(i18n("Thumbs Db Name:"));
0589     }
0590 }
0591 
0592 void DatabaseSettingsWidget::slotCheckMysqlServerConnection()
0593 {
0594     QString error;
0595 
0596     if (checkMysqlServerConnection(error))
0597     {
0598         QMessageBox::information(qApp->activeWindow(), i18nc("@title:window", "Database Connection Test"),
0599                                  i18n("Database connection test successful."));
0600     }
0601     else
0602     {
0603         QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Database Connection Test"),
0604                               i18n("Database connection test was not successful. <p>Error was: %1</p>",
0605                                    error));
0606     }
0607 }
0608 
0609 bool DatabaseSettingsWidget::checkMysqlServerConnectionConfig(QString& error)
0610 {
0611     if (d->hostName->text().isEmpty())
0612     {
0613         error = i18n("The server hostname is empty");
0614         return false;
0615     }
0616 
0617     if (d->userName->text().isEmpty())
0618     {
0619         error = i18n("The server user name is empty");
0620         return false;
0621     }
0622 
0623     return true;
0624 }
0625 
0626 bool DatabaseSettingsWidget::checkMysqlServerDbNamesConfig(QString& error)
0627 {
0628     if (d->dbNameCore->text().isEmpty())
0629     {
0630         error = i18n("The core database name is empty");
0631         return false;
0632     }
0633 
0634     if (d->dbNameThumbs->fileDlgPath().isEmpty())
0635     {
0636         error = i18n("The thumbnails database name is empty");
0637         return false;
0638     }
0639 
0640     if (d->dbNameFace->text().isEmpty())
0641     {
0642         error = i18n("The face database name is empty");
0643         return false;
0644     }
0645 
0646     if (d->dbNameSimilarity->text().isEmpty())
0647     {
0648         error = i18n("The similarity database name is empty");
0649         return false;
0650     }
0651 
0652     return true;
0653 }
0654 
0655 bool DatabaseSettingsWidget::checkMysqlServerConnection(QString& error)
0656 {
0657     if (!checkMysqlServerConnectionConfig(error))
0658     {
0659         return false;
0660     }
0661 
0662     bool result = false;
0663 
0664     qApp->setOverrideCursor(Qt::WaitCursor);
0665 
0666     QString databaseID(QLatin1String("ConnectionTest"));
0667 
0668     {
0669         QSqlDatabase testDatabase = QSqlDatabase::addDatabase(databaseBackend(), databaseID);
0670 
0671         DbEngineParameters prm    = getDbEngineParameters();
0672         qCDebug(DIGIKAM_DATABASE_LOG) << "Testing DB connection (" << databaseID << ") with these settings:";
0673         qCDebug(DIGIKAM_DATABASE_LOG) << prm;
0674 
0675         testDatabase.setHostName(prm.hostName);
0676         testDatabase.setPort(prm.port);
0677         testDatabase.setUserName(prm.userName);
0678         testDatabase.setPassword(prm.password);
0679         testDatabase.setConnectOptions(prm.connectOptions);
0680 
0681         result = testDatabase.open();
0682         error  = testDatabase.lastError().text();
0683         testDatabase.close();
0684     }
0685 
0686     QSqlDatabase::removeDatabase(databaseID);
0687 
0688     qApp->restoreOverrideCursor();
0689 
0690     return result;
0691 }
0692 
0693 void DatabaseSettingsWidget::setParametersFromSettings(const ApplicationSettings* const settings,
0694                                                        const bool& migration)
0695 {
0696     d->orgPrms = settings->getDbEngineParameters();
0697 
0698     if (d->orgPrms.databaseType == DbEngineParameters::SQLiteDatabaseType())
0699     {
0700         d->dbPathEdit->setFileDlgPath(d->orgPrms.getCoreDatabaseNameOrDir());
0701         d->dbType->setCurrentIndex(d->dbTypeMap[SQlite]);
0702         d->walModeCheck->setChecked(d->orgPrms.walMode);
0703         slotResetMysqlServerDBNames();
0704 
0705         if (settings->getDatabaseDirSetAtCmd() && !migration)
0706         {
0707             d->dbType->setEnabled(false);
0708             d->dbPathEdit->setEnabled(false);
0709             d->dbPathLabel->setText(d->dbPathLabel->text() +
0710                                     i18n("This path was set as a command line"
0711                                          "option (--database-directory)."));
0712         }
0713     }
0714 
0715 #ifdef HAVE_MYSQLSUPPORT
0716 
0717 #   ifdef HAVE_INTERNALMYSQL
0718 
0719     else if ((d->orgPrms.databaseType == DbEngineParameters::MySQLDatabaseType()) && d->orgPrms.internalServer)
0720     {
0721         d->dbPathEdit->setFileDlgPath(d->orgPrms.internalServerPath());
0722         d->dbType->setCurrentIndex(d->dbTypeMap[MysqlInternal]);
0723         d->mysqlUpgradeBin.setup(QFileInfo(d->orgPrms.internalServerMysqlUpgradeCmd).absoluteFilePath());
0724         d->mysqlServerBin.setup(QFileInfo(d->orgPrms.internalServerMysqlServerCmd).absoluteFilePath());
0725         d->mysqlAdminBin.setup(QFileInfo(d->orgPrms.internalServerMysqlAdminCmd).absoluteFilePath());
0726         d->mysqlInitBin.setup(QFileInfo(d->orgPrms.internalServerMysqlInitCmd).absoluteFilePath());
0727         d->dbBinariesWidget->allBinariesFound();
0728         d->walModeCheck->setChecked(false);
0729         slotResetMysqlServerDBNames();
0730     }
0731 
0732 #   endif
0733 
0734     else
0735     {
0736         d->dbType->setCurrentIndex(d->dbTypeMap[MysqlServer]);
0737         d->dbNameCore->setText(d->orgPrms.databaseNameCore);
0738         d->dbNameThumbs->setFileDlgPath(d->orgPrms.databaseNameThumbnails);
0739         d->dbNameFace->setText(d->orgPrms.databaseNameFace);
0740         d->dbNameSimilarity->setText(d->orgPrms.databaseNameSimilarity);
0741         d->hostName->setText(d->orgPrms.hostName);
0742         d->hostPort->setValue((d->orgPrms.port == -1) ? 3306 : d->orgPrms.port);
0743         d->connectOpts->setText(d->orgPrms.connectOptions);
0744         d->userName->setText(d->orgPrms.userName);
0745         d->password->setText(d->orgPrms.password);
0746         d->walModeCheck->setChecked(false);
0747     }
0748 
0749 #endif
0750 
0751     slotHandleDBTypeIndexChanged(d->dbType->currentIndex());
0752 }
0753 
0754 DbEngineParameters DatabaseSettingsWidget::getDbEngineParameters() const
0755 {
0756     DbEngineParameters prm;
0757 
0758     switch (databaseType())
0759     {
0760         case SQlite:
0761         {
0762             prm         = DbEngineParameters::parametersForSQLiteDefaultFile(databasePath());
0763             prm.walMode = d->walModeCheck->isChecked();
0764             break;
0765         }
0766 
0767         case MysqlInternal:
0768         {
0769             prm = DbEngineParameters::defaultParameters(databaseBackend());
0770             prm.setInternalServerPath(databasePath());
0771             prm.internalServerMysqlUpgradeCmd = d->mysqlUpgradeBin.path();
0772             prm.internalServerMysqlServerCmd  = d->mysqlServerBin.path();
0773             prm.internalServerMysqlAdminCmd   = d->mysqlAdminBin.path();
0774             prm.internalServerMysqlInitCmd    = d->mysqlInitBin.path();
0775             break;
0776         }
0777 
0778         default: // MysqlServer
0779         {
0780             prm.internalServer         = false;
0781             prm.databaseType           = databaseBackend();
0782             prm.databaseNameCore       = d->dbNameCore->text();
0783             prm.databaseNameThumbnails = d->dbNameThumbs->fileDlgPath();
0784             prm.databaseNameFace       = d->dbNameFace->text();
0785             prm.databaseNameSimilarity = d->dbNameSimilarity->text();
0786             prm.connectOptions         = d->connectOpts->text();
0787             prm.hostName               = d->hostName->text();
0788             prm.port                   = d->hostPort->value();
0789             prm.userName               = d->userName->text();
0790             prm.password               = d->password->text();
0791             break;
0792         }
0793     }
0794 
0795     return prm;
0796 }
0797 
0798 void DatabaseSettingsWidget::slotDatabasePathEditedDelayed()
0799 {
0800     QTimer::singleShot(300, this, SLOT(slotDatabasePathEdited()));
0801 }
0802 
0803 void DatabaseSettingsWidget::slotDatabasePathEdited()
0804 {
0805     QString newPath = databasePath();
0806 
0807 #ifndef Q_OS_WIN
0808 
0809     if (!newPath.isEmpty() && !QDir::isAbsolutePath(newPath))
0810     {
0811         d->dbPathEdit->setFileDlgPath(QDir::homePath() + QLatin1Char('/') + newPath);
0812     }
0813 
0814 #endif
0815 
0816     d->dbPathEdit->setFileDlgPath(newPath);
0817 }
0818 
0819 bool DatabaseSettingsWidget::checkDatabaseSettings()
0820 {
0821     switch (databaseType())
0822     {
0823         case SQlite:
0824         {
0825             return checkDatabasePath();
0826         }
0827 
0828         case MysqlInternal:
0829         {
0830             if (!checkDatabasePath())
0831             {
0832                 return false;
0833             }
0834 
0835             if (!d->dbBinariesWidget->allBinariesFound())
0836             {
0837                 return false;
0838             }
0839 
0840             return true;
0841         }
0842 
0843         default:  // MysqlServer
0844         {
0845             QString error;
0846 
0847             if (!checkMysqlServerDbNamesConfig(error))
0848             {
0849                 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Database Configuration"),
0850                                       i18n("The database names configuration is not valid. Error is <br/><p>%1</p><br/>"
0851                                            "Please check your configuration.",
0852                                            error));
0853                 return false;
0854             }
0855 
0856             if (!checkMysqlServerConnection(error))
0857             {
0858                 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Database Connection Test"),
0859                                       i18n("Testing database connection has failed with error<br/><p>%1</p><br/>"
0860                                            "Please check your configuration.",
0861                                            error));
0862                 return false;
0863             }
0864         }
0865     }
0866 
0867     return true;
0868 }
0869 
0870 bool DatabaseSettingsWidget::checkDatabasePath()
0871 {
0872     QString dbFolder = databasePath();
0873     qCDebug(DIGIKAM_DATABASE_LOG) << "Database directory is : " << dbFolder;
0874 
0875     if (dbFolder.isEmpty())
0876     {
0877         QMessageBox::information(qApp->activeWindow(), qApp->applicationName(),
0878                                 i18n("You must select a folder for digiKam to "
0879                                      "store information and metadata in a database file."));
0880         return false;
0881     }
0882 
0883     QDir targetPath(dbFolder);
0884 
0885     if (!targetPath.exists())
0886     {
0887         int rc = QMessageBox::question(qApp->activeWindow(), i18nc("@title:window", "Create Database Folder?"),
0888                                     i18n("<p>The folder to put your database in does not seem to exist:</p>"
0889                                          "<p><b>%1</b></p>"
0890                                          "Would you like digiKam to create it for you?", dbFolder));
0891 
0892         if (rc == QMessageBox::No)
0893         {
0894             return false;
0895         }
0896 
0897         if (!targetPath.mkpath(dbFolder))
0898         {
0899             QMessageBox::information(qApp->activeWindow(), i18nc("@title:window", "Create Database Folder Failed"),
0900                                     i18n("<p>digiKam could not create the folder to host your database file.\n"
0901                                          "Please select a different location.</p>"
0902                                          "<p><b>%1</b></p>", dbFolder));
0903             return false;
0904         }
0905     }
0906 
0907     QFileInfo path(dbFolder);
0908 
0909 #ifdef Q_OS_WIN
0910 
0911     // Work around bug #189168
0912 
0913     QTemporaryFile temp;
0914     temp.setFileTemplate(path.filePath() + QLatin1String("/XXXXXX"));
0915 
0916     if (!temp.open())
0917 
0918 #else
0919 
0920     if (!path.isWritable())
0921 
0922 #endif
0923 
0924     {
0925         QMessageBox::information(qApp->activeWindow(), i18nc("@title:window", "No Database Write Access"),
0926                                  i18n("<p>You do not seem to have write access "
0927                                       "for the folder to host the database file.<br/>"
0928                                       "Please select a different location.</p>"
0929                                       "<p><b>%1</b></p>", dbFolder));
0930         return false;
0931     }
0932 
0933     return true;
0934 }
0935 
0936 bool DatabaseSettingsWidget::isNotEqualToThumbName(const QString& name)
0937 {
0938     QFileInfo thumbDB(d->dbNameThumbs->fileDlgPath());
0939     bool isDir = (thumbDB.exists() && thumbDB.isDir() && thumbDB.isAbsolute());
0940 
0941     return (!isDir && (d->dbNameThumbs->fileDlgPath() !=  name));
0942 }
0943 
0944 } // namespace Digikam
0945 
0946 #include "moc_dbsettingswidget.cpp"