diff --git a/restruct.pro b/restruct.pro --- a/restruct.pro +++ b/restruct.pro @@ -1,48 +1,47 @@ ###################################################################### # Project started 11.01.2008 at 13:10 ###################################################################### QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets concurrent TEMPLATE = app TARGET = restruct DESTDIR = . BASEDIR = $$dirname(PWD) INCLUDEPATH += . ./src $$BASEDIR/include -PRECOMPILED_HEADER = ./src/stable.h DEFINES += KTOOLS_STATIC RESOURCES += resource/project.qrc SOURCES += src/restruct.cpp \ src/mainwindow.cpp \ src/updater.cpp \ src/sqlerrordlg.cpp HEADERS += src/mainwindow.h \ src/updater.h \ src/psettings.h \ src/sqlerrordlg.h LIBS += -L$$BASEDIR/lib -lktools -lpq win32 { RC_FILE = resource/project.rc LIBS += -luuid -loleaut32 -lgdi32 -luser32 } win32-msvc* { CONFIG(debug, debug|release) { QMAKE_LFLAGS *= /NODEFAULTLIB:msvcrt LIBS = $$replace(LIBS,-lktools,-lktoolsd) } DEFINES += _CRT_SECURE_NO_WARNINGS } #unix { # SOURCES += src/ # HEADERS += src/ #} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,451 +1,462 @@ -#include "stable.h" #include "mainwindow.h" #include "updater.h" #include "psettings.h" #include "sqlerrordlg.h" #include #include #include #include #ifdef Q_OS_WIN32 #include #endif - +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- MainWindow::MainWindow( QWidget* parent, Qt::WindowFlags flags ) : QDialog( parent, flags ) , _exitCode( 0 ) , _actionEventType( QEvent::User ) , _errorCount( 0 ) , _updater( new DatabaseUpdater ) { setWindowTitle( QString::fromUtf8( "Создание/обновление базы данных" ) ); setWindowFlags( windowFlags() & ~Qt::WindowContextHelpButtonHint ); setSizeGripEnabled( true ); QHBoxLayout* mainLayout = new QHBoxLayout; QVBoxLayout* leftLayout = new QVBoxLayout; QVBoxLayout* rightLayout = new QVBoxLayout; QHBoxLayout* bottomRightLayout = new QHBoxLayout; leftLayout->setContentsMargins( 0, 2* style()->pixelMetric( QStyle::PM_LayoutTopMargin ), style()->pixelMetric( QStyle::PM_LayoutRightMargin ), 0 ); QLabel* picture = new QLabel; picture->setPixmap( QPixmap( ":/picture.png" ) ); //кнопки, скрытые до завершения процесса _btnSave = new QPushButton( QString::fromUtf8( "Сохранить" ) ); _btnClose = new QPushButton( QString::fromUtf8( "Закрыть" ) ); _btnSave->setVisible( false ); _btnClose->setVisible( false ); _btnClose->setDefault( true ); leftLayout->addWidget( picture ); leftLayout->addStretch(); leftLayout->addWidget( _btnSave ); leftLayout->addWidget( _btnClose ); QLabel* label = new QLabel( QString::fromUtf8( "Протокол работы" ) ); rightLayout->addWidget( label ); _protocol = new QTextEdit(); _protocol->setReadOnly( true ); label->setBuddy( _protocol ); rightLayout->addWidget( _protocol ); //индикаторы выполнения _progress = new QProgressBar; _progress->setTextVisible( false ); _ai = new KActivityIndicator( KActivityIndicator::Gray ); _ai->setVisible( false ); bottomRightLayout->addWidget( _progress ); bottomRightLayout->addWidget( _ai ); rightLayout->addLayout( bottomRightLayout ); mainLayout->addLayout( leftLayout ); mainLayout->addLayout( rightLayout ); setLayout( mainLayout ); //нажатие Esc не соединяется со слотом, чтобы окно не закрывалось new QShortcut( QKeySequence(Qt::Key_Escape), this ); //соединить сигналы со слотами connect( _btnSave, SIGNAL(clicked()), this, SLOT(saveProtocol()) ); connect( _btnClose, SIGNAL(clicked()), this, SLOT(closeDialog()) ); //получить незадействованный тип события _actionEventType = QEvent::registerEventType(); } MainWindow::~MainWindow() { delete _updater; } //--------------------------------------------------------------------------- void MainWindow::showEvent( QShowEvent* a_event ) { QDialog::showEvent( a_event ); qApp->postEvent( this, new QEvent( QEvent::Type(_actionEventType) ) ); } //--------------------------------------------------------------------------- bool MainWindow::event( QEvent* a_event ) { if ( a_event->type() == _actionEventType ) { startExecution(); a_event->accept(); return true; } return QWidget::event( a_event ); } //--------------------------------------------------------------------------- void MainWindow::closeEvent( QCloseEvent* a_event ) { if ( _futureWatcher.isRunning() ) a_event->ignore(); else a_event->accept(); } //--------------------------------------------------------------------------- void MainWindow::startExecution() { if ( _futureWatcher.isRunning() ) return; ProgramSettings pset; processArguments( pset ); logon( pset ); connect( _updater, SIGNAL(progress(int)), _progress, SLOT(setValue(int)) ); connect( _updater, SIGNAL(error(const QString&)), this, SLOT(error(const QString&)) ); connect( _updater, SIGNAL(message(const QString&)), this, SLOT(message(const QString&)) ); connect( _updater, SIGNAL(sqlError(const QString&,const QString&,const QString&)), this, SLOT(sqlError(const QString&,const QString&,const QString&)) ); connect( _updater, SIGNAL(logConnectionParameters(const QString&,const QString&,const QString&)), this, SLOT(logConnectionParameters(const QString&,const QString&,const QString&)) ); connect( &_futureWatcher, SIGNAL(finished()), this, SLOT(finishExecution()) ); _ai->setVisible( true ); _ai->start(); //int rc = _updater.run( pset ); QFuture rc = QtConcurrent::run( _updater, &DatabaseUpdater::run, pset ); _futureWatcher.setFuture( rc ); } //--------------------------------------------------------------------------- void MainWindow::finishExecution() { _ai->stop(); _ai->setVisible( false ); //проверяем число ошибок, т.к. при выполнении без транзакции run() всегда возвращает 0 if ( _errorCount == 0 ) { int before = _updater->revisionBefore(); int after = _updater->revisionAfter(); if ( before == after ) message( QString::fromUtf8( "База данных в актуальном состоянии." ) ); else if ( before == 0 ) message( QString::fromUtf8( "База данных создана и обновлена до версии %1." ).arg( after ) ); else message( QString::fromUtf8( "База данных версии %1 обновлена до версии %2." ). arg( before ).arg( after ) ); _exitCode = (before & 0xFFFF) | ((after & 0xFFFF) << 16); closeDialog(); //при успешном завершении, то окно закрывается автоматически } else { _btnClose->show(); _btnSave->show(); _btnClose->setFocus(); message( QString::fromUtf8( "Выполнение прервано в результате ошибки." "
База данных не изменилась.") ); _exitCode = -1; } } //--------------------------------------------------------------------------- int MainWindow::executeAction() { ProgramSettings pset; processArguments( pset ); logon( pset ); _ai->start(); DatabaseUpdater updater; connect( &updater, SIGNAL(progress(int)), _progress, SLOT(setValue(int)) ); connect( &updater, SIGNAL(error(const QString&)), this, SLOT(error(const QString&)) ); connect( &updater, SIGNAL(message(const QString&)), this, SLOT(message(const QString&)) ); connect( &updater, SIGNAL(sqlError(const QString&,const QString&,const QString&)), this, SLOT(sqlError(const QString&,const QString&,const QString&)) ); connect( &updater, SIGNAL(logConnectionParameters(const QString&,const QString&,const QString&)), this, SLOT(logConnectionParameters(const QString&,const QString&,const QString&)) ); int rc = updater.run( pset ); //проверяем число ошибок, т.к. при выполнении без транзакции run() всегда возвращает 0 if ( _errorCount == 0 ) { int before = updater.revisionBefore(); int after = updater.revisionAfter(); if ( before == after ) message( QString::fromUtf8( "База данных в актуальном состоянии." ) ); else if ( before == 0 ) message( QString::fromUtf8( "База данных создана и обновлена до версии %1." ).arg( after ) ); else message( QString::fromUtf8( "База данных версии %1 обновлена до версии %2." ). arg( before ).arg( after ) ); rc = (before & 0xFFFF) | ((after & 0xFFFF) << 16); } else { _btnClose->show(); _btnSave->show(); _btnClose->setFocus(); message( QString::fromUtf8( "Выполнение прервано в результате ошибки." "
База данных не изменилась.") ); rc = -1; } return rc; } void MainWindow::processArguments( ProgramSettings& a_pset ) { QString USERNAME_PARAM( "username" ); QString PASSWORD_PARAM( "password" ); QString DATABASE_PARAM( "database" ); QString DROPDB_PARAM( "dropdb" ); QString HOST_PARAM( "host" ); QString PORT_PARAM( "port" ); QString FILE_PARAM( "file" ); //заполнить параметры default значениями QHash params; params.insert( USERNAME_PARAM, QString() ); params.insert( PASSWORD_PARAM, QString() ); params.insert( DATABASE_PARAM, QString() ); params.insert( DROPDB_PARAM, QString() ); params.insert( HOST_PARAM, QString() ); params.insert( PORT_PARAM, QString() ); params.insert( FILE_PARAM, QString() ); //чтение аргументов QString key; QString curArg; QStringListIterator args( QApplication::arguments() ); args.next(); //пропускаем первый параметр - имя программы while ( args.hasNext() ) { curArg = args.next(); if ( curArg.startsWith( "--" ) ) {//обработка длинного параметра curArg.remove( 0, 2 ); QMutableHashIterator option( params ); while ( option.hasNext() ) { option.next(); key = option.key(); if ( curArg.startsWith( key ) && curArg.at( key.size() ) == '=' ) option.setValue( curArg.right( curArg.size() - key.size() - 1 ) ); } key.clear(); } else if ( curArg.at( 0 ) == QChar('-') ) {//обработка короткого параметра key.clear(); switch ( curArg.at( 1 ).toLatin1() ) { case 'U': key = USERNAME_PARAM; break; case 'd': key = DATABASE_PARAM; break; case 'f': key = FILE_PARAM; break; case 'h': key = HOST_PARAM; break; case 'p': key = PORT_PARAM; break; case 'W': key = PASSWORD_PARAM; break; case '0': params[ DROPDB_PARAM ] = '1'; break; } } else {//обработка одиночного параметра if ( key.isEmpty() ) key = FILE_PARAM; params[ key ] = curArg; key.clear(); } } //записать прочитанные значения в структуру ProgramSettings a_pset.username = params.value( USERNAME_PARAM ); a_pset.password = params.value( PASSWORD_PARAM ); a_pset.database = params.value( DATABASE_PARAM ); a_pset.host = params.value( HOST_PARAM ); a_pset.port = params.value( PORT_PARAM ); a_pset.controlFile = params.value( FILE_PARAM ); a_pset.dropdb = !params.value( DROPDB_PARAM ).isEmpty(); } //--------------------------------------------------------------------------- void MainWindow::logon( ProgramSettings& a_pset ) { if ( !a_pset.host.isEmpty() || !a_pset.username.isEmpty() ) return; #if defined(Q_OS_WIN) KLogon* logon = KLogon::create(); if ( !logon ) return; QString defaultDSN; HKEY k; DWORD cb = 0; QString valuename( "System Directory" ); if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\irs\\b03"), 0, KEY_QUERY_VALUE, &k ) == ERROR_SUCCESS && RegQueryValueEx( k, LPCTSTR(valuename.constData()), 0, 0, 0, &cb ) == ERROR_SUCCESS && cb > 0 ) { QString systemdir; systemdir.resize( (cb / sizeof(QChar)) - 1 ); RegQueryValueEx( k, LPCTSTR(valuename.constData()), 0, 0, LPBYTE(systemdir.data()), &cb ); RegCloseKey( k ); KSettings systemini( systemdir + "/system.ini" ); systemini.beginGroup( "Database" ); defaultDSN = systemini.value( "DefaultDSN" ).toString(); } QString username = QString::fromLocal8Bit( qgetenv( "USERNAME" ) ); logon->setParent( winId() ); logon->setUsername( username ); if ( !defaultDSN.isNull() ) logon->setDSN( defaultDSN ); if ( !logon->execute() ) emit message( QString::fromUtf8( "Отказ от ввода имени и пароля пользователя" ) ); else { a_pset.host = logon->host(); a_pset.username = logon->username(); a_pset.password = logon->password(); } delete logon; #else QString username = QString::fromLocal8Bit( getenv( "USER" ) ); #endif } //--------------------------------------------------------------------------- void MainWindow::logConnectionParameters( const QString& a_host, const QString& a_database, const QString& a_username ) { message( QString::fromUtf8( "Подключение к базе данных
" "  Сервер: %1
  База данных: %2
" "  Пользователь: %3").arg( a_host, a_database, a_username ) ); } //--------------------------------------------------------------------------- void MainWindow::sqlError( const QString& a_dbError, const QString& a_commandDescription, const QString& a_command ) { QString errorHtml; if ( !a_commandDescription.isEmpty() ) errorHtml.append( QString::fromUtf8( "Операция:
" ) ). append( a_commandDescription ).append( "
" ); QString dbError = a_dbError; errorHtml.append( QString::fromUtf8( "Сообщение об ошибке:
" ) ).
             append( dbError.trimmed().replace( '\n', "
" ) ).append( "
"); if ( !a_command.isEmpty() ) errorHtml.append( QString::fromUtf8( "Текст команды:
" ) ).
                 append( a_command ).append( "
" ); errorHtmlToLog( errorHtml ); } //--------------------------------------------------------------------------- void MainWindow::error( const QString& a_error ) { QString errorHtml = a_error; errorHtmlToLog( errorHtml.replace( '<', "<" ).replace( '>', ">" ).replace( '\n', "
" ) ); } //--------------------------------------------------------------------------- void MainWindow::errorHtmlToLog( const QString& a_errorHtml ) { QString errorHtml = a_errorHtml; QTextCursor cursor = _protocol->textCursor(); cursor.insertHtml( QString::fromUtf8( "Ошибка
" ) ); cursor.insertHtml( errorHtml ); cursor.insertHtml( QString::fromUtf8( "
" ) ); _protocol->moveCursor( QTextCursor::End ); ++_errorCount; } //--------------------------------------------------------------------------- void MainWindow::message( const QString& a_message ) { QString message = a_message; _protocol->textCursor().insertHtml( message.replace( QChar('\n'), "
" ).append( "
" ) ); _protocol->moveCursor( QTextCursor::End ); } //--------------------------------------------------------------------------- void MainWindow::closeDialog() { QDialog::done( _exitCode ); } //--------------------------------------------------------------------------- void MainWindow::saveProtocol() { QString filename = QFileDialog::getSaveFileName( this, QString::fromUtf8( "Сохранить протокол в файл…" ), QString(), QString::fromUtf8( "Текстовые файлы (*.txt);;Все файлы (*.*)" ) ); if ( !filename.isEmpty() ) { QFile outf( filename ); if ( outf.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) { QTextStream os( &outf ); os.setGenerateByteOrderMark( true ); os.setCodec( "UTF-8" ); os << _protocol->toPlainText(); } } } //!главное окно приложения diff --git a/src/restruct.cpp b/src/restruct.cpp --- a/src/restruct.cpp +++ b/src/restruct.cpp @@ -1,55 +1,57 @@ -#include "stable.h" #include "mainwindow.h" +#include +#include +#include #ifdef Q_OS_WIN32 #include #endif int main( int argc, char* argv[] ) { //int width = 504; //int height = 262; int width = 679; int height = 420; QApplication app( argc, argv ); //загрузка русской локализации библиотеки Qt QTranslator translator; translator.load( QString("qt_ru") ); app.installTranslator(&translator); //создание главного окна MainWindow window; #ifdef Q_OS_WIN32 //масштабирование для случая увеличенного шрифта HWND hwnd = (HWND)app.desktop()->winId(); int dpi = GetDeviceCaps( GetDC(hwnd), LOGPIXELSY ); if ( dpi != 96 ) { width = (width * dpi) / 96; height = (height * dpi) / 96; } //шрифт окна такой же, как в системе подписи к иконкам LOGFONTW lf; SystemParametersInfoW( SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0 ); int fh = qAbs( (lf.lfHeight * 72) / dpi ); QString fn( (const QChar*)lf.lfFaceName, wcslen(lf.lfFaceName) ); window.setStyleSheet( QString( "*{ font-family: %1; font-size: %2pt; }" ).arg( fn ).arg( fh ) ); #endif //перемещение главного окна в центр экрана window.setMinimumSize( width, height ); QRect wg = window.geometry(); wg.setSize( window.minimumSize() ); wg.moveCenter( qApp->desktop()->screenGeometry().center() ); window.setGeometry( wg ); int exitCode = window.exec(); return exitCode; } //!приложение restruct diff --git a/src/sqlerrordlg.cpp b/src/sqlerrordlg.cpp --- a/src/sqlerrordlg.cpp +++ b/src/sqlerrordlg.cpp @@ -1,45 +1,49 @@ -#include "stable.h" +#include +#include +#include +#include +#include #include "sqlerrordlg.h" //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- SqlErrorDialog::SqlErrorDialog( QWidget* parent, Qt::WindowFlags flags ) : QDialog( parent, flags ) { setWindowTitle( QString::fromUtf8( "Список ошибок" ) ); setWindowFlags( windowFlags() & ~Qt::WindowContextHelpButtonHint ); setSizeGripEnabled( true ); setMinimumSize( QSize( 512, 428 ) ); QVBoxLayout* mainLayout = new QVBoxLayout( this ); QHBoxLayout* layout = new QHBoxLayout; mainLayout->addLayout( layout ); QLabel* label = new QLabel( QString( "Во время выполнения команд произошли\nследующие ошибки:" ) ); layout->addWidget( label ); layout->addStretch(); _btnClose = new QPushButton( QString( "Закрыть" ) ); _btnClose->setDefault( true ); layout->addWidget( _btnClose ); _errorLog = new QTextEdit; _errorLog->setReadOnly( true ); mainLayout->addWidget( _errorLog ); connect( _btnClose, SIGNAL(clicked()), this, SLOT(close()) ); } SqlErrorDialog::~SqlErrorDialog() { } void SqlErrorDialog::setLogText( const QString& a_text ) { _errorLog->clear(); _errorLog->setHtml( a_text ); } //!главное окно приложения diff --git a/src/stable.h b/src/stable.h deleted file mode 100644 --- a/src/stable.h +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include diff --git a/src/stable.h.cpp b/src/stable.h.cpp deleted file mode 100644 --- a/src/stable.h.cpp +++ /dev/null @@ -1,16 +0,0 @@ -/*-------------------------------------------------------------------- -* Precompiled header source file used by Visual Studio.NET to generate -* the .pch file. -* -* Due to issues with the dependencies checker within the IDE, it -* sometimes fails to recompile the PCH file, if we force the IDE to -* create the PCH file directly from the header file. -* -* This file is auto-generated by qmake since no PRECOMPILED_SOURCE was -* specified, and is used as the common stdafx.cpp. The file is only -* generated when creating .vcproj project files, and is not used for -* command line compilations by nmake. -* -* WARNING: All changes made in this file will be lost. ---------------------------------------------------------------------*/ -#include "stable.h" diff --git a/src/updater.cpp b/src/updater.cpp --- a/src/updater.cpp +++ b/src/updater.cpp @@ -1,875 +1,877 @@ -#include "stable.h" #include "updater.h" #include "psettings.h" #include #include #include +#include +#include +#include #include DatabaseUpdater::DatabaseUpdater() : QObject() , _pset(0) , _revisionBefore(0) , _revisionAfter(0) { } DatabaseUpdater::~DatabaseUpdater() { delete _pset; } int DatabaseUpdater::run( ProgramSettings& a_pset ) { _pset = new ProgramSettings( a_pset ); if ( !checkArguments() ) return -1; if ( !readConfig() ) return -1; if ( !loadScriptsFromResource() ) return -1; if ( !runScripts() ) return -1; return 0; } //--------------------------------------------------------------------------- bool DatabaseUpdater::checkArguments() { if ( _pset->controlFile.isEmpty() ) { emit error( QString::fromUtf8( "Не задан файл конфигурации." ) ); return false; } //если не задано полное имя файла конфигурации, то искать его в текущем каталоге QString filename = _pset->controlFile; if ( !QDir::isAbsolutePath( filename ) ) filename = QString( "%1/%2" ).arg( QDir::currentPath() ).arg( _pset->controlFile ); //проверить наличие файла if ( !QFile::exists( filename ) ) { emit error( QString::fromUtf8( "Не найден файл конфигурации\n" ) + QDir::toNativeSeparators( filename ) ); return false; } _pset->controlFile = filename; return true; } //--------------------------------------------------------------------------- bool DatabaseUpdater::readConfig() { //открываем входной файл QFile controlFile( _pset->controlFile ); if ( !controlFile.open( QIODevice::ReadOnly ) ) { emit error( QString::fromUtf8( "Сбой при загрузке файла конфигурации:\n" "Невозможно открыть для чтения файл %1" ).arg( QDir::toNativeSeparators( _pset->controlFile ) ) ); return false; } //парсим входной файл QXmlStreamReader xml; xml.setDevice( &controlFile ); emit message( QString::fromUtf8( "Загрузка конфигурации из файла\n%1" ). arg( QDir::toNativeSeparators( _pset->controlFile ) ) ); //идём по элементам документа while ( !xml.atEnd() ) { xml.readNext(); if ( xml.isStartElement() && xml.name() == "package" ) if ( !readPackage( xml ) ) return false; } if ( xml.hasError() ) { emit error( QString::fromUtf8( "Сбой при загрузке файла конфигурации:\n" "Ошибка: %1, строка %2, позиция %3"). arg( xml.errorString() ).arg( xml.lineNumber() ).arg( xml.columnNumber() ) ); return false; } controlFile.close(); //найти файл с расширением pkginfo и именем как у входного файла, если такого нет, //то использовать первый попавшийся файл с расширением pkginfo; //прочитать из него идентификатор пакета и имя базы данных; //если файла нет, то и не надо QFileInfo fi( controlFile ); QStringList files = QDir( QFileInfo( controlFile ).path(), "*.pkginfo", QDir::NoSort, QDir::Files ).entryList(); if ( !files.isEmpty() ) { QString pkgfile = QFileInfo( controlFile ).completeBaseName() + ".pkginfo"; if ( !files.contains( pkgfile, Qt::CaseInsensitive ) ) pkgfile = files.at( 0 ); pkgfile = QFileInfo( controlFile ).path() + '/' + pkgfile; emit message( QString::fromUtf8( "Загрузка конфигурации из файла\n%1" ). arg( QDir::toNativeSeparators( pkgfile ) ) ); KSettings pkginfo( pkgfile ); pkginfo.beginGroup( "package" ); QString s = pkginfo.value( "dbname" ).toString(); if ( !s.isEmpty() && _pset->database.isEmpty() ) _pset->database = s; QStringList ver = pkginfo.value( "version" ).toString().split( QChar('.') ); if ( ver.size() > 1 ) { s = ver.at( 1 ); bool ok; int i = s.toInt( &ok ); if ( ok ) _pset->dbVersion = i; } _pset->packageId = pkginfo.value( "id" ).toString(); } //после загрузки конфигурации из файлов должно быть определено имя БД if ( _pset->database.isEmpty() ) { emit error( QString::fromUtf8( "Не задано имя базы данных" ) ); return false; } return true; } //--------------------------------------------------------------------------- bool DatabaseUpdater::readPackage( QXmlStreamReader& a_xml ) { const QXmlStreamAttributes& packageAttrs = a_xml.attributes(); QString packageId = packageAttrs.value( "id" ).toString(); QString uriAttr = packageAttrs.value( "uri" ).toString(); bool uri = !((uriAttr == "no") || (uriAttr == "нет")); Package* package = new Package( packageId, uri ); _packages.append( package ); //собираем информацию о скриптах данного пакета const QString SCRIPT_NODE( "script" ); const QString REVISION_ATTR( "revision" ); const QString TRANSACTION_ATTR( "transaction" ); const QString COMMENT_ATTR( "comment" ); const QString SCRIPT_ATTR( "file" ); const QString URI_DATA_ATTR( "udata" ); const QString NO_REQUIRED_ATTR = QString::fromUtf8( "Неправильное содержание файла конфигурации:\n" "Не указан обязательный атрибут «%1» пакета «%2»" ); while ( !a_xml.atEnd() ) { a_xml.readNext(); if ( a_xml.isStartElement() && a_xml.name() == SCRIPT_NODE ) { const QXmlStreamAttributes& attrs = a_xml.attributes(); //проверка наличия обязательных атрибутов if ( !attrs.hasAttribute( REVISION_ATTR ) ) { emit error( NO_REQUIRED_ATTR.arg( REVISION_ATTR ).arg( packageId ) ); return false; } if ( !attrs.hasAttribute( SCRIPT_ATTR ) ) { emit error( NO_REQUIRED_ATTR.arg( SCRIPT_ATTR ).arg( packageId ) ); return false; } //проверка правильности указания атрибутов bool ok; int revision = attrs.value( REVISION_ATTR ).toString().toInt( &ok ); if ( !ok ) { emit error( QString::fromUtf8( "Атрибут «revision» пакета «%1» должен быть числом" ). arg( packageId ) ); return false; } //заносим информацию о скрипте в список скриптов пакета QString file = attrs.value( SCRIPT_ATTR ).toString(); QString uriScript = attrs.value( URI_DATA_ATTR ).toString(); QString comment = attrs.value( COMMENT_ATTR ).toString(); QString strans = attrs.value( TRANSACTION_ATTR ).toString(); bool transaction = !((strans == "0") || (strans.compare( "no", Qt::CaseInsensitive ) == 0) || (strans.compare( "нет", Qt::CaseInsensitive ) == 0)); DatabaseScript* script = new DatabaseScript( revision, transaction, file, uriScript, comment ); package->addScript( script ); } } //упорядочить скрипты по возрастанию номеров ревизий package->sortScripts(); return true; } //--------------------------------------------------------------------------- bool DatabaseUpdater::runScripts() { //определение пакета, скрипты которого будут выполняться Package* package = 0; QListIterator it( _packages ); while ( it.hasNext() ) { package = it.next(); if ( package->id() == _pset->packageId ) break; else package = 0; } if ( !package ) { emit error( QString::fromUtf8( "Для пакета «%1» не задано ни одного " "сценария создания базы данных" ).arg( _pset->packageId ) ); return false; } //проверка наличия файлов со скриптами на диске QStringList paths; QFileInfo fi( _pset->controlFile ); paths << fi.canonicalPath() << fi.canonicalPath() + "/script" << QDir::currentPath(); DatabaseScript* script = 0; QListIterator it2( package->scripts() ); while ( it2.hasNext() ) { script = it2.next(); if ( !script->findScript( paths ) ) { emit error( QString::fromUtf8( "Не найден файл сценария создания " "базы данных %1" ).arg( script->script() ) ); return false; } if ( package->uri() && !script->findUriScript( paths ) ) { emit error( QString::fromUtf8( "Не найден файл условно-реальной " "информации %1" ).arg( script->uriScript() ) ); return false; } } QString curDate = QDate::currentDate().toString( Qt::ISODate ); SqlProcessor proc; SqlProcessor uriProc; emit message( QString::fromUtf8( "Подключение к серверу баз данных" ) ); //пока идут служебные подключения к БД и запросы, подключаем только сигнал error connect( &proc, SIGNAL(error(const QString&,const QString&,const QString&)), this, SIGNAL(sqlError(const QString&,const QString&,const QString&)) ); connect( &uriProc, SIGNAL(error(const QString&,const QString&,const QString&)), this, SIGNAL(sqlError(const QString&,const QString&,const QString&)) ); qApp->processEvents( QEventLoop::ExcludeUserInputEvents ); //---- template1 ---- //попытка подключения к template1 и получение списка таблиц if ( !proc.connectdb( _pset->host, _pset->port, "template1", _pset->username, _pset->password ) ) return false; QStringList template1Tables = databaseTableList( proc ); int currentDatabaseRevision = 0; //БД нет или пустая //удаление БД если задано параметром при запуске программы bool dbExists = databaseExists( proc, _pset->database ); if ( dbExists && _pset->dropdb ) { emit message( QString::fromUtf8( "Удаление базы данных %1" ).arg( _pset->database ) ); qApp->processEvents( QEventLoop::ExcludeUserInputEvents ); if ( !dropDatabase( proc, _pset ) ) return false; dbExists = false; } //создание БД если она не существует if ( !dbExists ) { emit message( QString::fromUtf8( "Создание базы данных %1" ).arg( _pset->database ) ); qApp->processEvents( QEventLoop::ExcludeUserInputEvents ); if ( !createDatabase( proc, _pset ) ) return false; } QString uriDatabase = _pset->database + "_u"; if ( package->uri() ) { //удаление учебной БД если задано параметром при запуске программы bool uriDbExists = databaseExists( proc, uriDatabase ); if ( uriDbExists && _pset->dropdb ) { emit message( QString::fromUtf8( "Удаление учебной базы данных %1" ).arg( uriDatabase ) ); qApp->processEvents( QEventLoop::ExcludeUserInputEvents ); if ( !dropDatabase( proc, _pset, uriDatabase ) ) return false; uriDbExists = false; } //создание учебной БД если она не существует if ( !uriDbExists ) { emit message( QString::fromUtf8( "Создание учебной базы данных %1" ).arg( uriDatabase ) ); qApp->processEvents( QEventLoop::ExcludeUserInputEvents ); if ( !createDatabase( proc, _pset, uriDatabase ) ) return false; } } connect( &proc, SIGNAL(afterConnect(QString,QString,QString,QString,QString)), this, SLOT(afterConnect(QString,QString,QString,QString,QString)) ); //---- database ---- //подключение к созданной/существующей БД и получение списка таблиц if ( !proc.connectdb( _pset->host, _pset->port, _pset->database, _pset->username, _pset->password ) ) return false; QStringList databaseTables = databaseTableList( proc ); //подключение к учебной БД и получение списка таблиц QStringList uriDatabaseTables; if ( package->uri() ) { //---- database_u ---- if ( !uriProc.connectdb( _pset->host, _pset->port, uriDatabase, _pset->username, _pset->password ) ) return false; uriDatabaseTables = databaseTableList( uriProc ); } //считывание данных о версии БД из таблицы dm_version, //эта таблица существует только в основной БД, версия учебной должна совпадать bool legacyDatabase = false; proc.execSQL( "select relnatts from pg_class " "where relname='dm_version' and relkind='r'" ); KSimpleDataSet data = proc.result(); //если пустой результат запроса, значит таблицы dm_version нет в БД bool createDmVersion = data.size() == 0; //если таблица есть, то сравнить число её атрибутов с эталоном data.first(); if ( data.isValid() && data.value( 0 ).toInt() != DM_VERSION_FIELDS_COUNT ) { //не та структура, удаляем таблицу if ( !proc.execSQL( "drop table dm_version" ) ) return false; createDmVersion = true; } if ( createDmVersion ) { //БД уже существовала, а таблицы dm_version в ней нет, или не та структура if ( databaseTables != template1Tables ) { legacyDatabase = true; currentDatabaseRevision = 1; } emit message( QString::fromUtf8( "Создание таблицы изменений БД" ) ); proc.execSQL( "begin" ); if ( !proc.execute( _createDmVersionScript ) ) { emit error( QString::fromUtf8( "Сбой при создании таблицы изменений БД" ) ); return false; } if ( legacyDatabase ) { //запись информации о версии №1 в unversioned БД if ( !proc.execSQL( QString::fromUtf8( "insert into dm_version " "(revision,package_id,comment,gen_date) values " "(1,'%1','Изначальная версия','%2')" ).arg( package->id(), curDate ) ) ) return false; } proc.execSQL( "commit" ); } else { proc.execSQL( QString("select max(revision), count(*) from dm_version " "where package_id='%1'" ).arg( package->id() ) ); data = proc.result(); data.first(); if ( data.isValid() && (data.value( 1 ) > 0) ) currentDatabaseRevision = data.value( 0 ).toInt(); } //создание языка plpgsql if ( !createLanguagePlpgsql( proc, uriProc, package->uri() ) ) return false; _revisionAfter = _revisionBefore = currentDatabaseRevision; //выполнение необходимых скриптов connect( &proc, SIGNAL(progress(int)), this, SIGNAL(progress(int)) ); connect( &uriProc, SIGNAL(progress(int)), this, SIGNAL(progress(int)) ); QListIterator iter( package->scripts() ); while ( iter.hasNext() ) { script = iter.next(); int revision = script->revision(); bool transaction = script->transaction(); //если была найдена изначальная БД, то всё уже сделано для 1-й версии if ( (revision == 1) && legacyDatabase ) continue; //пропускаем уже установленные версии БД if ( revision <= currentDatabaseRevision ) continue; if ( revision == 1 ) emit message( QString::fromUtf8( "Создание базы данных версии %1" ).arg( revision ) ); else emit message( QString::fromUtf8( "Обновление базы данных до версии %1" ).arg( revision ) ); //подготовка скрипта к выполнению QList mainScript; QList globalsScript; prepareScripts( script->script(), mainScript, globalsScript ); //если в скрипте были команды создания глобальных объектов, то выполнить //их в отдельной транзакции if ( globalsScript.size() > 0 ) { proc.execSQL( "begin" ); if ( !proc.execute( globalsScript ) ) return false; proc.execSQL( "commit" ); } //выполнение скрипта if ( transaction ) proc.execSQL( "begin" ); if ( !proc.execute( mainScript ) ) return false; //выполнение скриптов в учебной БД, если задано её создание if ( package->uri() ) { if ( transaction ) uriProc.execSQL( "begin" ); if ( revision == 1 ) emit message( QString::fromUtf8( "Создание учебной базы данных версии %1" ).arg( revision ) ); else emit message( QString::fromUtf8( "Обновление учебной базы данных до версии %1" ).arg( revision ) ); if ( !uriProc.execute( mainScript ) ) { if ( transaction ) proc.execSQL( "rollback" ); return false; } if ( !script->uriScript().isEmpty() ) { emit message( QString::fromUtf8( "Загрузка условной информации в учебную базу данных" ) ); if ( !uriProc.execute( script->uriScript() ) ) { if ( transaction ) proc.execSQL( "rollback" ); return false; } } if ( !uriProc.execute( _setSearchPathScript ) ) return false; clearMaclabels( uriProc ); if ( transaction ) uriProc.execSQL( "commit" ); } //сброс параметра search_path, он мог быть изменён при выполнении скрипта if ( !proc.execute( _setSearchPathScript ) ) return false; //запись информации о версии в БД if ( !proc.execSQL( QString::fromUtf8( "insert into dm_version " "(revision,package_id,comment,gen_date) values " "(%1,'%2','%3','%4')" ).arg( revision ). arg( package->id() ).arg( script->comment() ). arg( curDate ) ) ) return false; //сброс мандатных меток, если они есть в БД clearMaclabels( proc ); if ( transaction ) proc.execSQL( "commit" ); _revisionAfter = revision; } return true; } //--------------------------------------------------------------------------- bool DatabaseUpdater::databaseExists( SqlProcessor& a_proc, const QString& a_dbname ) { a_proc.execSQL( QString( "select 1 from pg_database where datname='%1\'" ).arg(a_dbname) ); return a_proc.result().size() == 1; } //--------------------------------------------------------------------------- bool DatabaseUpdater::createDatabase( SqlProcessor& a_proc, ProgramSettings* a_pset, const QString& a_dbname ) { if ( a_dbname.isEmpty() && a_pset->database.isEmpty() ) return false; QString dbname = (a_dbname.isEmpty() ? a_pset->database : a_dbname ); //если уже подключены к указанной БД, то отключение if ( a_proc.database() == dbname ) a_proc.disconnectdb(); //если не подключены, то подключение к template1 if ( a_proc.database().isNull() ) { if ( !a_proc.connectdb( a_pset->host, a_pset->port, QString( "template1" ), a_pset->username, a_pset->password ) ) return false; } //если БД уже существует, то создавать не надо if ( databaseExists( a_proc, dbname ) ) return true; return a_proc.execSQL( QString( "create database \"%1\"" ).arg(dbname) ) && a_proc.commandStatus() == QString( "CREATE DATABASE" ); } //--------------------------------------------------------------------------- bool DatabaseUpdater::dropDatabase( SqlProcessor& a_proc, ProgramSettings* a_pset, const QString& a_dbname ) { if ( a_pset->database.isEmpty() ) return false; QString dbname = (a_dbname.isEmpty() ? a_pset->database : a_dbname ); //если уже подключены к указанной БД, то отключение if ( a_proc.database() == dbname ) a_proc.disconnectdb(); //если не подключены, то подключение к template1 if ( a_proc.database().isNull() ) { if ( !a_proc.connectdb( a_pset->host, a_pset->port, QString( "template1" ), a_pset->username, a_pset->password ) ) return false; } //если БД не существует, то удалять не надо if ( !databaseExists( a_proc, dbname ) ) return true; return a_proc.execSQL( QString( "drop database \"%1\"" ).arg(dbname) ) && a_proc.commandStatus() == QString( "DROP DATABASE" ); } //--------------------------------------------------------------------------- QStringList DatabaseUpdater::databaseTableList( SqlProcessor& a_proc ) { //получение списка таблиц QStringList result; if ( !a_proc.execSQL( "select tablename from pg_tables order by tablename" ) ) return result; KSimpleDataSet data = a_proc.result(); data.first(); while ( data.isValid() ) { result.append( data.value( 0 ) ); data.next(); } return result; } //--------------------------------------------------------------------------- bool DatabaseUpdater::loadScriptsFromResource() { QString errmess = QString::fromUtf8( "Сбой при чтении ресурсов программы" ); SqlProcessor proc; QFile resFile( ":/dm_version.sql" ); if ( !(resFile.open( QIODevice::ReadOnly ) ) ) { emit error( errmess ); return false; } _createDmVersionScript = proc.parse( resFile ); resFile.close(); resFile.setFileName( ":/create_helper_functions.sql"); if ( !(resFile.open( QIODevice::ReadOnly ) ) ) { emit error( errmess ); return false; } _createHelperFunctionsScript = proc.parse( resFile ); resFile.close(); resFile.setFileName( ":/drop_helper_functions.sql"); if ( !(resFile.open( QIODevice::ReadOnly ) ) ) { emit error( errmess ); return false; } _dropHelperFunctionsScript = proc.parse( resFile ); resFile.close(); resFile.setFileName( ":/set_search_path.sql"); if ( !(resFile.open( QIODevice::ReadOnly ) ) ) { emit error( errmess ); return false; } _setSearchPathScript = proc.parse( resFile ); return true; } //--------------------------------------------------------------------------- // Подготавливает скрипт, считанный из файла, к выполнению в БД. // Команды анализируются и разделяются на два скрипта — основной и глобальных // объектов. Обработка заключается в следующем: // 1. Команды создания глобальных объектов // CREATE GROUP // CREATE USER // CREATE ROLE // CREATE TABLESPACE // заменяются вызовами функций-обёрток для их безопасного выполнения. // Эти вызовы вставляются в скрипт глобальных объектов. В этот же скрипт // добавляются команды создания функций-обёрток и их удаления. // 2. Команды установки кодировки клиента // SET CLIENT_ENCODING // SET NAMES // \encoding // добавляются в оба скрипта. // 3. Команда подключения языка plpgsql // CREATE [ TRUSTED ] [ PROCEDURAL ] LANGUAGE plpgsql // игнорируется и не попадает ни в один скрипт. Создание языка plpgsql // производится отдельно, перед началом выполнения скриптов. //--------------------------------------------------------------------------- void DatabaseUpdater::prepareScripts( const QString& a_filename, QList& oa_mainScript, QList& oa_globalsScript ) { //пропустим файл через парсер, на выходе список команд bool helperInjected = false; SqlProcessor proc; QList result = proc.parse( a_filename ); //обход списка и анализ команд QListIterator it( result ); while ( it.hasNext() ) { const QByteArray originalLine = it.next(); QByteArray line = originalLine.toLower(); int i = 0; int end = line.size(); //пропускаем первое слово, перед ним пробелов нет, парсер их убирает while ( i != end && !QChar(line.at( i )).isSpace() ) ++i; //анализируем первое слово QByteArray command1 = line.left( i ); if ( command1 == "\\encoding" ) { oa_mainScript.append( originalLine ); oa_globalsScript.append( originalLine ); continue; } //переход на начало второго слова while ( i != end && QChar(line.at( i )).isSpace() ) ++i; int p = i; //пропускаем второе слово while ( i != end && !QChar(line.at( i )).isSpace() ) ++i; //анализируем команду QByteArray command2 = line.mid( p, i - p ); if ( command1 == "set" ) { oa_mainScript.append( originalLine ); if ( command2 == "names" || command2 == "client_encoding" ) oa_globalsScript.append( originalLine ); } else if ( command1 == "create" ) { if ( command2 == "group" || command2 == "user" || command2 == "role" || command2 == "tablespace" ) { //добавляем в выходной скрипт globals вызов безопасной функции-обёртки if ( !helperInjected ) { //но сначала вставляем команды создания функций-обёрток helperInjected = true; QListIterator helperIter( _createHelperFunctionsScript ); while ( helperIter.hasNext() ) oa_globalsScript.append( helperIter.next() ); } //переход на начало имени объекта while ( i != end && QChar(line.at( i )).isSpace() ) ++i; QByteArray name; if ( i != end ) { if ( line.at( i ) == '"' ) { //если имя объекта начинается с двойной кавычки, то пропуск до следующей //двойной кавычки ++i; //пропустить открывающую двойную кавычку p = i; while ( i != end && line.at( i ) != '"' ) ++i; name = line.mid( p, i - p ); if ( i != end ) ++i; //пропустить закрывающую двойную кавычку } else { //пропуск до конца слова p = i; while ( i != end && !QChar(line.at( i )).isSpace() ) ++i; //т.к. имя без двойных кавычек, то перевести его в нижний регистр, //как это делает PostgreSQL name = line.mid( p, i - p ).toLower(); if ( name.size() > 0 && name.at( name.size() - 1 ) == ';' ) name.chop( 1 ); } } QByteArray parameters( line.right( end - i ) ); if ( parameters.size() > 0 && parameters.at( parameters.size() - 1 ) == ';' ) parameters.chop( 1 ); QByteArray safeCall( "select safe_create_" ); safeCall.append( command2 ).append( "('" ).append( name ).append( "','" ) .append( parameters ).append( "');" ); oa_globalsScript.append( safeCall ); } else if ( command2 == "trusted" || command2 == "procedural" || command2 == "language" ) { //create language не нужен continue; } else { //create неглобального объекта, в основной скрипт oa_mainScript.append( originalLine ); } } else { //все остальные команды в основной скрипт oa_mainScript.append( originalLine ); } } //цикл по командам if ( helperInjected ) oa_globalsScript.append( _dropHelperFunctionsScript ); } //--------------------------------------------------------------------------- void DatabaseUpdater::clearMaclabels( SqlProcessor& a_proc ) { a_proc.execSQL( "select distinct 1 from pg_attribute a join pg_class c " "on (a.attrelid=c.oid) where c.relname='pg_class' and a.attname='relmaclabel'" ); if ( a_proc.result().size() == 1 ) a_proc.execSQL( "update pg_class set relmaclabel=null" ); } //--------------------------------------------------------------------------- bool DatabaseUpdater::createLanguagePlpgsql( SqlProcessor& a_proc, SqlProcessor& a_uriProc, bool a_uri ) { static const char* CREATE_SQL = "create language plpgsql"; static const char* CHECK_SQL = "select 1 from pg_language where lanname='plpgsql'"; a_proc.execSQL( CHECK_SQL ); if ( a_proc.result().size() == 0 ) { if ( !a_proc.execSQL( CREATE_SQL ) ) return false; } if ( a_uri ) { a_uriProc.execSQL( CHECK_SQL ); if ( a_uriProc.result().size() == 0 ) { if ( !a_uriProc.execSQL( CREATE_SQL ) ) return false; } } return true; } //--------------------------------------------------------------------------- void DatabaseUpdater::afterConnect( const QString& a_host, const QString& a_port, const QString& a_database, const QString& a_username, const QString& a_password ) { Q_UNUSED(a_port); Q_UNUSED(a_password); emit logConnectionParameters( a_host, a_database, a_username ); } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- bool DatabaseScript::findScript( const QStringList& a_paths ) { _script.replace( QChar('\\'), QChar('/') ); foreach( QString path, a_paths ) { QDir dir( path ); if ( dir.exists( _script ) ) { _script = dir.absoluteFilePath( _script ); return true; } } return false; } //--------------------------------------------------------------------------- bool DatabaseScript::findUriScript( const QStringList& a_paths ) { _uriScript.replace( QChar('\\'), QChar('/') ); if ( _uriScript.isEmpty() ) return true; foreach( QString path, a_paths ) { QDir dir( path ); if ( dir.exists( _uriScript ) ) { _uriScript = dir.absoluteFilePath( _uriScript ); return true; } } return false; }