Закончим с SxGeoCountry, в которой нет справочников и информации кроме ID страны.
В оригинальном исходнике есть массив с кодами стран, а позиция в массиве, как раз ID страны из базы. Соответственно, надо просто сходить в нужный элемент массива за ISO-кодом страны.
Сделаем соответствующую функцию private string IdToIso(uint ID)
Тут все просто, мы должны либо взять определенное количество байт из массива (на это дело есть bSubstr), либо прочиатать данные с диска. На это у нас есть поток SxStream и все позиции — start — откуда начинать читать, seek — сдвиг в файле, и max — максимальная длина записи в байтах.
//читает данные из справочников
private byte[] ReadDBDirs(long start, uint seek, uint max, byte[] db)
{
byte[] buf = new byte[max];
if (DatabaseMode == SxGeoMode.MemoryAllMode) //вся БД в памяти
{
//в db - массив байт с нужной базой
buf = bSubstr(db, seek, max);
}
else //справочники на диске
{
SxStream.Seek(start + seek,SeekOrigin.Begin);
SxStream.Read(buf, 0, (int)max);
}
return buf;
}
Фактически, перебор всего справочника, по некому полю, понадобился только для страны (справочника стран). Формат БД и работа с ним не предполагает загрузку справочников в виде таблиц, например в DataSet. И это так бы и осталось куском кода для отладки, если бы не справочник стран. В справочнике стран смещения, по которому ищутся данные нет. Есть только ID, сохраненный так же в country_id справочника городов.
В оригинальном исходнике поиск по справочнику стран так же не работал, попытка вызова оригинальной функции getCountry приводила к ошибке.
Потому пришлось изобретать велосипед.
Чтение с диска:
1. Становимся на начало справочника со странами.
2. Заводим переменную под фактически прочитанное количество байт (Readed)
3. Переменную, в которой сохраняется сколько байт надо прочитать далее (int NextRead = (int)Header.CountrySize;) Изначально ей присваивается значение, равное максимальному размеру записи для страны.
4. Далее заводим распаковщик «Универсального формата упаковки данных»
SxGeoUnpack Unpacker = new SxGeoUnpack(Header.pack_country, Header.DBEncoding);
5. Далее в цикле последовательно читаем нужное количество байт, пока справочник стран не кончится:
while (Readed < Header.CountrySize - 1)
5.1. Читаем запись
//читаем запись
byte[] buf = ReadBytes(NextRead);
if (buf == null)
{
return null;
}
5.2 Заводим переменную, под длину реально прочитанных данных.
int RealLength = 0;
5.3 "Распаковываем" прочитанное:
Dictionary
out RealLength);
5.4. Проверяем, не нашли ли нужный ID страны:
if ((byte)Record["id"] == CountryID)
{
return Record;
}
5.5. Сохраняем количество фактически прочитанных байт:
//Сохраняем количество фактических байт записи
Readed += RealLength;
5.6. Заводим переменную, хранящую значение, насколько нужно отступить для чтения следующей записи:
long backstep = 0;
5.7. Если на чтение файла не хватило максимальной длины записи (т.е. мы запись прочитали по максимальной длине, но файл кончился).
if (TableStart + Readed + Header.MaxCountry > FileSize)
5.7.1. То вычисляем "отскок", т.е. байты в файловом потоке, откуда начинается и где кончается прошлая запись (ее размер у нас есть):
backstep = -NextRead + RealLength
5.7.2. Вычисляем количество байт, которое нужно дочитать:
NextRead = (int)(FileSize - TableStart - Readed);
5.7.3. Иначе просо вычисляем количество байт и точку, с которой надо читать следущую запись:
backstep = -NextRead + RealLength;
5.8. Становимся на начало записи:
Seek(backstep, SeekOrigin.Current);
5.9. Идем к пункту 5.
Данный функционал реализован в функции GetCountryDisk(byte CountryID).
Функция поиска по ID в памяти не сильно отличается, там просто не надо делать "отскок". Код функции GetCountryMem(byte CountryID) здесь
Функция AddData() добавляет данныкв выходные словари IPInfo и IPInfoTypes, при этом функция проверяет, имеются ли во входном ассоциативном массиве ненужные поля, если имеются, то функция их удаляет, копируя в выходной массив только нужные поля:
private void AddData(Dictionarydata, Dictionary dataTypes, string prefix) { //удаляем лишние поля из ответа foreach (string remove in ignore_fields) { data.Remove(remove); } if (RemoveRU) { foreach (string remove in ignore_fields_ru) { data.Remove(remove); } }
Финальный ответ мы будем хранить в словарях (так проще будет в дальнейшем обрабатывать данные).
Когда мы создавали класс, были созданы две переменных. Вот они и понадобились:
private Dictionary
private Dictionary
В первый будем помещать данные, во второй - тип C#.
Для получения типов данных сделаем функцию-оболочку:
public DictionaryGetIPInfoTypes() { return IPInfoTypes; }
А далее разберем основную функцию:
1. Создаем временные словари для исходных данных:
Dictionary<string, object> data_city = new Dictionary<string, object> ();
Dictionary<string, object> data_country = new Dictionary<string, object>();
Dictionary<string, object> data_region = new Dictionary<string, object>();
2. Проверяем, открыта ли БД:
if (!IsOpen)
{
ErrorMessage = "Database not open.";
return null;
}
3. Проверяем, есть ли на входе IP адрес:
if (!IPConverter.IsIP(IP)) //проверяем IP ли это)
{
ErrorMessage = IP+" is not valid IP address.";
return null;
}
4. Получаем ID или смещение в файле:
//получаем ID IP-адреса
uint ID = SearchID(IP);
5. Создаем переменные для хранения ответа:
//создаем переменные для хранения ответа
IPInfo = new Dictionary
IPInfoTypes = new Dictionary
6. Добавляем в ответ IP-адрес:
//добавляем сам адрес
IPInfo.Add("ip", IP);
IPInfoTypes.Add("ip", typeof(string));
7. Далее проверяем тип базы данных - если у нас БД SxGeoCountry, то мы можем выдать только код ISO страны:
if (Header.IdLen == 1) //БД SxGeo, ничего кроме ISO-кода вывести не сможем
{
IPInfo.Add("country_iso", IdToIso(ID));
IPInfoTypes.Add("country_iso", typeof(string));
}
8. Создаем распаковщик и объект для расшифровки данных (из справочников):
SxGeoUnpack Unpacker = null;
byte[] buf = null;
9. Если мы нашли только страну - ищем ее в справочнике, и выводим данные:
//если найденный 'ID' < размера справочника городов
//город не найден - только страна
if (ID < Header.CountrySize)
{
Unpacker = new SxGeoUnpack(Header.pack_country, Header.DBEncoding);
buf = ReadDBDirs(Header.countries_begin, ID, Header.MaxCountry,cities_db);
data_country = Unpacker.Unpack(buf);
AddData(data_country, Unpacker.GetRecordTypes(), "country_");
return IPInfo;
}
10. Пытаемся найти информацию о стране по ее ID (если на ранних этапах не свалились/получили информацию):
//о стране по ID страны
data_country = GetCountry((byte)data_city["country_id"]);
11. Далее нам надо проанализировать, какую информацию мы хотим получить. Тип сохранен в перечислении (enum) SxGeoInfoType:
11.1. Если нужна информация только о стране, то добавляем в выходной Dictionary только информацию о стране
AddData(data_country,
SxGeoUnpack.GetRecordTypes(Header.pack_country), "country_");
"country_" - префикс, который будет совмещен с именем элемента выходного Dictionary
11.2. Если (SxGeoInfoType.CountryCity), то нужно получить информацию о стране и городе (если найдено):
AddData(data_country, SxGeoUnpack.GetRecordTypes(Header.pack_country), "country_"); AddData(data_city, Unpacker.GetRecordTypes(), "city_");
11.3 Поиск всех данных (город, страна, регион):
Unpacker = new SxGeoUnpack(Header.pack_region, Header.DBEncoding);
buf = ReadDBDirs(Header.regions_begin,
(uint)data_city["region_seek"], Header.MaxRegion,regions_db);
data_region = Unpacker.Unpack(buf);
AddData(data_country,
SxGeoUnpack.GetRecordTypes(Header.pack_country), "country_");
AddData(data_city,
SxGeoUnpack.GetRecordTypes(Header.pack_city), "city_");
AddData(data_region,
Unpacker.GetRecordTypes(), "region_");