Toyota DB

Инвариант
Не помню, с чего все началось, но задача показалась мне интересной. Как я понял, у фирмы Toyota есть какая-то программа, видимо для сервисных центров, по диагностике неисправностей. Многоязычная. Один из языков - английский. В какой-то старой версии был и русский. Клиенту хотелось перевести на русский новую.

Как водится, файлы данных были упакованы и зашифрованы и это было дополнительной трудностью к переводу. Я полюбопытствовал, а кто занимался предыдущей версией. Оказалось, тогда это была инициатива фирмы. У них, разумеется, проблем с расшифровкой не было.

Погоняв немного программу, я согласился на эту работу, получил аванс и взялся за дело. С точки зрения пользователя, программа имела ряд экранных форм, текст в которых зависел от локализации. Когда программе требовалось отобразить какой-либо текст, она передавала в расшифровщик его ID и, в зависимости от языка, получала расшифрованную строку. Довольно быстро выяснилось, что все интересное для меня находится в трех файлах данных. Одна версия этих файлов была для английского языка, другая - для русского.

Посидев недолго в отладчике, я нашел уязвимость: за распаковку и расшифровку отвечала та же самая DLL, в которой была подходящая функция. После некоторых манипуляций, смысл которых остался мне неясен, возможно, это был род обфускации, преобразованный ID запрашиваемой строки попадал в эту функцию, внутри что-то хитро искалось и возвращалась нужная строка. Как ни странно, но вся огромная база распаковывалась целиком. В этот-то момент и следовало снять дамп - отдельно для каждого из используемых языковых файлов. Таким образом, уже в первый вечер у меня в руках оказались все "сырые" хэш-таблицы с текстами. Но, разумеется, это было только началом работы. Таблицы следовало редактировать, тексты - перевести. Единственным вариантом такой работы было написание собственного редактора, синхронизирующего две версии текста - английскую и русскую.

Думаю, здесь я принял неверное решение. Вместо использования какой-либо готовой базы данных, например, того же SQLite, я решил писать собственную. Доводов в пользу такого решения было, в тот момент, два. Во-первых, программа предполагалась к продаже, то есть должна была быть "чистой". Это исключало использование каких-либо "пиратских" решений, например, Btrieve. Во-вторых, то, что можно было использовать без ограничений было либо неэффективно, либо сложно. Сегодня, с учетом того, сколько сил и времени ушло у меня на отладку собственных решений, я, наверняка, взглянул бы на это по-другому.

За основу я решил взять старый формат DBF. Во-первых, предельно простой и быстрый, во-вторых, с кучей уже готовых вьюверов, так что не придется писать собственный. В модифицированный a la DBF файл было решено засунуть все поля фиксированного размера, а текст поместить в Memo-файле. Стандартный DBase III memo показался мне неэкономным, так что я использовал собственную модификацию с выравниванием на двойное слово. Индексы тоже были самописные, B+Tree, по мотивам статей Jan'а Jannink'а. В свое время, они тоже стоили мне немало бессонных ночей.

Редактор представлял из себя двухоконную оболочку: в одном окошке оригинальный английский текст, в другом - русский перевод, с набором сервисных элементов. Среди прочего, предусматривалось два словаря. Один должен был составляться пользователем, по мере работы. Второй, стандартный, был большой политехнический англо-русский словарь (168,000+ entries) для использования в "трудных случаях". Для словарей был задействован тот же самый DBF-like формат.

Вся база делилась на две части: константную не требовавшую обновления, и перезаписываемую. Обе части имели тот же самый формат, но использовались различно. Константная часть напрямую отображалась в память, что обеспечивало максимальную скорость доступа, с обновляемой частью работа шла обычным образом.

Изначально предполагалось, что будет вестись список удаленных записей и автообновление индексов, но вскоре после выплаты аванса, клиент пропал, проект был остановлен и автообновление базы так и осталось недоделанным. Останки проекта можно найти в моей директории на github.

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

Понятно, что работа такого объема не могла быть выполнена в одиночку, тем более, непрофессионалом. Предполагалось, что весь текст удастся разделить на части во-первых, по файлам и, во-вторых, по диапазону ID. Каждый участник должен был бы взять свой диапазон ID для перевода.

При написании редактора пришлось решать и еще одну непростую задачу. Оказалось, что некоторые тексты с разными ID дословно повторяются, часто не одну сотню раз. Глупо было бы каждый раз переводить их заново. Требовалось автоматически выявить и убрать, а после перевода - восстановить назад текстовые повторы.

Эта задача была решена с помощью двойной индексации: на каждый текстовый файл создавалось два индекса, один - уникальный, не допускавший дублей и второй с дубликатами. Используя эти индексы, текстовый файл отображался в редуцированную версию, не содержащую дублей и файл расширения, по которому после перевода требовалось восстановить оригинальный файл.

Сама идея двухоконного синхронизируемого редактора кажется мне удачной для любого переводчика. Я знаю про профессиональные решения с базой текстов и синхронизацией версий за сотни долларов, но это простое "любительское" решение, подходящее "домашнему" пользователю. К сожалению, больше мне не представился случай применить его где-либо еще.