SxGeoSharp. Интерфейс на C# для базы данных SypexGeo. -Часть IV. Получение данных.

Получение кода страны по ID (для БД SxGeoCountry)

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

Поиск в справочнике по ID

Фактически, перебор всего справочника, по некому полю, понадобился только для страны (справочника стран). Формат БД и работа с ним не предполагает загрузку справочников в виде таблиц, например в 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 Record = Unpacker.Unpack(buf,
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(Dictionary data,
            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 IPInfo = null;
private Dictionary IPInfoTypes = null;

В первый будем помещать данные, во второй - тип C#.

Для получения типов данных сделаем функцию-оболочку:

public Dictionary GetIPInfoTypes()
{
    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_");

Функция полностью здесь

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *