Закончим с 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_");