I. Вступление
Интернет всё больше и больше входит в нашу жизнь. Мы часто
сидим и нажимаем кнопку "обновить", ожидая новых данных на заветной страничке.
Но ведь надо и работать когда-то, поэтому задачу мониторинга странички с
новостями мы поручим Delphi, а сами будем работать (или заниматься другим
полезным делом – жизнь ведь прекрасна). Если что – всё будет скачано и
представлено в удобном для нас виде.
В качестве "подопытного кролика" я выбрал многим известный
цитатник рунета – bash.org.ru. Программа будет забирать с главной странички
цитаты, и складывать их в базу. А с цитатами в базе можно делать что угодно.
Эта статья отличается от моих других тем, что я даю сразу
готовое приложение и комментирую интересные моменты.
II. Общие замечания
Внешний интерфейс программы не претендует на награду. Я над
ним особенно не задумывался, так как основная задача в другом. Интерфейс
сделаете сами, если он вам нужен.
Это приложение построено по модульно-объектной технологии (мне
больше нравится название "Разделяй и властвуй"). То-есть, для каждой сущности
есть объект, и эти объекты взаимодействуют между собой. Так как связи и
зависимости между ними минимальные, то можно легко их заменять и модифицировать.
Чтобы легче было искать место в коде, на которое я ссылаюсь, я
использую специальные маркеры. В тексте статьи они выглядят так {текст}.
Чтобы найти код, который соответствует ему, нужно сделать следующее. Перейти в
Delphi, выбрать в меню View » ToDo List. Найти там
соответствующий текст и кликнуть дважды. А по тексту программы вы увидите их как
{ TODO -oVadim -cView : текст }.
III. Класс базы данных (БД)
Обзор класса
Начнём разбор с класса, который обслуживает базу данных.
Только объекты этого класса имеют доступ к базе данных, только они знают как с
ней работать. Остальные классы не должны ничего знать о базе данных.
В самом верху юнита BBasa объявлен абстрактный класс
TBBasaCustom {B1}. Этот класс имеет только один метод для
добавления цитат в базу, да и тот абстрактный. Рабочий класс будет наследоваться
от него. Это сделано специально, чтобы из того объекта, который будет добавлять
данные, нельзя было бы обратиться к другим методам, и ему было всё равно, какие
ещё методы есть у класса. Они ему не нужны. Это выглядит дивно, но позже станет
понятно.
В этом же юните объявлен класс TBBasa {B2}.
Этот класс наследуется от нашего класса и реализовывает всю нужную
функциональность, а именно:
Открывает БД при своем создании. Если файл базы отсутствует –
он его создаст. {B6}
Умеет добавлять цитату, так как реализовывает абстрактный
метод Add. {B3}
Умеет найти в базе цитату по номеру – метод GetQuotes.
{B4}
Посмотрите внимательно на public методы. Ни один из них не
расскажет о том, как и где хранятся данные. Поэтому, никто не мешает переписать
методы и хранить цитаты в текстовом файле или нескольких файлах. Остальной код
этого никак не почувствует. Ощущаете силу такого подхода к программированию?
В качестве базы данных я использую SQLite.
Рассмотрим некоторые методы детальнее.
Метод Add
Этот метод {B3} получает два параметра –
Number и Text – соответственно номер цитаты и её текст. Перед добавлением
вначале проверяет, а нет ли уже цитаты в базе. Для этого он делает запрос на
выборку цитат с заданным номером. Если цитата не найдена (количество найденных
строк равно нулю), то добавляем в базу. Конечно, теоретически может быть, что в
базе будет несколько записей с одним номером (программная ошибка, к примеру), но
я не усложняю. В конце метода есть вызов SendCommand. Это оповещение интерфейса
о том, что есть новые цитаты. Как это работает – будет рассказано ниже.
Метод GetQuotes
Этот метод {B4} по заданному номеру цитаты
находит её и возвращает, предварительно удалив спецсимволы. Если цитата не
найдена – возвращает пустую строку (оно логично). Интересно также то, что этот
метод обращается к другому, который запрашивает "сырой текст" из базы.
IV. Класс потока
Обзор
Данный класс занимается основной работой – он скачивает
главную страничку, парсит, и отдаёт поштучно цитаты объекту БД. Вся реализация
находится в юните BTread.
Как видно из кода, у этого класса только три публичных метода
– конструктор, деструктор и запрос на обновление. А больше ему и не надо.
Рассмотрим как всё это работает. Через конструктор
{T1} при создании класса мы передаём ему ссылку на объект БД, чтобы
потом иметь возможность добавлять цитаты.
После того, как поток будет запущен, начинает выполняться
метод Execute {T2}. Он предельно прост – пока нас не завершили,
повторять: скачать, распарсить и подождать. Все действия специально разделены,
чтобы быть независимыми.
Скачивание страницы
Метод скачать страничку GetText {T3} просто
возвращает в виде строки содержимое страницы. Если вам по каким-то
идеологическим причинам не нравятся компоненты Indy, просто замените здесь на
свой код, и всё. Сам метод хорош ещё тем, что показывает, как в RunTime
создавать компонент и назначать методы.
Парсинг
Вытягивать цитаты {T4} наиболее просто с
помощью регулярных выражений. Откуда я взял регулярное выражение – не
спрашивайте. Учитесь сами его составлять. Больше в этом методе ничего
интересного.
Сон
Между скачиванием цитат надо делать паузу. Так как мы
находимся в потоке, то свободно можем писать Sleep. Никакого "подвисания
интерфеса" не будет. Но эта функция плоха тем, что поток сложно пробудить из
такого сна. И если мы захотим в этот момент закрыть программу, то нам придётся
ждать, пока функция не отработает.
Именно по этой причине я использую немного другой метод
{T5}. Я использую функцию WaitForSingleObject, которая, в
отличие от Sleep, может также завершиться, если "сработал объект-событие",
который ей передали. Сработал – это перешёл с SetEvent.
Поэтому, если нужно обновить раньше времени или завершить
работу потока, мы "включаем событие" и метод Sleep завершается досрочно.
V. Основное приложение
Обзор
Теперь, когда мы рассмотрели все вспомогательные объекты, пора
их объединить и заставить работать.
Так как основная функциональность у нас уже реализована, то
код предельно прост. Первым делом мы создаём два объекта – один для БД, другой
для потока. Делаем это в OnCreate формы {M2}. В OnDestroy
соответственно уничтожаем. Обратите внимание на последовательность создания и
удаления. Она важна.
Кнопка "обновить" тоже не должна вызвать осложнений. Она
просто вызывает метод Update у объекта потока.
Обратная связь
Помните вызов SendCommand у объекта БД? Эта функция находится
в юните BMessage. Сама она очень проста – она просто вызывает WinAPI функцию
SendMessage для посылки сообщения главной форме, а хендл главной формы берётся
из Application.MainForm.Handle. Главная форма принимает эти сообщения, и уже
самостоятельно обращается к нужным объектам.
Рассмотрим для примера передачу сообщения о новой цитате.
После того, как цитата была сохранена, посылается сообщение MY_NEW_QUOTES и в
качестве параметра передаётся номер цитаты. Второй параметр нам не нужен, и мы
пишем туда 0.
В класса формы объявлен метод {M7} для
перехвата этого сообщения. Получив его, мы обращаемся к объекту БД, по
номеру получаем само сообщение и добавляем в Memo.
VI. Выводы
Конечно, это очень сырой пример, и на выставку он не годится.
Но он хорош тем, что является заготовкой для различных программ подобного типа –
собирать новости, выкачивать галереи картинок, собирать данные.
И помните, в некоторых случаях подобные программы могут не
дружить с законом! Будьте внимательны, и не нарушайте.
Файлы к статье
здесь
|