Программирование баз данных в Delphi

Программное администрирование баз данных InterBase

Разбить на страницы
Показывать лекцию целиком

Данная лекция позволит изучить способы администрирования баз данных программно, из проекта Delphi. Ведь может случиться, что вы будете писать программу и проектировать базу данных "на заказ", для какой-нибудь небольшой фирмы. Такая фирма, как правило, не может позволить себе иметь в штате профессионального программиста и (или) администратора. В подобных фирмах роль администратора берет на себя один из сотрудников, который является более-менее опытным пользователем. Обучить такого пользователя премудростям и тонкостям работы с утилитой IBConsole непросто, еще сложнее научить его пользоваться утилитами командной строки. И здесь нам на помощь приходит набор компонентов с вкладки " InterBase Admin " палитры компонентов, который входит в стандартный состав Delphi. С помощью этих компонентов можно создать простое приложение, которое будет выполнять административные функции с базами данных, и с которым сможет работать любой пользователь.

Разработка программы AdminIB

Итак, наша задача: создать приложение, которое позволит регулярно делать резервную копию базы first.gdb как в ручном, так и в автоматическом режиме. Также приложение, в случае необходимости, должно уметь восстанавливать базу данных из резервной копии, и сохранять лог-файлы о копировании и восстановлении. В случае каких-то проблем, администратор вышлет вам эти логи, по которым вы сможете определить проблему. Также программа должна уметь добавлять новых пользователей, удалять или редактировать старых. Разумеется, пользователя SYSDBA мы удалять не позволим.

Убедимся, что сервер InterBase у нас работает, и загружаем Delphi. Выделим форму, в свойство Name которой впишем fMain, а в свойство Caption - "Администрирование базы данных First.gdb ". Сохраним проект в отдельную папку, модулю дадим имя Main, а проекту в целом - AdminIB. Неплохо было бы сразу установить свойство BorderStyle в bsDialog, чтобы программа не могла менять размеры, а свойство Position - в poDesktopCenter.

Реализация резервного копирования

Переходим на вкладку Win32 Палитры компонентов, находим и устанавливаем на форму компонент PageControl (каждая задача будет на своей вкладке). Переименуем полученный PageControl1 в PC1 для краткости кода, а свойству Align присвоим значение alClient.

Щелкаем по компоненту PC1 правой кнопкой и выбираем команду New Page - создалась новая вкладка. Выделим ее, и в свойстве Name вместо TabSheet1 напишем TSh1, а в свойстве Caption - "Резервное копирование базы данных". Будьте внимательны: очень легко ошибиться, и вместо листа TabSheet выделить весь компонент PageControl.

Примерный вид вкладки смотрите на рисунке:

(рис 27.1) Вкладка "Резервное копирование базы данных"

Вначале установим на лист панель Panel1, свойству Align которого присвоим значение alTop. Очистим свойство Caption. На панель поместим две радиокнопки RadioButton. В свойству Name первой кнопки укажем RB1, у второй - RB2. А в свойстве Caption, соответственно, "Ручное копирование" и "Автоматическое копирование". Свойство Checked компонента RB2 переведем в True.

Ниже поместим простую кнопку Button, которую переименуем в bStartBackup, а в свойстве Caption напишем "Начать копирование". Свойство Enabled переведем в False. Поскольку по умолчанию, программа будет выполнять автоматическое копирование, эта кнопка будет недоступна. Как только мы выделим радиокнопку "Ручное копирование", сразу вернем доступность этой кнопке.

В правой части панели поместите компонент Label. Чтобы компонент мог содержать многострочный текст, свойство AutoSize переведите в False, а свойство WordWrap - в True. В свойстве Caption напишите текст "Время запуска автоматического резервного копирования:", и подгоните размеры, как на рисунке.

Ниже находятся еще два Label с текстом "Часы:" и "Минуты:". Рядом с ними располагаются два компонента SpinEdit с вкладки Samples Палитры компонентов. Первый переименуйте в SE1, второй - в SE2. У первого в параметре MinValue оставьте 0, в параметре MaxValue укажите 23, а в параметре Value поставьте 1 (по умолчанию, автокопирование будет начинаться в час ночи, когда сотрудников на работе нет). У второго SpinEdit параметры такие: MinValue = 0, MaxValue = 59, Value = 0. Вместо этих компонентов можно было бы использовать один MaskEdit с маской "99:99", но, к сожалению, сам компонент не проверяет правильность ввода значений часов и минут, придется делать это самим. Поэтому остановимся на компонентах SpinEdit, которые позволят пользователю вводить только целые числа в заданном диапазоне.

Под панелью установите компонент Memo. Свойство Align установите в alClient, свойство ScrollBars в ssVertical, и не забудьте очистить от текста свойство Lines.

Далее главное: нужно добавить два не визуальных компонента - Timer с вкладки System и IBBackupService с вкладки InterBase Admin. Компоненты не визуальные, их можно расположить на любом месте, первый будет запускать копирование автоматически, когда подойдет указанное время, второй - осуществлять само копирование. У Timer свойство Interval установите 60000, чтобы таймер срабатывал один раз в минуту.

Компонент IBBackupService предназначен для создания резервных копий базы данных InterBase. Этот компонент позволяет делать различные настройки резервного копирования, в зависимости от того, какие параметры в свойстве Options включены.

У IBBackupService свойство Name для краткости переименуйте в IBBS. На этом приготовления вкладки закончены, приступим к кодированию. Все резервные копии должны попадать в одну папку, например, C:\DataBases\Backup. Эта папка физически должна существовать на диске. При этом мы не сможем делать копии с одним и тем же именем, ведь операционная система не позволит создавать файл с таким же именем. Конечно, можно перед очередным копированием удалять старую копию, но это тоже не выход - что, если новая резервная копия будет создана с ошибками, то есть, в базе данных появились нарушения? Можно было бы восстановить ее из старой копии, но для этого ее не нужно удалять! Выход: добавлять к имени файла дату и время его создания, тогда можно делать сколько угодно копий, и у всех будут разные имена.

Для этого в разделе Private модуля главной формы опишем функцию GetName:

private
    { Private declarations }
    function GetName():String;

Установите курсор на название функции и нажмите <Ctrl+Shift+C>, чтобы сгенерировать саму функцию. Вот ее код:

{Функция возвращает префикс имени файла в виде yyyymmdd_hhmmss_ }
function TfMain.GetName: String;
var
  ye, mo, da : Word; //для даты
  ho, mi, se, ms : Word; //для времени
  st : String[2]; //для добавления нуля, например 05
begin
  DecodeDate(Date, ye, mo, da); //декодируем на составные дату
  DecodeTime(Time, ho, mi, se, ms); //декодируем время
  //теперь собираем строку:
  Result:= IntToStr(ye); //добавили год
  //получаем и добавляем месяц:
  st:= IntToStr(mo);
  if Length(st) = 1 then st:= '0' + st; //если 1 символ, добавим спереди 0
  Result := Result + st;
  //получаем и добавляем день:
  st:= IntToStr(da);
  if Length(st) = 1 then st:= '0' + st;
  Result := Result + st + '_';
  //теперь получаем и добавляем час:
  st:= IntToStr(ho);
  if Length(st) = 1 then st:= '0' + st;
  Result := Result + st;
  //получаем и добавляем минуты:
  st:= IntToStr(mi);
  if Length(st) = 1 then st:= '0' + st;
  Result := Result + st;
  //получаем и добавляем секунды:
  st:= IntToStr(se);
  if Length(st) = 1 then st:= '0' + st;
  Result := Result + st + '_';
  //теперь функция вернет префикс имени файла, например:
  //20100205_010012_
end;

Здесь мы декодировали на составные части дату и время. Год уже имеет 4 цифры, поэтому его дополнительно обрабатывать не нужно. А вот месяц, день, час, минута или секунда могут состоять из одной цифры. Чтобы не запутаться, делаем проверку:

st:= IntToStr(mo);
  if Length(st) = 1 then st:= '0' + st; //если 1 символ, добавим спереди  0
  Result := Result + st;

В переменную st мы получаем номер месяца. Если этот номер состоит из одной цифры, перед ней добавим '0'. И в конце прибавим результат в переменную Result. Таким же образом мы проверяем и остальные данные. Эта функция гарантирует нам не только уникальность имени файла, но и правильную сортировку файлов по дате. К тому же по имени сразу видно - когда был создан файл.

Теперь сделаем доступной кнопку bStartBackup, если пользователь отметил радиокнопку RB1 (Ручное копирование). Для этого сгенерируем для RB1 событие onClick:

{Щелкнули по "Ручное копирование"}
procedure TfMain.RB1Click(Sender: TObject);
begin
  //если отмечено Ручное копирование, делаем
  //кнопку доступной:
  if RB1.Checked then
    bStartBackup.Enabled:= True;
end;

Точно также сделаем кнопку недоступной, если щелкнули по RB2 (Автоматическое копирование). Для этого сгенерируем событие onClick для RB2:

{Щелкнули по "Автоматическое копирование"}
procedure TfMain.RB2Click(Sender: TObject);
begin
  //если отмечено Автоматическое копирование, делаем
  //кнопку недоступной:
  if RB2.Checked then
    bStartBackup.Enabled:= False;
end;

Соображаем дальше. Копирование у нас будет начинаться либо по нажатию кнопки, либо по срабатыванию таймера, в зависимости от того, какая радиокнопка выделена. Значит, чтобы дважды не писать один и тот же код, создадим процедуру резервного копирования, которую будем вызывать из двух мест. Поскольку процедура будет напрямую работать с компонентом IBBS, вначале объявим ее в разделе private, после функции GetName:

private
    { Private declarations }
    function GetName():String;
    procedure BackupCopy;

Установите курсор на процедуру и нажмите <Ctrl+Shift+C>, чтобы сгенерировать тело процедуры. Ее код:

{Процедура реализации резервного копирования}
procedure TfMain.BackupCopy;
var s : String; //для получения префикса файла
begin
  //получим префикс:
  s:= GetName();
  //очистим Memo1, если там что то есть:
  Memo1.Clear;
  //задаем параметры компонента IBBackupService:
  IBBS.ServerName := 'MyServ';
  IBBS.LoginPrompt:= False;
  IBBS.Params.Add('user_name=sysdba');
  IBBS.Params.Add('password=masterkey');
  IBBS.Active := True;
  //начинаем копирование
  try
    IBBS.Verbose:= True;
    IBBS.DatabaseName:= 'c:\DataBases\first.gdb';
    IBBS.BackupFile.Clear;
    IBBS.BackupFile.Add('c:\DataBases\Backup\' + 
s + 'first.gbk');
    IBBS.ServiceStart;
    //пока не дошли до конца, записываем параллельно лог в Memo1:
    while not IBBS.Eof do
      Memo1.Lines.Add(IBBS.GetNextLine);
  finally
    IBBS.Active:= False;
  end;
  //сохраним лог в файл:
  Memo1.Lines.SaveToFile('c:\DataBases\Backup\' +  
s + 'first.log');
end;

Здесь мы вначале получили в переменную префикс имени файла. Зачем это нужно? Нам требуется:

  • Сделать резервную копию.
  • Сохранить лог копирования в файл.
  • Эти действия будут производиться в разное время, поэтому чтобы префикс копии совпадал с префиксом лог-файла, мы и сохраняем его в переменную s:

    //получим префикс:
      s:= GetName();

    Далее мы очищаем Memo1, если вдруг там уже был текст. После чего приступаем к настройкам параметров компонента IBBS:

    IBBS.ServerName := 'MyServ';
      IBBS.LoginPrompt:= False;
      IBBS.Params.Add('user_name=sysdba');
      IBBS.Params.Add('password=masterkey');
      IBBS.Active := True;

    Тут все достаточно прозрачно. Поскольку копирование будет производиться на серверном ПК, то будет использован локальный адрес, следовательно, свойство ServerName мы можем назвать, как угодно. Если бы сервер был сетевым, пришлось бы писать сетевое имя этого ПК, или его IP -адрес. При этом следует обратить внимание на свойство Protocol. Это свойство позволяет выбрать протокол соединения. Поскольку программа будет загружаться там же, где установлен InterBase и где хранится база данных, оставляет значение по умолчанию - Local. Если программа будет работать с удаленным сервером, то здесь нужно будет выбрать один из сетевых протоколов, обычно выбирают TCP.

    Далее с помощью LoginPrompt мы заявляем, что не нужно запрашивать имя пользователя и пароль (кто же введет эти данные в час ночи, когда запустится авто-копирование?). Затем мы вводим в свойство Params имя пользователя SYSDBA и его пароль. Если вы изменили пароль (а это обязательно нужно сделать в реальной системе), вместо " masterkey " укажите свой пароль. В конце мы делаем IBBS активным.

    Само копирование мы помещаем в блок try…finally…end, поскольку копирование может быть и неудачным, если БД разрушена:

    try
        IBBS.Verbose:= True;
        IBBS.DatabaseName:= 'c:\DataBases\first.gdb';
        IBBS.BackupFile.Clear;
        IBBS.BackupFile.Add('c:\DataBases\Backup\' + s + 'first.gbk');
        IBBS.ServiceStart;
        //пока не дошли до конца, записываем параллельно лог в Memo1:
        while not IBBS.Eof do
          Memo1.Lines.Add(IBBS.GetNextLine);
      finally
        IBBS.Active:= False;
      end;

    Здесь свойство Verbose (многословность) определяет - будет ли выводиться лог резервного копирования. При значении True лог ведется, иначе - нет. Нам нужно, чтобы лог создавался.

    В свойство DatabaseName помещаем адрес и имя нашей рабочей базы данных.

    Свойство BackupFile должно содержать имя файла (или файлов) резервной копии. У нас база данных состоит из одного файла, и резервная копия также будет состоять из одного файла, поэтому прежде, чем мы поместим туда имя очередной резервной копии, это свойство нужно очистить. Затем формируем имя файла из адреса, префикса и самого имени " first.gbk". А затем стартуем копирование методом ServiceStart. Заметим, что установка True в свойстве Active компонента не начинает резервное копирование, а только делает компонент активным.

    Параллельно копированию в компонент Memo1 помещаем отчет о текущем действии, от начала копирования до конца. Метод GetNextLine компонента IBBackupService возвращает очередную строку лог-отчета. Когда копирование окончено, делаем компонент IBBS неактивным.

    И в самом конце процедуры записываем полученный в Memo1 лог в файл с таким же именем, как резервная копия, но расширением *.log:

    //сохраним лог в файл:
      Memo1.Lines.SaveToFile('c:\DataBases\Backup\' +  
    s + 'first.log');

    Теперь для ручного копирования нам осталось сгенерировать процедуру нажатия на кнопку "Начать копирование", откуда вызовем процедуру BackupCopy:

    {Нажали на кнопку "Начать копирование"}
    procedure TfMain.bStartBackupClick(Sender: TObject);
    begin
      BackupCopy;
    end;

    Попробуйте выполнить ручное копирование. Напомню, что сервер InterBase должен быть запущен, а папка " C:\DataBases\Backup " должна физически существовать на диске. Как только вы нажмете на кнопку, копирование начнется. Закончится оно примерно такой строкой:

    gbak: closing file, committing and finishing. 38912 bytes written.

    Зайдите в папку Backup - там должна появиться пара файлов с одинаковым именем и расширениями *.gbk и *.log. Это и есть наши резервная копия и лог-файл.

    Займемся автоматическим копированием. Выделите таймер и сгенерируйте для него событие onTimer, которое будет срабатывать каждую минуту ( Interval =60000):

    {Сработал таймер}
    procedure TfMain.Timer1Timer(Sender: TObject);
    var
      ho, mi, se, ms : Word; //для времени
    begin
      //если включено ручное копирование, просто выходим:
      if RB1.Checked then Exit;
      //иначе декодируем время
      DecodeTime(Time, ho, mi, se, ms); //декодируем время
      //если нужный час и нужная минута, начинаем копирование:
      if (ho = SE1.Value) and (mi = SE2.Value) then BackupCopy;
    end;

    Вначале мы проверяем, не включено ли ручное копирование. Если включено, то сразу выходим, ничего не делая. Затем мы снова декодируем время, чтобы посмотреть - совпадают ли данные в компонентах SpinEdit с текущими часом и минутой. И если совпадают, вызываем процедуру BackupCopy.

    Заметим, что таймер может сработать не сразу, как системные часы покажут нужное время. Ведь в таймере учитываются и секунды. Значит, если копирование у нас должно начаться в 01:00, а когда программа была запущена, ваше системное время показывало, скажем, 25 секунд, то и таймер сработает в 01:00:25. Что, в принципе, не столь важно.

    Сохраните проект, скомпилируйте и запустите. Попробуйте установить время авто-копирования на минуту-другую больше, чем показывают ваши системные часы (должна быть выделена радиокнопка "Автоматическое копирование"). Дождитесь, когда системное время сравняется с указанным. Возможно, придется подождать еще несколько секунд, после чего резервное копирование начнется автоматически. Опять появится пара файлов в папке Backup. Таким образом, достаточно выделить радиокнопку RB2 (у нас она выделена по умолчанию), и не выключать на сервере программу, чтобы обеспечить регулярное резервное копирование базы данных. Разумеется, серверный ПК с InterBase, вашей базой данных и администраторской программой должен быть включен в назначенное время (серверные компьютеры обычно работают в режиме 24/7, то есть 24 часа в сутки, 7 дней в неделю).

    Напоследок заметим, что компонент IBBackupService имеет сложное раскрывающееся свойство Options, которое имеет следующие переключатели:

    Переключатели свойства Options компонента IBBackupService
    Переключатель Описание
    IgnoreChecksums Игнорировать ошибки контрольных сумм. Применяется обычно при попытке восстановить поврежденную БД.
    IgnoreLimbo Игнорировать ошибки двухфазного подтверждения limbo -транзакций. Также применяется при восстановлении поврежденной БД.
    MetadataOnly При включенном переключателе в резервную копию попадут только метаданные. Можно использовать, если вы хотите получить пустую БД с таблицами, индексами, генераторами, триггерами и проч., но без записей.
    NoGarbageCollection Не делать сборку мусора во время резервного копирования.
    OldMetadataDesc Делать резервную копию в старом стиле. Как правило, не применяется.
    NonTransportable Делать резервную копию, непереносимую на другие версии InterBase (а также Firebird и Yaffil ).
    ConvertExtTables Преобразует внешние файлы во внутренние таблицы. Также может применяться при восстановлении поврежденной БД.

    Как видно из таблицы, резервное копирование можно делать с некоторыми параметрами. Мы не использовали этих параметров, то есть, делали копию с параметрами "по умолчанию". В результате получили обычную резервную копию, для чего и нужна наша программа. Если же доведется ремонтировать базу данных, то делать это нужно утилитами gfix и gback, которые предоставляют гораздо больше возможностей, чем указанные переключатели.

    Реализация восстановления из резервной копии

    Определимся с задачей. Нам нужно:

  • Получить имя файла резервной копии, из которой будем делать восстановление.
  • Выбрать необходимые параметры восстановления.
  • Произвести само восстановление. Причем восстанавливать будем в отдельную папку, например C:\DataBases\Restore, которая уже должна существовать на диске. Восстанавливаемой БД перед именем присвоим префикс из даты и времени, как и у резервного копирования, также будем создавать лог-файл. Если восстановление будет успешным, потом администратор сможет переименовать полученную БД и перенести ее в рабочую папку средствами Windows или файловым менеджером.
  • Для восстановления базы данных сделаем новую вкладку. Выделите компонент PC1 ( PageControl ). Чтобы выделить именно компонент, а не страницу на нем, нужно щелкать правее ярлычка вкладки. Щелкнем по нему правой кнопкой и выберем команду New Page. В свойстве Name новой вкладки вместо TabSheet1 напишем TSh2, а в свойстве Caption - "Восстановление базы". Внешний вид вкладки представлен на рисунке ниже:

    (рис 27.2) Вкладка "Восстановление базы"

    Здесь у нас будет:

  • Одна простая панель Panel.
  • Три компонента Label.
  • Один Edit.
  • Одна панель GroupBox.
  • Шесть флажков CheckBox.
  • Один SpinEdit.
  • Один ComboBox.
  • Две простых кнопки.
  • Компонент Memo.
  • Компонент IBRestoreService из вкладки InterBase Admin. Компонент предназначен для восстановления базы данных InterBase из резервной копии. Имеет достаточно широкие возможности настроек.
  • Диалог OpenDialog.
  • Сделаем дизайн вкладки, как на рисунке 27.2. Вначале ставим панель Panel, свойство Align = alTop, свойство Caption очищаем, высоту делаем немного меньше половины страницы.

    На панель сверху устанавливаем Label, Edit и Button. В свойстве Caption компонента Label выводим текст "Выберите резервную копию, из которой будем восстанавливать БД:", свойство Text компонента Edit1 очищаем. Кнопку делаем квадратной, размерами под Edit1: Height = Width = 21. В свойстве Caption помещаем "…". Свойство Name переименуем в bOpenBak.

    Ниже на панель помещаем панельку GroupBox, в свойстве Caption которой напишем "Параметры восстановления". Внутрь GroupBox1 поместим шесть компонентов CheckBox, которые назовем соответственно CB1CB6 (сверху - вниз по первой колонке, и сверху - вниз по второй). В свойстве Caption этих флажков поместим соответственно текст:

    "Отключить индексы"

    "Отключить теневые копии"

    "Отключить проверку внеш.ключей"

    "Записывать данные для таблиц отдельно"

    "Создать новую БД"

    "Заполнить страницы на 100%"

    Подгоните размеры компонентов, как на рисунке. У флажка CB5 свойство Checked переводим в True, так как нам по умолчанию нужно создать новую базу данных. Кроме того, включим еще и флажок CB2, чтобы не восстанавливать теневые копии, если они уже есть. Когда база данных будет восстановлена, ей всегда можно назначить новую теневую копию. А попытка восстановления БД с теневой копией приведет к ошибке, если сначала не удалить этот shadow -файл.

    Ниже помещаем Label с текстом "Размер буфера страницы:", а рядом помещаем SpinEdit, свойство Name которого переименуем в SE3. В справочнике Delphi размер буфера страницы у компонента IBRestoreService указан как 3000 килобайт. Не будем спорить с производителями Delphi и по умолчанию установим именно этот размер. Для этого сделаем следующие настройки SE3:

    MinValue = 1000 (минимальное значение)
    MaxValue = 5000 (максимальное значение)
    Increment = 100 (шаг приращения/убывания)
    Value = 3000 (текущее значение)

    Ниже разместим еще один Label с текстом "Размер страницы:", а правее поместим ComboBox. Переименуем его свойство Name в CBox1, откроем редактор свойства Items и впишем построчно следующие возможные размеры страниц в InterBase:

    1024
    2048
    4096
    8192

    В свойство Text поместим рекомендованное значение 8192.

    Правее этих компонентов поместим обычную кнопку, в свойстве Caption которой напишем "Начать восстановление БД", а свойство Enable переведем в False (мы сделаем кнопку доступной, когда пользователь выберет какую-нибудь резервную копию, из которой требуется сделать восстановление). Свойство Name кнопки переименуйте в bStartRestore.

    Ниже панели Panel поместим Memo (оставим имя по умолчанию Memo2 ). Свойство Align переведем в alClient, в свойстве ScrollBars выберем ssVertical, чтобы появилась вертикальная прокрутка, а также очистим текст в свойстве Lines.

    Займемся не визуальными компонентами. OpenDialog переименуем в OD1. Откроем редактор свойства Filter и в столбец Filter Name впишем "Резервные копии БД", а в столбец Filter - "*.gbk". В свойстве InitialDir укажем папку с нашими резервными копиями "C:\DataBases\Backup". Также в свойстве DefaultExt укажем расширение файлов по умолчанию "gbk" (без точки).

    Перейдем к компоненту IBRestoreService. Для краткости обращений переименуем его свойство Name в IBRS. Как видите, этот компонент также имеет сложное раскрывающееся свойство Options, в котором можно настроить следующие переключатели:

    Переключатели свойства Options компонента IBRestoreService
    Переключатель Описание
    DeactivateIndexes Сделать индексы в базе данных неактивными.
    NoShadow При восстановлении БД не восстанавливать ее теневые копии.
    NoValidityCheck Отключить проверку внешних ограничений ( Foreign Key ). Делать это нужно осторожно, предварительно сделав копию метаданных.
    OneRelationAtATime Восстановить метаданные и данные для каждой таблицы по одной записи за раз. Бывает полезно, если обычное восстановление не получается из-за разрушенных данных или нарушенных внешних ограничений.
    Replace При значении True можно будет восстанавливать копию поверх существующей базы данных. Крайне не рекомендуется.
    CreateNew При True создается новый файл базы данных.
    UseAllSpace При восстановлении заполнять страницы базы на 100%, вместо положенных по умолчанию 80%. Чтобы не ухудшить производительность БД, делать это стоит лишь с базами "только для чтения".

    Для шести из семи этих переключателей у нас имеются соответствующие компоненты ComboBox, мы опустили только переключатель Replace, так как восстанавливать резервную копию, затирая существующую базу данных, не рекомендуется. Кроме того, имя восстанавливаемой БД, благодаря префиксу, будет уникальным, так что восстановить копию поверх БД все равно не получится. Если программа не предусматривает настройки параметров восстановления, то эти параметры можно было бы указать прямо в свойстве Options компонента. Однако наша программа предусматривает настройку, поэтому параметры восстановления будем устанавливать программно, в зависимости от того, какие CheckBox включены.

    Займемся кодированием. Сгенерируйте процедуру нажатия на кнопку "…". Ее код:

    {Щелкнули по кнопке "..."}
    procedure TfMain.bOpenBakClick(Sender: TObject);
    begin
       //если диалог состоялся:
       if OD1.Execute then begin
          //укажем выбранный файл
          Edit1.Text:= OD1.FileName;
          //делаем доступной кнопку "Начать восстановление БД"
          bStartRestore.Enabled:= True;
       end; //if
    end;

    Остается реализовать само восстановление. Сгенерируйте процедуру нажатия на кнопку "Начать восстановление БД". Ее код:

    {Щелкнули по кнопке "Начать восстановление БД"}
    procedure TfMain.bStartRestoreClick(Sender: TObject);
    var st: String;
    begin
      //получим префикс для восстановленной БД и лога:
      st := GetName();
      //Очистим Memo2:
      Memo2.Clear;
      //устанавливаем начальные параметры:
      IBRS.ServerName:= 'MyServ';
      IBRS.LoginPrompt:= False;
      IBRS.Params.Add('user_name=sysdba');
      IBRS.Params.Add('password=masterkey');
      IBRS.Active:= True;
      //начинаем восстановление
      try
        IBRS.Verbose:= True; //вести лог
        IBRS.Options:= []; //очистим свойство Options
        //далее мы включаем в Options переключатель,
        //если его ComboBox включен:
        if CB1.Checked then IBRS.Options :=
               IBRS.Options + [DeactivateIndexes];
        if CB2.Checked then IBRS.Options :=
               IBRS.Options + [NoShadow];
        if CB3.Checked then IBRS.Options :=
               IBRS.Options + [NoValidityCheck];
        if CB4.Checked then IBRS.Options :=
               IBRS.Options + [OneRelationAtATime];
        if CB5.Checked then IBRS.Options :=
               IBRS.Options + [CreateNewDB];
        if CB6.Checked then IBRS.Options :=
               IBRS.Options + [UseAllSpace];
        //устанавливаем размер буфера страницы:
        IBRS.PageBuffers:= SE3.Value;
        IBRS.PageSize:= StrToInt(CBox1.Text); //размер самой страницы
        //очищаем свойство с именем БД, которая должна получиться:
        IBRS.DatabaseName.Clear;
        //устанавливаем новое имя с префиксом:
        IBRS.DatabaseName.Add('c:\DataBases\Restore\' + 
    st + 'first.gdb');
        //очищаем свойство с именем резервной копии:
        IBRS.BackupFile.Clear;
        //устанавливаем новое имя из OpenDialog:
        IBRS.BackupFile.Add(OD1.FileName);
        //стартуем восстановление:
        IBRS.ServiceStart;
        //параллельно ведем лог, пока не наступит конец восстановления:
        while not IBRS.Eof do
          Memo2.Lines.Add(IBRS.GetNextLine);
      finally
        IBRS.Active:= False; //деактивируем компонент
      end; //try
      //запишем полученный лог в файл:
      Memo2.Lines.SaveToFile('c:\DataBases\Restore\' + 
    st + 'first.log');
      //теперь очистим Edit1 и снова сделаем кнопку недоступной:
      Edit1.Text:= '';
      bStartRestore.Enabled:= False;
    end;

    Как видно из листинга, работа компонента IBRestoreService мало отличается от работы IBBackupService, который мы рассматривали достаточно подробно. Имеется два новых свойства компонента: PageBuffers и PageSize. Первое свойство устанавливает буфер страницы - размер кэша, или размер оперативной памяти, который будет использоваться для считывания данных. Если размер данных окажется больше, чем размер буфера, программа будет обращаться к жесткому диску. В справочнике Delphi в примере работы компонента IBRestoreService указан размер буфера 3000 килобайт.

    Свойство PageSize устанавливает размер страниц восстанавливаемой базы данных. Как уже говорилось в прошлых лекциях, при восстановлении БД из резервной копии можно изменить размер страниц этой БД. Например, если старая версия БД имела размер 4096, то при восстановлении можно установить размер страниц 8192.

    Код процедуры подробно комментирован, и в дополнительных пояснениях не нуждается. Сохраните проект, скомпилируйте его и попробуйте сделать восстановление из какой-нибудь резервной копии БД, которую делали ранее. Восстановление закончится, когда в Memo2 появится строка " gbak: finishing, closing, and going home ", а в папке C:\DataBases\Restore появятся два файла: восстановленная база данных и лог-файл.

    Работа с пользователями

    Определимся с задачей:

  • Добавление нового пользователя.
  • Изменение данных существующего пользователя (пароль, имя, отчество, фамилия). Если пользователь - SYSDBA, редактирование запрещаем. Ведь при резервном копировании и восстановлении мы жестко задавали имя SYSDBA и пароль masterkey (если вы изменили этот пароль, значит у вас свой вариант). И если администратор изменит этот пароль, программа не сможет работать, вам придется изменить этот пароль в программе и перекомпилировать ее. Впрочем, в дальнейшем вы можете усложнить программу, добавив в нее, например, работу с ini -файлом (см. лекцию №18 курса "Введение в программирование на Delphi "). В этом случае, вы сможете прописать в ini -файл новый пароль пользователя SYSDBA, если администратор изменит его, и в дальнейшем использовать новый пароль. Если же администратор не будет менять пароля, то можно использовать пароль по умолчанию, " masterkey " (или ваш вариант).
  • Удаление любого пользователя, кроме SYSDBA.
  • Для реализации этой задачи создадим новую вкладку, которую назовем TSh3, а в свойстве Caption напишем "Пользователи". Внешний вид вкладки показан на следующем рисунке:

    (рис 27.3 ) Вкладка "Пользователи"

    Здесь мы установили компонент ListBox, свойство Name которого переименовали в LB1, а свойство Align перевели в alLeft, чтобы компонент занял всю левую часть окна. Измените размеры компонента, как на рисунке.

    В правой части установили три простых кнопки Button. Свойства Name кнопок переименовали в bAddUser, bModifyUser и bDeleteUser, а в свойстве Caption, соответственно, прописали "Добавить", "Редактировать" и "Удалить".

    Кроме того, мы добавили не визуальный компонент IBSecurityService с вкладки InterBase Admin. Компонент для краткости обращения переименовали в IBSS. Этот компонент предназначен для работы с пользователями, зарегистрированными в InterBase, и позволит нам редактировать, добавлять и удалять пользователей.

    Несмотря на кажущуюся простоту окна, реализация работы с пользователями немного сложнее предыдущих примеров. В отличие от компонентов IBBackupService и IBRestoreService, компонент IBSecurityService выполняет не одну, а три задачи, что выражено кнопками в правой части окна. Кроме того, IBSecurityService работает не с нашей базой данных first.gdb, а с системной БД InterBase isc4.gdb, что накладывает некоторые ограничения. Например, имя, отчество и фамилию пользователя не получится вносить русскими буквами, так как isc4.gdb создавалась не в кодировке WIN1251. Кроме того, в целях безопасности, компонент IBSecurityService не выводит существующий пароль пользователя, хотя и позволяет менять его. Компонент позволяет оперировать такими данными, как логин пользователя, его пароль, имя, отчество, фамилия, идентификатор пользователя UserID и идентификатор группы GroupID.

    Для доступа к данным пользователя компонент IBSecurityService имеет свойство UserInfo, которое представляет собой список пользователей. Индекс свойства начинается с 0, как и список строк . UserInfo будут совпадать, это сильно облегчит нашу задачу.

    Первым делом нам нужно при создании главной формы заполнить компонент IBSecurityService свежими данными о пользователях, и обновить этими данными список ListBox.Так как нам то же самое придется делать при добавлении, редактировании и удалении пользователей, в разделе private добавим новую процедуру ReloadUsers:

    private
        { Private declarations }
        function GetName():String;
        procedure BackupCopy;
        procedure ReloadUsers;

    Реализация этой процедуры:

    {Перечитываем данные о пользователях}
    procedure TfMain.ReloadUsers;
    var i: Integer; //для счетчика
    begin
      //вначале очистим ListBox:
      LB1.Clear;
      //заполняем список пользователями:
      IBSS.ServerName:= 'MyServ';
      IBSS.LoginPrompt:= False;
      IBSS.Params.Add('user_name=sysdba');
      IBSS.Params.Add('password=masterkey');
      IBSS.Active:= True;
      try
        IBSS.DisplayUsers; //получаем  информацию о пользователях
        for i:=0 to IBSS.UserInfoCount -1 do
          LB1.Items.Add(IBSS.UserInfo[i].UserName);
      finally
        IBSS.Active:= False;
      end; //try
    end;

    Здесь все достаточно прозрачно. Вначале очищаем ListBox от возможных старых записей. Затем настраиваем сервис IBSecurityService, так же, как ранее настраивали компоненты IBRestoreService и IBBackupService.

    Метод DisplayUsers получает в компонент всю информацию о пользователях, после чего она доступна в свойстве UserInfo. Свойство UserInfoCount показывает общее количество пользователей. Подсвойство UserName свойства UserInfo содержит логин текущего пользователя. Строкой

    LB1.Items.Add(IBSS.UserInfo[i].UserName);

    мы добавляем в ListBox имя очередного пользователя. Теперь нужно вызвать эту процедуру при создании формы. Для этого сгенерируйте для главной формы событие OnCreate, в котором сделаем вызов этой процедуры:

    ReloadUsers;

    Проверить результат можно, скомпилировав и загрузив программу. При загрузке, в окне ListBox должны отобразиться зарегистрированные пользователи.

    Далее нам придется сделать новую форму - редактор данных пользователей. Выберите команду File -> New -> Form. Форму назовите fEditor, в свойстве Caption напишите "Редактирование данных пользователя", сохраните новый модуль под именем Editor.

    Свойство BorderStyle формы переведем в bsDialog, чтобы пользователь не мог менять размеры окна, а в свойстве Position выберем poMainFormCenter.

    Внешний вид новой формы показан на рисунке ниже:

    (рис 27.4 ) Форма fEditor

    Итак, у нас имеется:

  • Два компонента GroupBox.
  • Шесть компонентов Label.
  • Шесть компонентов Edit.
  • Две кнопки Button.
  • В свойстве Caption первого GroupBox напишем "Обязательные данные", а второго - "Дополнительные данные".

    На верхний GroupBox помещаем три Label и заполняем текст, как на рисунке. Затем помещаем три Edit. У первого свойство Name переименуем в eUser, у второго - ePass, и у третьего - ePass2.

    Выделите одновременно (с нажатой Shift ) компоненты ePass и ePass2. В свойстве PasswordChar укажите символ "*", чтобы скрыть реальный пароль. Не забудьте удалить текст из свойства Text всех компонентов Edit.

    На нижний GroupBox также помещаем три Label и заполняем текст, как на рисунке. Затем помещаем три Edit. У первого свойство Name переименуем в eName, у второго - eMiddle, и у третьего - eLast. Очищаем свойство Text у всех Edit.

    Ниже расположим две кнопки, которые переименуем соответственно, в bAccept и bCancel, а свойства Caption - как на рисунке.

    Теперь немного подумаем. Эта форма будет показываться в двух случаях: при добавлении нового пользователя, и при редактировании существующего. В первом случае пользователь еще не имеет пароля, во втором - имеет. Причем при редактировании существующего пользователя, администратор может как сменить старый пароль, так и не трогать его. Как узнать, менялся ли пароль существующего пользователя, если мы не имеем возможности вывести его с помощью IBSecurityService? Вариант: при редактировании существующего пользователя поместим в ePass и ePass 2 какой-нибудь длинный пароль "по умолчанию", например, двадцать единичек. А при сохранении результата редактирования будем смотреть - если в ePass пароль "по умолчанию", значит сохранять пароль не нужно. В случае же добавления нового пользователя ePass и ePass2 заполняться не будут.

    Пойдем дальше. Как уже говорилось, база данных isc4.gdb не поддерживает кодировку win1251, следовательно, русские буквы в нее помещать нельзя. Значит, при вводе текста во все шесть компонентов Edit придется делать проверку - что вводит пользователь. Если английские буквы или цифры, или BackSpace, то ничего не делаем, иначе запрещаем символ. Чтобы шесть раз не писать один и тот же код, в разделе private создадим функцию KeyCan:

    private
        { Private declarations }
        function KeyCan(c:Char):Boolean;

    Код реализации функции представлен ниже:

    {Проверяем допустимость символа}
    function TfEditor.KeyCan(c: Char): Boolean;
    begin
      case c of
        'A'..'z': Result:= True; //англ. буквы разрешаем
        '0'..'9': Result:= True; //цифры разрешаем
        #8 : Result:= True; //BackSpace разрешаем
      else Result:= False; //остальное запрещаем
      end; //case
    end;

    Теперь нам нужно реализовать проверку во всех шести Edit. Выделите первый, и сгенерируйте для него событие OnKeyPress. В процедуру поместите только одну строку:

    if not KeyCan(Key) then Key:= #0;

    То же самое проделайте с оставшимися пятью Edit. Таким образом, мы реализовали проверку на ввод допустимых символов. Если пользователь попытается ввести что-нибудь, кроме английских букв, цифр или BackSpace, то его действия будут игнорироваться.

    Пойдем дальше. Если мы редактируем нового пользователя, то в ePass и ePass2 у нас будет текст в двадцать единиц. Если администратор захочет изменить пароль, то он изменит текст в ePass. Значит, для ePass нужно сгенерировать событие OnChange, в котором очищаем текст у ePass2:

    ePass2.Text:= '';

    Теперь подумаем вот о чем. Окно редактора пользователей мы будем вызывать из главной формы, там же мы будем сохранять изменения, если администратор нажмет кнопку "Подтвердить" в окне редактора. А как из главной формы узнать - хочет ли администратор сохранить изменения, или нет? Введем глобальную переменную izmen в модуль редактора пользователей:

    var
      fEditor: TfEditor;
      izmen: Boolean;

    Теперь для формы редактора пользователей сгенерируем событие OnShow, в котором сразу пропишем:

    izmen:= False;

    В событии нажатия на кнопку "Подтвердить" вписываем код:

    izmen:= True;
      Close;

    А при нажатии на кнопку "Отменить" просто закрываем форму:

    Close;

    Теперь, если администратор закроет эту форму иначе, чем нажатием на "Подтвердить", мы ничего предпринимать не будем. Осталось сделать проверки на то, ввел ли администратор логин нового пользователя, совпадают ли пароли в ePass и ePass2? Для этого сгенерируем событие OnClose для формы редактора:

    {при закрытии формы}
    procedure TfEditor.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
      //если изменения не нужно делать, просто выходим:
      if not izmen then Exit;
      //Иначе делаем проверку. Если нет имени пользователя:
      if eUser.Text = '' then begin
        ShowMessage('Введите имя пользователя!');
        Action := caNone; //запрещаем покидать форму
        eUser.SetFocus; //переводим фокус на имя пользователя
      end;
      //Если пароль не '11111111111111111111', делаем проверку
      //совпадает ли ePass и ePass2:
      if ePass.Text <> '11111111111111111111' then
        if ePass.Text <> ePass2.Text then begin
          ShowMessage('Введите правильный пароль!');
          Action := caNone; //запрещаем покидать форму
          //очистим ePass и ePass2:
          ePass.Text:= '';
          ePass2.Text:= '';
          ePass.SetFocus; //переводим фокус на имя пользователя
        end;
    end;

    Комментарии достаточно подробны, чтобы вы смогли разобраться с кодом.

    С этой формой закончили, вернемся на главную форму. Не забудем сразу же командой File -> Use Unit подключить к ней модуль Editor.

    Добавление нового пользователя

    Реализуем добавление нового пользователя. Сгенерируйте обработчик нажатия на кнопку "Добавить". Код обработчика следующий:

    {Нажали "Добавить"}
    procedure TfMain.bAddUserClick(Sender: TObject);
    begin
      //вначале очистим данные, которые могут быть
      //в редакторе пользователей:
      fEditor.eUser.Text:= '';
      fEditor.ePass.Text:= '';
      fEditor.ePass2.Text:= '';
      fEditor.eName.Text:= '';
      fEditor.eMiddle.Text:= '';
      fEditor.eLast.Text:= '';
      //теперь показываем форму:
      fEditor.ShowModal;
      //если изменений делать не нужно, просто выходим:
      if not Editor.izmen then Exit;
      //иначе готовим компонент IBSS к созданию нового пользователя.
      IBSS.Active:= True;
      try
        //теперь вводим данные нового пользователя:
        IBSS.UserName:= fEditor.eUser.Text;
        IBSS.Password:= fEditor.ePass.Text;
        IBSS.FirstName:= fEditor.eName.Text;
        IBSS.MiddleName:= fEditor.eMiddle.Text;
        IBSS.LastName:= fEditor.eLast.Text;
        //вызываем метод AddUser, который добавляет пользователя:
        IBSS.AddUser;
      finally
        IBSS.Active:= False; //закрываем компонент
      end; //try
      //перечитываем информацию о пользователях:
      ReloadUsers;
    end;

    Поскольку пользователь у нас новый, данных на него пока никаких нет. Значит, вначале мы очистим все Edit на форме редактора, и только потом покажем ее.

    Когда администратор закончит работу с формой, и закроет ее, начинает работу следующая строка процедуры:

    //если изменений делать не нужно, просто выходим:
      if not Editor.izmen then Exit;

    То есть, если администратор не нажал кнопку "Подтвердить", мы просто выйдем, ничего не делая. Если же процедура работает дальше, то нужно сохранить нового пользователя:

    IBSS.Active:= True;
      try
        //теперь вводим данные нового пользователя:
        IBSS.UserName:= fEditor.eUser.Text;
        IBSS.Password:= fEditor.ePass.Text;
        IBSS.FirstName:= fEditor.eName.Text;
        IBSS.MiddleName:= fEditor.eMiddle.Text;
        IBSS.LastName:= fEditor.eLast.Text;
        //вызываем метод AddUser, который добавляет пользователя:
        IBSS.AddUser;
    …

    Как видите, при сохранении данных мы используем свойства компонента IBSecurityService UserName (логин), Password (пароль), FirstName (имя), MiddleName (отчество) и LastName (фамилия). Есть еще два свойства, которые мы не использовали, и которые устанавливаются по умолчанию в ноль: UserID (идентификатор пользователя) и GroupID (идентификатор группы), обычно программисты не задействуют эти параметры. Физически новый пользователь добавляется методом AddUser. В заключение процедуры мы отключаем IBSecurityService и вызываем процедуру ReloadUsers для обновления данных.

    Сохраните проект, скомпилируйте его и попробуйте добавить нового пользователя. Пользователь должен появиться в окне ListBox, кроме того, он должен быть виден и в утилите IBConsole, в разделе Users дерева серверов.

    Редактирование пользователя

    Редактирование выбранного пользователя вызывается кнопкой "Редактировать". Сгенерируйте этот обработчик. Вот его код:

    {Нажали "Редактировать"}
    procedure TfMain.bModifyUserClick(Sender: TObject);
    var i: Integer; //счетчик
    begin
      //если не один пользователь не выбран, просто выходим:
      if LB1.ItemIndex = -1 then begin
        ShowMessage('Выберите пользователя!');
        Exit;
      end
      //если выбран SYSDBA, тоже выходим:
      else if LB1.Items[LB1.ItemIndex] = 'SYSDBA' then begin
        ShowMessage('Редактировать пользователя SYSDBA нельзя.');
        Exit;
      end; //else if
      //иначе редактируем
      //установим у IBSecurityService выбранного пользователя:
      IBSS.Active:= True;
      IBSS.UserName:= LB1.Items[LB1.ItemIndex];
      //найдем в IBSS индекс нужного пользователя:
      for i:= 0 to IBSS.UserInfoCount - 1 do
        if IBSS.UserInfo[i].UserName =
           LB1.Items[LB1.ItemIndex] then break;
      //теперь i содержит индекс пользователя
      //заполняем редактор пользователей данными:
      fEditor.eUser.Text:= IBSS.UserInfo[i].UserName;;
      //раз пользователь уже есть, выводим единички в качестве пароля:
      fEditor.ePass.Text:= '11111111111111111111';
      fEditor.ePass2.Text:= '11111111111111111111';
      fEditor.eName.Text:= IBSS.UserInfo[i].FirstName;
      fEditor.eMiddle.Text:= IBSS.UserInfo[i].MiddleName;
      fEditor.eLast.Text:= IBSS.UserInfo[i].LastName;
      //теперь покажем редактор:
      fEditor.ShowModal;
      //если изменений делать не нужно, просто выходим:
      if not Editor.izmen then Exit;
      //иначе сохраняем их:
      try
        //Имя пользователя менять не будем.
        //Сохраним пароль, если там не единички:
        if fEditor.ePass.Text <> '11111111111111111111' then
          IBSS.Password:= fEditor.ePass.Text;
        //Далее сохраняем остальные параметры:
        IBSS.FirstName:= fEditor.eName.Text;
        IBSS.MiddleName:= fEditor.eMiddle.Text;
        IBSS.LastName:= fEditor.eLast.Text;
        //сохраняем изменения физически методом ModifyUser:
        IBSS.ModifyUser;
      finally
        IBSS.Active:= False;
      end; //try
      //перечитываем информацию о пользователях:
      ReloadUsers;
    end;

    Подробно разбирать весь код не будем, так как комментариев достаточно, но остановимся на некоторых моментах. Строка

    IBSS.UserName:= LB1.Items[LB1.ItemIndex];

    делает в компоненте IBSS текущим пользователем того, который в данный момент выбран в списке ListBox. Затем мы находим индекс этого пользователя в списке. Зачем это нужно? Дело в том, что данные о пользователе можно вытащить только через свойство UserInfo. Другими словами, если мы хотим посмотреть отчество пользователя, то IBSS. MiddleName вернет пустую строку, даже если отчество есть, а IBSS. UserInfo [i]. MiddleName вернет отчество. При сохранении отчества нужно напротив, обращаться к IBSS. MiddleName. Физическое изменение данных реализуется методом ModifyUser. Сохраните проект, скомпилируйте его и попробуйте отредактировать какого либо существующего пользователя, например, добавив к нему дополнительные данные (фамилию, имя, отчество). После сохранения результата, в утилите IBConsole должны отобразиться эти данные.

    Удаление пользователя

    Это самая простая операция с IBSecurityService. Ее код:

    {Удаление пользователя}
    procedure TfMain.bDeleteUserClick(Sender: TObject);
    var s: String; //для формирования строки
    begin
      //если не один пользователь не выбран, просто выходим:
      if LB1.ItemIndex = -1 then begin
        ShowMessage('Выберите пользователя!');
        Exit;
      end;
      //иначе продолжаем. формируем запрос:
      s:= 'Вы действительно желаете удалить пользователя ' +
        LB1.Items[LB1.ItemIndex] + '?';
      //попросим подтверждения. выходим, если не подтвердили:
      if Application.MessageBox(PChar(s), 'Удаление пользователя',
        MB_YESNOCANCEL+MB_ICONQUESTION) <> IDYES then Exit;
      //если не вышли, значит удаляем пользователя
      IBSS.Active:= True;
      try
        //делаем пользователя текущим:
        IBSS.UserName:= LB1.Items[LB1.ItemIndex];
        //удаляем его методом DeleteUser:
        IBSS.DeleteUser;
      finally
        IBSS.Active:= False;
      end; //try
      //перечитываем информацию о пользователях:
      ReloadUsers;
    end;

    Тут все почти так же, как в предыдущих примерах, за исключением нового метода DeleteUser, который физически удаляет пользователя. Перед окончательной компиляцией программы не забудьте сделать текущей вкладку "Резервное копирование базы данных", чтобы именно она открывалась при загрузке программы.

    Вкладка InterBase Admin Палитры компонентов содержит еще целый ряд сервисных компонентов. Однако в рамках лекции просто невозможно разобрать их все, мы рассмотрели работу только основных компонентов. Остальные работают похожим образом, вы сможете самостоятельно разобраться с ними, изучив справку Delphi по компонентам.

    Страницы:

    Данная лекция позволит изучить способы администрирования баз данных программно, из проекта Delphi. Ведь может случиться, что вы будете писать программу и проектировать базу данных "на заказ", для какой-нибудь небольшой фирмы. Такая фирма, как правило, не может позволить себе иметь в штате профессионального программиста и (или) администратора. В подобных фирмах роль администратора берет на себя один из сотрудников, который является более-менее опытным пользователем. Обучить такого пользователя премудростям и тонкостям работы с утилитой IBConsole непросто, еще сложнее научить его пользоваться утилитами командной строки. И здесь нам на помощь приходит набор компонентов с вкладки " InterBase Admin " палитры компонентов, который входит в стандартный состав Delphi. С помощью этих компонентов можно создать простое приложение, которое будет выполнять административные функции с базами данных, и с которым сможет работать любой пользователь.

    Разработка программы AdminIB

    Итак, наша задача: создать приложение, которое позволит регулярно делать резервную копию базы first.gdb как в ручном, так и в автоматическом режиме. Также приложение, в случае необходимости, должно уметь восстанавливать базу данных из резервной копии, и сохранять лог-файлы о копировании и восстановлении. В случае каких-то проблем, администратор вышлет вам эти логи, по которым вы сможете определить проблему. Также программа должна уметь добавлять новых пользователей, удалять или редактировать старых. Разумеется, пользователя SYSDBA мы удалять не позволим.

    Убедимся, что сервер InterBase у нас работает, и загружаем Delphi. Выделим форму, в свойство Name которой впишем fMain, а в свойство Caption - "Администрирование базы данных First.gdb ". Сохраним проект в отдельную папку, модулю дадим имя Main, а проекту в целом - AdminIB. Неплохо было бы сразу установить свойство BorderStyle в bsDialog, чтобы программа не могла менять размеры, а свойство Position - в poDesktopCenter.

    Реализация резервного копирования

    Переходим на вкладку Win32 Палитры компонентов, находим и устанавливаем на форму компонент PageControl (каждая задача будет на своей вкладке). Переименуем полученный PageControl1 в PC1 для краткости кода, а свойству Align присвоим значение alClient.

    Щелкаем по компоненту PC1 правой кнопкой и выбираем команду New Page - создалась новая вкладка. Выделим ее, и в свойстве Name вместо TabSheet1 напишем TSh1, а в свойстве Caption - "Резервное копирование базы данных". Будьте внимательны: очень легко ошибиться, и вместо листа TabSheet выделить весь компонент PageControl.

    Примерный вид вкладки смотрите на рисунке:

    (рис 27.1) Вкладка "Резервное копирование базы данных"

    Вначале установим на лист панель Panel1, свойству Align которого присвоим значение alTop. Очистим свойство Caption. На панель поместим две радиокнопки RadioButton. В свойству Name первой кнопки укажем RB1, у второй - RB2. А в свойстве Caption, соответственно, "Ручное копирование" и "Автоматическое копирование". Свойство Checked компонента RB2 переведем в True.

    Ниже поместим простую кнопку Button, которую переименуем в bStartBackup, а в свойстве Caption напишем "Начать копирование". Свойство Enabled переведем в False. Поскольку по умолчанию, программа будет выполнять автоматическое копирование, эта кнопка будет недоступна. Как только мы выделим радиокнопку "Ручное копирование", сразу вернем доступность этой кнопке.

    В правой части панели поместите компонент Label. Чтобы компонент мог содержать многострочный текст, свойство AutoSize переведите в False, а свойство WordWrap - в True. В свойстве Caption напишите текст "Время запуска автоматического резервного копирования:", и подгоните размеры, как на рисунке.

    Ниже находятся еще два Label с текстом "Часы:" и "Минуты:". Рядом с ними располагаются два компонента SpinEdit с вкладки Samples Палитры компонентов. Первый переименуйте в SE1, второй - в SE2. У первого в параметре MinValue оставьте 0, в параметре MaxValue укажите 23, а в параметре Value поставьте 1 (по умолчанию, автокопирование будет начинаться в час ночи, когда сотрудников на работе нет). У второго SpinEdit параметры такие: MinValue = 0, MaxValue = 59, Value = 0. Вместо этих компонентов можно было бы использовать один MaskEdit с маской "99:99", но, к сожалению, сам компонент не проверяет правильность ввода значений часов и минут, придется делать это самим. Поэтому остановимся на компонентах SpinEdit, которые позволят пользователю вводить только целые числа в заданном диапазоне.

    Под панелью установите компонент Memo. Свойство Align установите в alClient, свойство ScrollBars в ssVertical, и не забудьте очистить от текста свойство Lines.

    Далее главное: нужно добавить два не визуальных компонента - Timer с вкладки System и IBBackupService с вкладки InterBase Admin. Компоненты не визуальные, их можно расположить на любом месте, первый будет запускать копирование автоматически, когда подойдет указанное время, второй - осуществлять само копирование. У Timer свойство Interval установите 60000, чтобы таймер срабатывал один раз в минуту.

    Компонент IBBackupService предназначен для создания резервных копий базы данных InterBase. Этот компонент позволяет делать различные настройки резервного копирования, в зависимости от того, какие параметры в свойстве Options включены.

    У IBBackupService свойство Name для краткости переименуйте в IBBS. На этом приготовления вкладки закончены, приступим к кодированию. Все резервные копии должны попадать в одну папку, например, C:\DataBases\Backup. Эта папка физически должна существовать на диске. При этом мы не сможем делать копии с одним и тем же именем, ведь операционная система не позволит создавать файл с таким же именем. Конечно, можно перед очередным копированием удалять старую копию, но это тоже не выход - что, если новая резервная копия будет создана с ошибками, то есть, в базе данных появились нарушения? Можно было бы восстановить ее из старой копии, но для этого ее не нужно удалять! Выход: добавлять к имени файла дату и время его создания, тогда можно делать сколько угодно копий, и у всех будут разные имена.

    Для этого в разделе Private модуля главной формы опишем функцию GetName:

    private
        { Private declarations }
        function GetName():String;

    Установите курсор на название функции и нажмите <Ctrl+Shift+C>, чтобы сгенерировать саму функцию. Вот ее код:

    {Функция возвращает префикс имени файла в виде yyyymmdd_hhmmss_ }
    function TfMain.GetName: String;
    var
      ye, mo, da : Word; //для даты
      ho, mi, se, ms : Word; //для времени
      st : String[2]; //для добавления нуля, например 05
    begin
      DecodeDate(Date, ye, mo, da); //декодируем на составные дату
      DecodeTime(Time, ho, mi, se, ms); //декодируем время
      //теперь собираем строку:
      Result:= IntToStr(ye); //добавили год
      //получаем и добавляем месяц:
      st:= IntToStr(mo);
      if Length(st) = 1 then st:= '0' + st; //если 1 символ, добавим спереди 0
      Result := Result + st;
      //получаем и добавляем день:
      st:= IntToStr(da);
      if Length(st) = 1 then st:= '0' + st;
      Result := Result + st + '_';
      //теперь получаем и добавляем час:
      st:= IntToStr(ho);
      if Length(st) = 1 then st:= '0' + st;
      Result := Result + st;
      //получаем и добавляем минуты:
      st:= IntToStr(mi);
      if Length(st) = 1 then st:= '0' + st;
      Result := Result + st;
      //получаем и добавляем секунды:
      st:= IntToStr(se);
      if Length(st) = 1 then st:= '0' + st;
      Result := Result + st + '_';
      //теперь функция вернет префикс имени файла, например:
      //20100205_010012_
    end;

    Здесь мы декодировали на составные части дату и время. Год уже имеет 4 цифры, поэтому его дополнительно обрабатывать не нужно. А вот месяц, день, час, минута или секунда могут состоять из одной цифры. Чтобы не запутаться, делаем проверку:

    st:= IntToStr(mo);
      if Length(st) = 1 then st:= '0' + st; //если 1 символ, добавим спереди  0
      Result := Result + st;

    В переменную st мы получаем номер месяца. Если этот номер состоит из одной цифры, перед ней добавим '0'. И в конце прибавим результат в переменную Result. Таким же образом мы проверяем и остальные данные. Эта функция гарантирует нам не только уникальность имени файла, но и правильную сортировку файлов по дате. К тому же по имени сразу видно - когда был создан файл.

    Теперь сделаем доступной кнопку bStartBackup, если пользователь отметил радиокнопку RB1 (Ручное копирование). Для этого сгенерируем для RB1 событие onClick:

    {Щелкнули по "Ручное копирование"}
    procedure TfMain.RB1Click(Sender: TObject);
    begin
      //если отмечено Ручное копирование, делаем
      //кнопку доступной:
      if RB1.Checked then
        bStartBackup.Enabled:= True;
    end;

    Точно также сделаем кнопку недоступной, если щелкнули по RB2 (Автоматическое копирование). Для этого сгенерируем событие onClick для RB2:

    {Щелкнули по "Автоматическое копирование"}
    procedure TfMain.RB2Click(Sender: TObject);
    begin
      //если отмечено Автоматическое копирование, делаем
      //кнопку недоступной:
      if RB2.Checked then
        bStartBackup.Enabled:= False;
    end;

    Соображаем дальше. Копирование у нас будет начинаться либо по нажатию кнопки, либо по срабатыванию таймера, в зависимости от того, какая радиокнопка выделена. Значит, чтобы дважды не писать один и тот же код, создадим процедуру резервного копирования, которую будем вызывать из двух мест. Поскольку процедура будет напрямую работать с компонентом IBBS, вначале объявим ее в разделе private, после функции GetName:

    private
        { Private declarations }
        function GetName():String;
        procedure BackupCopy;

    Установите курсор на процедуру и нажмите <Ctrl+Shift+C>, чтобы сгенерировать тело процедуры. Ее код:

    {Процедура реализации резервного копирования}
    procedure TfMain.BackupCopy;
    var s : String; //для получения префикса файла
    begin
      //получим префикс:
      s:= GetName();
      //очистим Memo1, если там что то есть:
      Memo1.Clear;
      //задаем параметры компонента IBBackupService:
      IBBS.ServerName := 'MyServ';
      IBBS.LoginPrompt:= False;
      IBBS.Params.Add('user_name=sysdba');
      IBBS.Params.Add('password=masterkey');
      IBBS.Active := True;
      //начинаем копирование
      try
        IBBS.Verbose:= True;
        IBBS.DatabaseName:= 'c:\DataBases\first.gdb';
        IBBS.BackupFile.Clear;
        IBBS.BackupFile.Add('c:\DataBases\Backup\' + 
    s + 'first.gbk');
        IBBS.ServiceStart;
        //пока не дошли до конца, записываем параллельно лог в Memo1:
        while not IBBS.Eof do
          Memo1.Lines.Add(IBBS.GetNextLine);
      finally
        IBBS.Active:= False;
      end;
      //сохраним лог в файл:
      Memo1.Lines.SaveToFile('c:\DataBases\Backup\' +  
    s + 'first.log');
    end;

    Здесь мы вначале получили в переменную префикс имени файла. Зачем это нужно? Нам требуется:

  • Сделать резервную копию.
  • Сохранить лог копирования в файл.
  • Эти действия будут производиться в разное время, поэтому чтобы префикс копии совпадал с префиксом лог-файла, мы и сохраняем его в переменную s:

    //получим префикс:
      s:= GetName();

    Далее мы очищаем Memo1, если вдруг там уже был текст. После чего приступаем к настройкам параметров компонента IBBS:

    IBBS.ServerName := 'MyServ';
      IBBS.LoginPrompt:= False;
      IBBS.Params.Add('user_name=sysdba');
      IBBS.Params.Add('password=masterkey');
      IBBS.Active := True;

    Тут все достаточно прозрачно. Поскольку копирование будет производиться на серверном ПК, то будет использован локальный адрес, следовательно, свойство ServerName мы можем назвать, как угодно. Если бы сервер был сетевым, пришлось бы писать сетевое имя этого ПК, или его IP -адрес. При этом следует обратить внимание на свойство Protocol. Это свойство позволяет выбрать протокол соединения. Поскольку программа будет загружаться там же, где установлен InterBase и где хранится база данных, оставляет значение по умолчанию - Local. Если программа будет работать с удаленным сервером, то здесь нужно будет выбрать один из сетевых протоколов, обычно выбирают TCP.

    Далее с помощью LoginPrompt мы заявляем, что не нужно запрашивать имя пользователя и пароль (кто же введет эти данные в час ночи, когда запустится авто-копирование?). Затем мы вводим в свойство Params имя пользователя SYSDBA и его пароль. Если вы изменили пароль (а это обязательно нужно сделать в реальной системе), вместо " masterkey " укажите свой пароль. В конце мы делаем IBBS активным.

    Само копирование мы помещаем в блок try…finally…end, поскольку копирование может быть и неудачным, если БД разрушена:

    try
        IBBS.Verbose:= True;
        IBBS.DatabaseName:= 'c:\DataBases\first.gdb';
        IBBS.BackupFile.Clear;
        IBBS.BackupFile.Add('c:\DataBases\Backup\' + s + 'first.gbk');
        IBBS.ServiceStart;
        //пока не дошли до конца, записываем параллельно лог в Memo1:
        while not IBBS.Eof do
          Memo1.Lines.Add(IBBS.GetNextLine);
      finally
        IBBS.Active:= False;
      end;

    Здесь свойство Verbose (многословность) определяет - будет ли выводиться лог резервного копирования. При значении True лог ведется, иначе - нет. Нам нужно, чтобы лог создавался.

    В свойство DatabaseName помещаем адрес и имя нашей рабочей базы данных.

    Свойство BackupFile должно содержать имя файла (или файлов) резервной копии. У нас база данных состоит из одного файла, и резервная копия также будет состоять из одного файла, поэтому прежде, чем мы поместим туда имя очередной резервной копии, это свойство нужно очистить. Затем формируем имя файла из адреса, префикса и самого имени " first.gbk". А затем стартуем копирование методом ServiceStart. Заметим, что установка True в свойстве Active компонента не начинает резервное копирование, а только делает компонент активным.

    Параллельно копированию в компонент Memo1 помещаем отчет о текущем действии, от начала копирования до конца. Метод GetNextLine компонента IBBackupService возвращает очередную строку лог-отчета. Когда копирование окончено, делаем компонент IBBS неактивным.

    И в самом конце процедуры записываем полученный в Memo1 лог в файл с таким же именем, как резервная копия, но расширением *.log:

    //сохраним лог в файл:
      Memo1.Lines.SaveToFile('c:\DataBases\Backup\' +  
    s + 'first.log');

    Теперь для ручного копирования нам осталось сгенерировать процедуру нажатия на кнопку "Начать копирование", откуда вызовем процедуру BackupCopy:

    {Нажали на кнопку "Начать копирование"}
    procedure TfMain.bStartBackupClick(Sender: TObject);
    begin
      BackupCopy;
    end;

    Попробуйте выполнить ручное копирование. Напомню, что сервер InterBase должен быть запущен, а папка " C:\DataBases\Backup " должна физически существовать на диске. Как только вы нажмете на кнопку, копирование начнется. Закончится оно примерно такой строкой:

    gbak: closing file, committing and finishing. 38912 bytes written.

    Зайдите в папку Backup - там должна появиться пара файлов с одинаковым именем и расширениями *.gbk и *.log. Это и есть наши резервная копия и лог-файл.

    Займемся автоматическим копированием. Выделите таймер и сгенерируйте для него событие onTimer, которое будет срабатывать каждую минуту ( Interval =60000):

    {Сработал таймер}
    procedure TfMain.Timer1Timer(Sender: TObject);
    var
      ho, mi, se, ms : Word; //для времени
    begin
      //если включено ручное копирование, просто выходим:
      if RB1.Checked then Exit;
      //иначе декодируем время
      DecodeTime(Time, ho, mi, se, ms); //декодируем время
      //если нужный час и нужная минута, начинаем копирование:
      if (ho = SE1.Value) and (mi = SE2.Value) then BackupCopy;
    end;

    Вначале мы проверяем, не включено ли ручное копирование. Если включено, то сразу выходим, ничего не делая. Затем мы снова декодируем время, чтобы посмотреть - совпадают ли данные в компонентах SpinEdit с текущими часом и минутой. И если совпадают, вызываем процедуру BackupCopy.

    Заметим, что таймер может сработать не сразу, как системные часы покажут нужное время. Ведь в таймере учитываются и секунды. Значит, если копирование у нас должно начаться в 01:00, а когда программа была запущена, ваше системное время показывало, скажем, 25 секунд, то и таймер сработает в 01:00:25. Что, в принципе, не столь важно.

    Сохраните проект, скомпилируйте и запустите. Попробуйте установить время авто-копирования на минуту-другую больше, чем показывают ваши системные часы (должна быть выделена радиокнопка "Автоматическое копирование"). Дождитесь, когда системное время сравняется с указанным. Возможно, придется подождать еще несколько секунд, после чего резервное копирование начнется автоматически. Опять появится пара файлов в папке Backup. Таким образом, достаточно выделить радиокнопку RB2 (у нас она выделена по умолчанию), и не выключать на сервере программу, чтобы обеспечить регулярное резервное копирование базы данных. Разумеется, серверный ПК с InterBase, вашей базой данных и администраторской программой должен быть включен в назначенное время (серверные компьютеры обычно работают в режиме 24/7, то есть 24 часа в сутки, 7 дней в неделю).

    Напоследок заметим, что компонент IBBackupService имеет сложное раскрывающееся свойство Options, которое имеет следующие переключатели:

    Переключатели свойства Options компонента IBBackupService
    Переключатель Описание
    IgnoreChecksums Игнорировать ошибки контрольных сумм. Применяется обычно при попытке восстановить поврежденную БД.
    IgnoreLimbo Игнорировать ошибки двухфазного подтверждения limbo -транзакций. Также применяется при восстановлении поврежденной БД.
    MetadataOnly При включенном переключателе в резервную копию попадут только метаданные. Можно использовать, если вы хотите получить пустую БД с таблицами, индексами, генераторами, триггерами и проч., но без записей.
    NoGarbageCollection Не делать сборку мусора во время резервного копирования.
    OldMetadataDesc Делать резервную копию в старом стиле. Как правило, не применяется.
    NonTransportable Делать резервную копию, непереносимую на другие версии InterBase (а также Firebird и Yaffil ).
    ConvertExtTables Преобразует внешние файлы во внутренние таблицы. Также может применяться при восстановлении поврежденной БД.

    Как видно из таблицы, резервное копирование можно делать с некоторыми параметрами. Мы не использовали этих параметров, то есть, делали копию с параметрами "по умолчанию". В результате получили обычную резервную копию, для чего и нужна наша программа. Если же доведется ремонтировать базу данных, то делать это нужно утилитами gfix и gback, которые предоставляют гораздо больше возможностей, чем указанные переключатели.

    Реализация восстановления из резервной копии

    Определимся с задачей. Нам нужно:

  • Получить имя файла резервной копии, из которой будем делать восстановление.
  • Выбрать необходимые параметры восстановления.
  • Произвести само восстановление. Причем восстанавливать будем в отдельную папку, например C:\DataBases\Restore, которая уже должна существовать на диске. Восстанавливаемой БД перед именем присвоим префикс из даты и времени, как и у резервного копирования, также будем создавать лог-файл. Если восстановление будет успешным, потом администратор сможет переименовать полученную БД и перенести ее в рабочую папку средствами Windows или файловым менеджером.
  • Для восстановления базы данных сделаем новую вкладку. Выделите компонент PC1 ( PageControl ). Чтобы выделить именно компонент, а не страницу на нем, нужно щелкать правее ярлычка вкладки. Щелкнем по нему правой кнопкой и выберем команду New Page. В свойстве Name новой вкладки вместо TabSheet1 напишем TSh2, а в свойстве Caption - "Восстановление базы". Внешний вид вкладки представлен на рисунке ниже:

    (рис 27.2) Вкладка "Восстановление базы"

    Здесь у нас будет:

  • Одна простая панель Panel.
  • Три компонента Label.
  • Один Edit.
  • Одна панель GroupBox.
  • Шесть флажков CheckBox.
  • Один SpinEdit.
  • Один ComboBox.
  • Две простых кнопки.
  • Компонент Memo.
  • Компонент IBRestoreService из вкладки InterBase Admin. Компонент предназначен для восстановления базы данных InterBase из резервной копии. Имеет достаточно широкие возможности настроек.
  • Диалог OpenDialog.
  • Сделаем дизайн вкладки, как на рисунке 27.2. Вначале ставим панель Panel, свойство Align = alTop, свойство Caption очищаем, высоту делаем немного меньше половины страницы.

    На панель сверху устанавливаем Label, Edit и Button. В свойстве Caption компонента Label выводим текст "Выберите резервную копию, из которой будем восстанавливать БД:", свойство Text компонента Edit1 очищаем. Кнопку делаем квадратной, размерами под Edit1: Height = Width = 21. В свойстве Caption помещаем "…". Свойство Name переименуем в bOpenBak.

    Ниже на панель помещаем панельку GroupBox, в свойстве Caption которой напишем "Параметры восстановления". Внутрь GroupBox1 поместим шесть компонентов CheckBox, которые назовем соответственно CB1CB6 (сверху - вниз по первой колонке, и сверху - вниз по второй). В свойстве Caption этих флажков поместим соответственно текст:

    "Отключить индексы"

    "Отключить теневые копии"

    "Отключить проверку внеш.ключей"

    "Записывать данные для таблиц отдельно"

    "Создать новую БД"

    "Заполнить страницы на 100%"

    Подгоните размеры компонентов, как на рисунке. У флажка CB5 свойство Checked переводим в True, так как нам по умолчанию нужно создать новую базу данных. Кроме того, включим еще и флажок CB2, чтобы не восстанавливать теневые копии, если они уже есть. Когда база данных будет восстановлена, ей всегда можно назначить новую теневую копию. А попытка восстановления БД с теневой копией приведет к ошибке, если сначала не удалить этот shadow -файл.

    Ниже помещаем Label с текстом "Размер буфера страницы:", а рядом помещаем SpinEdit, свойство Name которого переименуем в SE3. В справочнике Delphi размер буфера страницы у компонента IBRestoreService указан как 3000 килобайт. Не будем спорить с производителями Delphi и по умолчанию установим именно этот размер. Для этого сделаем следующие настройки SE3:

    MinValue = 1000 (минимальное значение)
    MaxValue = 5000 (максимальное значение)
    Increment = 100 (шаг приращения/убывания)
    Value = 3000 (текущее значение)

    Ниже разместим еще один Label с текстом "Размер страницы:", а правее поместим ComboBox. Переименуем его свойство Name в CBox1, откроем редактор свойства Items и впишем построчно следующие возможные размеры страниц в InterBase:

    1024
    2048
    4096
    8192

    В свойство Text поместим рекомендованное значение 8192.

    Правее этих компонентов поместим обычную кнопку, в свойстве Caption которой напишем "Начать восстановление БД", а свойство Enable переведем в False (мы сделаем кнопку доступной, когда пользователь выберет какую-нибудь резервную копию, из которой требуется сделать восстановление). Свойство Name кнопки переименуйте в bStartRestore.

    Ниже панели Panel поместим Memo (оставим имя по умолчанию Memo2 ). Свойство Align переведем в alClient, в свойстве ScrollBars выберем ssVertical, чтобы появилась вертикальная прокрутка, а также очистим текст в свойстве Lines.

    Займемся не визуальными компонентами. OpenDialog переименуем в OD1. Откроем редактор свойства Filter и в столбец Filter Name впишем "Резервные копии БД", а в столбец Filter - "*.gbk". В свойстве InitialDir укажем папку с нашими резервными копиями "C:\DataBases\Backup". Также в свойстве DefaultExt укажем расширение файлов по умолчанию "gbk" (без точки).

    Перейдем к компоненту IBRestoreService. Для краткости обращений переименуем его свойство Name в IBRS. Как видите, этот компонент также имеет сложное раскрывающееся свойство Options, в котором можно настроить следующие переключатели:

    Переключатели свойства Options компонента IBRestoreService
    Переключатель Описание
    DeactivateIndexes Сделать индексы в базе данных неактивными.
    NoShadow При восстановлении БД не восстанавливать ее теневые копии.
    NoValidityCheck Отключить проверку внешних ограничений ( Foreign Key ). Делать это нужно осторожно, предварительно сделав копию метаданных.
    OneRelationAtATime Восстановить метаданные и данные для каждой таблицы по одной записи за раз. Бывает полезно, если обычное восстановление не получается из-за разрушенных данных или нарушенных внешних ограничений.
    Replace При значении True можно будет восстанавливать копию поверх существующей базы данных. Крайне не рекомендуется.
    CreateNew При True создается новый файл базы данных.
    UseAllSpace При восстановлении заполнять страницы базы на 100%, вместо положенных по умолчанию 80%. Чтобы не ухудшить производительность БД, делать это стоит лишь с базами "только для чтения".

    Для шести из семи этих переключателей у нас имеются соответствующие компоненты ComboBox, мы опустили только переключатель Replace, так как восстанавливать резервную копию, затирая существующую базу данных, не рекомендуется. Кроме того, имя восстанавливаемой БД, благодаря префиксу, будет уникальным, так что восстановить копию поверх БД все равно не получится. Если программа не предусматривает настройки параметров восстановления, то эти параметры можно было бы указать прямо в свойстве Options компонента. Однако наша программа предусматривает настройку, поэтому параметры восстановления будем устанавливать программно, в зависимости от того, какие CheckBox включены.

    Займемся кодированием. Сгенерируйте процедуру нажатия на кнопку "…". Ее код:

    {Щелкнули по кнопке "..."}
    procedure TfMain.bOpenBakClick(Sender: TObject);
    begin
       //если диалог состоялся:
       if OD1.Execute then begin
          //укажем выбранный файл
          Edit1.Text:= OD1.FileName;
          //делаем доступной кнопку "Начать восстановление БД"
          bStartRestore.Enabled:= True;
       end; //if
    end;

    Остается реализовать само восстановление. Сгенерируйте процедуру нажатия на кнопку "Начать восстановление БД". Ее код:

    {Щелкнули по кнопке "Начать восстановление БД"}
    procedure TfMain.bStartRestoreClick(Sender: TObject);
    var st: String;
    begin
      //получим префикс для восстановленной БД и лога:
      st := GetName();
      //Очистим Memo2:
      Memo2.Clear;
      //устанавливаем начальные параметры:
      IBRS.ServerName:= 'MyServ';
      IBRS.LoginPrompt:= False;
      IBRS.Params.Add('user_name=sysdba');
      IBRS.Params.Add('password=masterkey');
      IBRS.Active:= True;
      //начинаем восстановление
      try
        IBRS.Verbose:= True; //вести лог
        IBRS.Options:= []; //очистим свойство Options
        //далее мы включаем в Options переключатель,
        //если его ComboBox включен:
        if CB1.Checked then IBRS.Options :=
               IBRS.Options + [DeactivateIndexes];
        if CB2.Checked then IBRS.Options :=
               IBRS.Options + [NoShadow];
        if CB3.Checked then IBRS.Options :=
               IBRS.Options + [NoValidityCheck];
        if CB4.Checked then IBRS.Options :=
               IBRS.Options + [OneRelationAtATime];
        if CB5.Checked then IBRS.Options :=
               IBRS.Options + [CreateNewDB];
        if CB6.Checked then IBRS.Options :=
               IBRS.Options + [UseAllSpace];
        //устанавливаем размер буфера страницы:
        IBRS.PageBuffers:= SE3.Value;
        IBRS.PageSize:= StrToInt(CBox1.Text); //размер самой страницы
        //очищаем свойство с именем БД, которая должна получиться:
        IBRS.DatabaseName.Clear;
        //устанавливаем новое имя с префиксом:
        IBRS.DatabaseName.Add('c:\DataBases\Restore\' + 
    st + 'first.gdb');
        //очищаем свойство с именем резервной копии:
        IBRS.BackupFile.Clear;
        //устанавливаем новое имя из OpenDialog:
        IBRS.BackupFile.Add(OD1.FileName);
        //стартуем восстановление:
        IBRS.ServiceStart;
        //параллельно ведем лог, пока не наступит конец восстановления:
        while not IBRS.Eof do
          Memo2.Lines.Add(IBRS.GetNextLine);
      finally
        IBRS.Active:= False; //деактивируем компонент
      end; //try
      //запишем полученный лог в файл:
      Memo2.Lines.SaveToFile('c:\DataBases\Restore\' + 
    st + 'first.log');
      //теперь очистим Edit1 и снова сделаем кнопку недоступной:
      Edit1.Text:= '';
      bStartRestore.Enabled:= False;
    end;

    Как видно из листинга, работа компонента IBRestoreService мало отличается от работы IBBackupService, который мы рассматривали достаточно подробно. Имеется два новых свойства компонента: PageBuffers и PageSize. Первое свойство устанавливает буфер страницы - размер кэша, или размер оперативной памяти, который будет использоваться для считывания данных. Если размер данных окажется больше, чем размер буфера, программа будет обращаться к жесткому диску. В справочнике Delphi в примере работы компонента IBRestoreService указан размер буфера 3000 килобайт.

    Свойство PageSize устанавливает размер страниц восстанавливаемой базы данных. Как уже говорилось в прошлых лекциях, при восстановлении БД из резервной копии можно изменить размер страниц этой БД. Например, если старая версия БД имела размер 4096, то при восстановлении можно установить размер страниц 8192.

    Код процедуры подробно комментирован, и в дополнительных пояснениях не нуждается. Сохраните проект, скомпилируйте его и попробуйте сделать восстановление из какой-нибудь резервной копии БД, которую делали ранее. Восстановление закончится, когда в Memo2 появится строка " gbak: finishing, closing, and going home ", а в папке C:\DataBases\Restore появятся два файла: восстановленная база данных и лог-файл.

    Работа с пользователями

    Определимся с задачей:

  • Добавление нового пользователя.
  • Изменение данных существующего пользователя (пароль, имя, отчество, фамилия). Если пользователь - SYSDBA, редактирование запрещаем. Ведь при резервном копировании и восстановлении мы жестко задавали имя SYSDBA и пароль masterkey (если вы изменили этот пароль, значит у вас свой вариант). И если администратор изменит этот пароль, программа не сможет работать, вам придется изменить этот пароль в программе и перекомпилировать ее. Впрочем, в дальнейшем вы можете усложнить программу, добавив в нее, например, работу с ini -файлом (см. лекцию №18 курса "Введение в программирование на Delphi "). В этом случае, вы сможете прописать в ini -файл новый пароль пользователя SYSDBA, если администратор изменит его, и в дальнейшем использовать новый пароль. Если же администратор не будет менять пароля, то можно использовать пароль по умолчанию, " masterkey " (или ваш вариант).
  • Удаление любого пользователя, кроме SYSDBA.
  • Для реализации этой задачи создадим новую вкладку, которую назовем TSh3, а в свойстве Caption напишем "Пользователи". Внешний вид вкладки показан на следующем рисунке:

    (рис 27.3 ) Вкладка "Пользователи"

    Здесь мы установили компонент ListBox, свойство Name которого переименовали в LB1, а свойство Align перевели в alLeft, чтобы компонент занял всю левую часть окна. Измените размеры компонента, как на рисунке.

    В правой части установили три простых кнопки Button. Свойства Name кнопок переименовали в bAddUser, bModifyUser и bDeleteUser, а в свойстве Caption, соответственно, прописали "Добавить", "Редактировать" и "Удалить".

    Кроме того, мы добавили не визуальный компонент IBSecurityService с вкладки InterBase Admin. Компонент для краткости обращения переименовали в IBSS. Этот компонент предназначен для работы с пользователями, зарегистрированными в InterBase, и позволит нам редактировать, добавлять и удалять пользователей.

    Несмотря на кажущуюся простоту окна, реализация работы с пользователями немного сложнее предыдущих примеров. В отличие от компонентов IBBackupService и IBRestoreService, компонент IBSecurityService выполняет не одну, а три задачи, что выражено кнопками в правой части окна. Кроме того, IBSecurityService работает не с нашей базой данных first.gdb, а с системной БД InterBase isc4.gdb, что накладывает некоторые ограничения. Например, имя, отчество и фамилию пользователя не получится вносить русскими буквами, так как isc4.gdb создавалась не в кодировке WIN1251. Кроме того, в целях безопасности, компонент IBSecurityService не выводит существующий пароль пользователя, хотя и позволяет менять его. Компонент позволяет оперировать такими данными, как логин пользователя, его пароль, имя, отчество, фамилия, идентификатор пользователя UserID и идентификатор группы GroupID.

    Для доступа к данным пользователя компонент IBSecurityService имеет свойство UserInfo, которое представляет собой список пользователей. Индекс свойства начинается с 0, как и список строк . UserInfo будут совпадать, это сильно облегчит нашу задачу.

    Первым делом нам нужно при создании главной формы заполнить компонент IBSecurityService свежими данными о пользователях, и обновить этими данными список ListBox.Так как нам то же самое придется делать при добавлении, редактировании и удалении пользователей, в разделе private добавим новую процедуру ReloadUsers:

    private
        { Private declarations }
        function GetName():String;
        procedure BackupCopy;
        procedure ReloadUsers;

    Реализация этой процедуры:

    {Перечитываем данные о пользователях}
    procedure TfMain.ReloadUsers;
    var i: Integer; //для счетчика
    begin
      //вначале очистим ListBox:
      LB1.Clear;
      //заполняем список пользователями:
      IBSS.ServerName:= 'MyServ';
      IBSS.LoginPrompt:= False;
      IBSS.Params.Add('user_name=sysdba');
      IBSS.Params.Add('password=masterkey');
      IBSS.Active:= True;
      try
        IBSS.DisplayUsers; //получаем  информацию о пользователях
        for i:=0 to IBSS.UserInfoCount -1 do
          LB1.Items.Add(IBSS.UserInfo[i].UserName);
      finally
        IBSS.Active:= False;
      end; //try
    end;

    Здесь все достаточно прозрачно. Вначале очищаем ListBox от возможных старых записей. Затем настраиваем сервис IBSecurityService, так же, как ранее настраивали компоненты IBRestoreService и IBBackupService.

    Метод DisplayUsers получает в компонент всю информацию о пользователях, после чего она доступна в свойстве UserInfo. Свойство UserInfoCount показывает общее количество пользователей. Подсвойство UserName свойства UserInfo содержит логин текущего пользователя. Строкой

    LB1.Items.Add(IBSS.UserInfo[i].UserName);

    мы добавляем в ListBox имя очередного пользователя. Теперь нужно вызвать эту процедуру при создании формы. Для этого сгенерируйте для главной формы событие OnCreate, в котором сделаем вызов этой процедуры:

    ReloadUsers;

    Проверить результат можно, скомпилировав и загрузив программу. При загрузке, в окне ListBox должны отобразиться зарегистрированные пользователи.

    Далее нам придется сделать новую форму - редактор данных пользователей. Выберите команду File -> New -> Form. Форму назовите fEditor, в свойстве Caption напишите "Редактирование данных пользователя", сохраните новый модуль под именем Editor.

    Свойство BorderStyle формы переведем в bsDialog, чтобы пользователь не мог менять размеры окна, а в свойстве Position выберем poMainFormCenter.

    Внешний вид новой формы показан на рисунке ниже:

    (рис 27.4 ) Форма fEditor

    Итак, у нас имеется:

  • Два компонента GroupBox.
  • Шесть компонентов Label.
  • Шесть компонентов Edit.
  • Две кнопки Button.
  • В свойстве Caption первого GroupBox напишем "Обязательные данные", а второго - "Дополнительные данные".

    На верхний GroupBox помещаем три Label и заполняем текст, как на рисунке. Затем помещаем три Edit. У первого свойство Name переименуем в eUser, у второго - ePass, и у третьего - ePass2.

    Выделите одновременно (с нажатой Shift ) компоненты ePass и ePass2. В свойстве PasswordChar укажите символ "*", чтобы скрыть реальный пароль. Не забудьте удалить текст из свойства Text всех компонентов Edit.

    На нижний GroupBox также помещаем три Label и заполняем текст, как на рисунке. Затем помещаем три Edit. У первого свойство Name переименуем в eName, у второго - eMiddle, и у третьего - eLast. Очищаем свойство Text у всех Edit.

    Ниже расположим две кнопки, которые переименуем соответственно, в bAccept и bCancel, а свойства Caption - как на рисунке.

    Теперь немного подумаем. Эта форма будет показываться в двух случаях: при добавлении нового пользователя, и при редактировании существующего. В первом случае пользователь еще не имеет пароля, во втором - имеет. Причем при редактировании существующего пользователя, администратор может как сменить старый пароль, так и не трогать его. Как узнать, менялся ли пароль существующего пользователя, если мы не имеем возможности вывести его с помощью IBSecurityService? Вариант: при редактировании существующего пользователя поместим в ePass и ePass 2 какой-нибудь длинный пароль "по умолчанию", например, двадцать единичек. А при сохранении результата редактирования будем смотреть - если в ePass пароль "по умолчанию", значит сохранять пароль не нужно. В случае же добавления нового пользователя ePass и ePass2 заполняться не будут.

    Пойдем дальше. Как уже говорилось, база данных isc4.gdb не поддерживает кодировку win1251, следовательно, русские буквы в нее помещать нельзя. Значит, при вводе текста во все шесть компонентов Edit придется делать проверку - что вводит пользователь. Если английские буквы или цифры, или BackSpace, то ничего не делаем, иначе запрещаем символ. Чтобы шесть раз не писать один и тот же код, в разделе private создадим функцию KeyCan:

    private
        { Private declarations }
        function KeyCan(c:Char):Boolean;

    Код реализации функции представлен ниже:

    {Проверяем допустимость символа}
    function TfEditor.KeyCan(c: Char): Boolean;
    begin
      case c of
        'A'..'z': Result:= True; //англ. буквы разрешаем
        '0'..'9': Result:= True; //цифры разрешаем
        #8 : Result:= True; //BackSpace разрешаем
      else Result:= False; //остальное запрещаем
      end; //case
    end;

    Теперь нам нужно реализовать проверку во всех шести Edit. Выделите первый, и сгенерируйте для него событие OnKeyPress. В процедуру поместите только одну строку:

    if not KeyCan(Key) then Key:= #0;

    То же самое проделайте с оставшимися пятью Edit. Таким образом, мы реализовали проверку на ввод допустимых символов. Если пользователь попытается ввести что-нибудь, кроме английских букв, цифр или BackSpace, то его действия будут игнорироваться.

    Пойдем дальше. Если мы редактируем нового пользователя, то в ePass и ePass2 у нас будет текст в двадцать единиц. Если администратор захочет изменить пароль, то он изменит текст в ePass. Значит, для ePass нужно сгенерировать событие OnChange, в котором очищаем текст у ePass2:

    ePass2.Text:= '';

    Теперь подумаем вот о чем. Окно редактора пользователей мы будем вызывать из главной формы, там же мы будем сохранять изменения, если администратор нажмет кнопку "Подтвердить" в окне редактора. А как из главной формы узнать - хочет ли администратор сохранить изменения, или нет? Введем глобальную переменную izmen в модуль редактора пользователей:

    var
      fEditor: TfEditor;
      izmen: Boolean;

    Теперь для формы редактора пользователей сгенерируем событие OnShow, в котором сразу пропишем:

    izmen:= False;

    В событии нажатия на кнопку "Подтвердить" вписываем код:

    izmen:= True;
      Close;

    А при нажатии на кнопку "Отменить" просто закрываем форму:

    Close;

    Теперь, если администратор закроет эту форму иначе, чем нажатием на "Подтвердить", мы ничего предпринимать не будем. Осталось сделать проверки на то, ввел ли администратор логин нового пользователя, совпадают ли пароли в ePass и ePass2? Для этого сгенерируем событие OnClose для формы редактора:

    {при закрытии формы}
    procedure TfEditor.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
      //если изменения не нужно делать, просто выходим:
      if not izmen then Exit;
      //Иначе делаем проверку. Если нет имени пользователя:
      if eUser.Text = '' then begin
        ShowMessage('Введите имя пользователя!');
        Action := caNone; //запрещаем покидать форму
        eUser.SetFocus; //переводим фокус на имя пользователя
      end;
      //Если пароль не '11111111111111111111', делаем проверку
      //совпадает ли ePass и ePass2:
      if ePass.Text <> '11111111111111111111' then
        if ePass.Text <> ePass2.Text then begin
          ShowMessage('Введите правильный пароль!');
          Action := caNone; //запрещаем покидать форму
          //очистим ePass и ePass2:
          ePass.Text:= '';
          ePass2.Text:= '';
          ePass.SetFocus; //переводим фокус на имя пользователя
        end;
    end;

    Комментарии достаточно подробны, чтобы вы смогли разобраться с кодом.

    С этой формой закончили, вернемся на главную форму. Не забудем сразу же командой File -> Use Unit подключить к ней модуль Editor.

    Добавление нового пользователя

    Реализуем добавление нового пользователя. Сгенерируйте обработчик нажатия на кнопку "Добавить". Код обработчика следующий:

    {Нажали "Добавить"}
    procedure TfMain.bAddUserClick(Sender: TObject);
    begin
      //вначале очистим данные, которые могут быть
      //в редакторе пользователей:
      fEditor.eUser.Text:= '';
      fEditor.ePass.Text:= '';
      fEditor.ePass2.Text:= '';
      fEditor.eName.Text:= '';
      fEditor.eMiddle.Text:= '';
      fEditor.eLast.Text:= '';
      //теперь показываем форму:
      fEditor.ShowModal;
      //если изменений делать не нужно, просто выходим:
      if not Editor.izmen then Exit;
      //иначе готовим компонент IBSS к созданию нового пользователя.
      IBSS.Active:= True;
      try
        //теперь вводим данные нового пользователя:
        IBSS.UserName:= fEditor.eUser.Text;
        IBSS.Password:= fEditor.ePass.Text;
        IBSS.FirstName:= fEditor.eName.Text;
        IBSS.MiddleName:= fEditor.eMiddle.Text;
        IBSS.LastName:= fEditor.eLast.Text;
        //вызываем метод AddUser, который добавляет пользователя:
        IBSS.AddUser;
      finally
        IBSS.Active:= False; //закрываем компонент
      end; //try
      //перечитываем информацию о пользователях:
      ReloadUsers;
    end;

    Поскольку пользователь у нас новый, данных на него пока никаких нет. Значит, вначале мы очистим все Edit на форме редактора, и только потом покажем ее.

    Когда администратор закончит работу с формой, и закроет ее, начинает работу следующая строка процедуры:

    //если изменений делать не нужно, просто выходим:
      if not Editor.izmen then Exit;

    То есть, если администратор не нажал кнопку "Подтвердить", мы просто выйдем, ничего не делая. Если же процедура работает дальше, то нужно сохранить нового пользователя:

    IBSS.Active:= True;
      try
        //теперь вводим данные нового пользователя:
        IBSS.UserName:= fEditor.eUser.Text;
        IBSS.Password:= fEditor.ePass.Text;
        IBSS.FirstName:= fEditor.eName.Text;
        IBSS.MiddleName:= fEditor.eMiddle.Text;
        IBSS.LastName:= fEditor.eLast.Text;
        //вызываем метод AddUser, который добавляет пользователя:
        IBSS.AddUser;
    …

    Как видите, при сохранении данных мы используем свойства компонента IBSecurityService UserName (логин), Password (пароль), FirstName (имя), MiddleName (отчество) и LastName (фамилия). Есть еще два свойства, которые мы не использовали, и которые устанавливаются по умолчанию в ноль: UserID (идентификатор пользователя) и GroupID (идентификатор группы), обычно программисты не задействуют эти параметры. Физически новый пользователь добавляется методом AddUser. В заключение процедуры мы отключаем IBSecurityService и вызываем процедуру ReloadUsers для обновления данных.

    Сохраните проект, скомпилируйте его и попробуйте добавить нового пользователя. Пользователь должен появиться в окне ListBox, кроме того, он должен быть виден и в утилите IBConsole, в разделе Users дерева серверов.

    Редактирование пользователя

    Редактирование выбранного пользователя вызывается кнопкой "Редактировать". Сгенерируйте этот обработчик. Вот его код:

    {Нажали "Редактировать"}
    procedure TfMain.bModifyUserClick(Sender: TObject);
    var i: Integer; //счетчик
    begin
      //если не один пользователь не выбран, просто выходим:
      if LB1.ItemIndex = -1 then begin
        ShowMessage('Выберите пользователя!');
        Exit;
      end
      //если выбран SYSDBA, тоже выходим:
      else if LB1.Items[LB1.ItemIndex] = 'SYSDBA' then begin
        ShowMessage('Редактировать пользователя SYSDBA нельзя.');
        Exit;
      end; //else if
      //иначе редактируем
      //установим у IBSecurityService выбранного пользователя:
      IBSS.Active:= True;
      IBSS.UserName:= LB1.Items[LB1.ItemIndex];
      //найдем в IBSS индекс нужного пользователя:
      for i:= 0 to IBSS.UserInfoCount - 1 do
        if IBSS.UserInfo[i].UserName =
           LB1.Items[LB1.ItemIndex] then break;
      //теперь i содержит индекс пользователя
      //заполняем редактор пользователей данными:
      fEditor.eUser.Text:= IBSS.UserInfo[i].UserName;;
      //раз пользователь уже есть, выводим единички в качестве пароля:
      fEditor.ePass.Text:= '11111111111111111111';
      fEditor.ePass2.Text:= '11111111111111111111';
      fEditor.eName.Text:= IBSS.UserInfo[i].FirstName;
      fEditor.eMiddle.Text:= IBSS.UserInfo[i].MiddleName;
      fEditor.eLast.Text:= IBSS.UserInfo[i].LastName;
      //теперь покажем редактор:
      fEditor.ShowModal;
      //если изменений делать не нужно, просто выходим:
      if not Editor.izmen then Exit;
      //иначе сохраняем их:
      try
        //Имя пользователя менять не будем.
        //Сохраним пароль, если там не единички:
        if fEditor.ePass.Text <> '11111111111111111111' then
          IBSS.Password:= fEditor.ePass.Text;
        //Далее сохраняем остальные параметры:
        IBSS.FirstName:= fEditor.eName.Text;
        IBSS.MiddleName:= fEditor.eMiddle.Text;
        IBSS.LastName:= fEditor.eLast.Text;
        //сохраняем изменения физически методом ModifyUser:
        IBSS.ModifyUser;
      finally
        IBSS.Active:= False;
      end; //try
      //перечитываем информацию о пользователях:
      ReloadUsers;
    end;

    Подробно разбирать весь код не будем, так как комментариев достаточно, но остановимся на некоторых моментах. Строка

    IBSS.UserName:= LB1.Items[LB1.ItemIndex];

    делает в компоненте IBSS текущим пользователем того, который в данный момент выбран в списке ListBox. Затем мы находим индекс этого пользователя в списке. Зачем это нужно? Дело в том, что данные о пользователе можно вытащить только через свойство UserInfo. Другими словами, если мы хотим посмотреть отчество пользователя, то IBSS. MiddleName вернет пустую строку, даже если отчество есть, а IBSS. UserInfo [i]. MiddleName вернет отчество. При сохранении отчества нужно напротив, обращаться к IBSS. MiddleName. Физическое изменение данных реализуется методом ModifyUser. Сохраните проект, скомпилируйте его и попробуйте отредактировать какого либо существующего пользователя, например, добавив к нему дополнительные данные (фамилию, имя, отчество). После сохранения результата, в утилите IBConsole должны отобразиться эти данные.

    Удаление пользователя

    Это самая простая операция с IBSecurityService. Ее код:

    {Удаление пользователя}
    procedure TfMain.bDeleteUserClick(Sender: TObject);
    var s: String; //для формирования строки
    begin
      //если не один пользователь не выбран, просто выходим:
      if LB1.ItemIndex = -1 then begin
        ShowMessage('Выберите пользователя!');
        Exit;
      end;
      //иначе продолжаем. формируем запрос:
      s:= 'Вы действительно желаете удалить пользователя ' +
        LB1.Items[LB1.ItemIndex] + '?';
      //попросим подтверждения. выходим, если не подтвердили:
      if Application.MessageBox(PChar(s), 'Удаление пользователя',
        MB_YESNOCANCEL+MB_ICONQUESTION) <> IDYES then Exit;
      //если не вышли, значит удаляем пользователя
      IBSS.Active:= True;
      try
        //делаем пользователя текущим:
        IBSS.UserName:= LB1.Items[LB1.ItemIndex];
        //удаляем его методом DeleteUser:
        IBSS.DeleteUser;
      finally
        IBSS.Active:= False;
      end; //try
      //перечитываем информацию о пользователях:
      ReloadUsers;
    end;

    Тут все почти так же, как в предыдущих примерах, за исключением нового метода DeleteUser, который физически удаляет пользователя. Перед окончательной компиляцией программы не забудьте сделать текущей вкладку "Резервное копирование базы данных", чтобы именно она открывалась при загрузке программы.

    Вкладка InterBase Admin Палитры компонентов содержит еще целый ряд сервисных компонентов. Однако в рамках лекции просто невозможно разобрать их все, мы рассмотрели работу только основных компонентов. Остальные работают похожим образом, вы сможете самостоятельно разобраться с ними, изучив справку Delphi по компонентам.

    Вернуться к учебному плану