Page MenuHomePhabricator

No OneTemporary

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 <sqlproc.h>
#include <kactivityindicator.h>
#include <ksettings.h>
#include <ksds.h>
#ifdef Q_OS_WIN32
#include <klogon_win.h>
#endif
-
+#include <QApplication>
+#include <QCloseEvent>
+#include <QEvent>
+#include <QLabel>
+#include <QFileDialog>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+#include <QPushButton>
+#include <QProgressBar>
+#include <QtConcurrentRun>
+#include <QShortcut>
+#include <QStyle>
+#include <QTextEdit>
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
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<int> 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( "Выполнение прервано в результате ошибки."
"<br>База данных не изменилась.") );
_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( "Выполнение прервано в результате ошибки."
"<br>База данных не изменилась.") );
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<QString, QString> 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<QString, QString> 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( "Подключение к базе данных<br>"
"&nbsp;&nbsp;Сервер: <b>%1</b><br>&nbsp;&nbsp;База данных: <b>%2</b><br>"
"&nbsp;&nbsp;Пользователь: <b>%3</b>").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( "<font color=\"#0000FF\">Операция:</font><br>" ) ).
append( a_commandDescription ).append( "<br>" );
QString dbError = a_dbError;
errorHtml.append( QString::fromUtf8( "<font color=\"#0000FF\">Сообщение об ошибке:</font><pre>" ) ).
append( dbError.trimmed().replace( '\n', "<br>" ) ).append( "</pre>");
if ( !a_command.isEmpty() )
errorHtml.append( QString::fromUtf8( "<font color=\"#0000FF\">Текст команды:</font><pre>" ) ).
append( a_command ).append( "</pre>" );
errorHtmlToLog( errorHtml );
}
//---------------------------------------------------------------------------
void MainWindow::error( const QString& a_error )
{
QString errorHtml = a_error;
errorHtmlToLog( errorHtml.replace( '<', "&lt;" ).replace( '>', "&gt;" ).replace( '\n', "<br>" ) );
}
//---------------------------------------------------------------------------
void MainWindow::errorHtmlToLog( const QString& a_errorHtml )
{
QString errorHtml = a_errorHtml;
QTextCursor cursor = _protocol->textCursor();
cursor.insertHtml( QString::fromUtf8( "<font color=\"#0000FF\">Ошибка</font><br>" ) );
cursor.insertHtml( errorHtml );
cursor.insertHtml( QString::fromUtf8( "<hr>" ) );
_protocol->moveCursor( QTextCursor::End );
++_errorCount;
}
//---------------------------------------------------------------------------
void MainWindow::message( const QString& a_message )
{
QString message = a_message;
_protocol->textCursor().insertHtml( message.replace( QChar('\n'), "<br>" ).append( "<br>" ) );
_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 <QApplication>
+#include <QDesktopWidget>
+#include <QTranslator>
#ifdef Q_OS_WIN32
#include <qt_windows.h>
#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 <QLabel>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+#include <QPushButton>
+#include <QTextEdit>
#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 <QApplication>
-#include <QCheckBox>
-#include <QCloseEvent>
-#include <QtConcurrentRun>
-#include <QDesktopWidget>
-#include <QDialog>
-#include <QFile>
-#include <QFileDialog>
-#include <QGroupBox>
-#include <QHash>
-#include <QHBoxLayout>
-#include <QLabel>
-#include <QListWidget>
-#include <QMessageBox>
-#include <QProgressBar>
-#include <QPushButton>
-#include <QShortcut>
-#include <QtDebug>
-#include <QTemporaryFile>
-#include <QTextCodec>
-#include <QTextEdit>
-#include <QTime>
-#include <QTranslator>
-#include <QVBoxLayout>
-#include <QXmlStreamReader>
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 <ksds.h>
#include <ksettings.h>
#include <sqlproc.h>
+#include <QCoreApplication>
+#include <QDate>
+#include <QDir>
#include <QXmlStreamReader>
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<Package*> 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<DatabaseScript*> 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( "Удаление базы данных <b>%1</b>" ).arg( _pset->database ) );
qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
if ( !dropDatabase( proc, _pset ) )
return false;
dbExists = false;
}
//создание БД если она не существует
if ( !dbExists )
{
emit message( QString::fromUtf8( "Создание базы данных <b>%1</b>" ).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( "Удаление учебной базы данных <b>%1</b>" ).arg( uriDatabase ) );
qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
if ( !dropDatabase( proc, _pset, uriDatabase ) )
return false;
uriDbExists = false;
}
//создание учебной БД если она не существует
if ( !uriDbExists )
{
emit message( QString::fromUtf8( "Создание учебной базы данных <b>%1</b>" ).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<DatabaseScript*> 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<QByteArray> mainScript;
QList<QByteArray> 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<QByteArray>& oa_mainScript,
QList<QByteArray>& oa_globalsScript )
{
//пропустим файл через парсер, на выходе список команд
bool helperInjected = false;
SqlProcessor proc;
QList<QByteArray> result = proc.parse( a_filename );
//обход списка и анализ команд
QListIterator<QByteArray> 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<QByteArray> 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;
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Jun 11, 8:13 PM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
126716

Event Timeline