From: Colin Clark Date: Mon, 17 Jun 2019 10:32:22 +0000 (+0100) Subject: Implement downloadable timezone database X-Git-Tag: v1.5~8 X-Git-Url: http://geeqie.org/cgi-bin/gitweb.cgi?p=geeqie.git;a=commitdiff_plain;h=cc9a39fff411f20094b2ea09074bc541d0568330 Implement downloadable timezone database Option in Preferences/General to download timezone database from geeqie.org/downloads Updated source files from https://github.com/BertoldVdb/ZoneDetect Script for generation of timezone database --- diff --git a/HACKING b/HACKING index 4720e8c9..d7e27b75 100644 --- a/HACKING +++ b/HACKING @@ -15,4 +15,7 @@ appropriate to keep translations in sync with the code. Maintainers, don't forget to run make update-po before releases. +The scripts folder contains a script for generating the timezone database. +The database will need updating occasionally. + Translators, please have a look at po/README. diff --git a/doc/docbook/GuideOptionsGeneral.xml b/doc/docbook/GuideOptionsGeneral.xml index cd53064a..9de3fda1 100644 --- a/doc/docbook/GuideOptionsGeneral.xml +++ b/doc/docbook/GuideOptionsGeneral.xml @@ -234,6 +234,15 @@ +
+ Timezone Database + + The timezone database is used to correct exif time and date for UTC offset and Daylight Saving Time as described + here. + This option allows you to install or update the database. An Internet connection is required. + + +
On-line help search diff --git a/scripts/zonedetect/builder.cpp b/scripts/zonedetect/builder.cpp new file mode 100644 index 00000000..af6e65e1 --- /dev/null +++ b/scripts/zonedetect/builder.cpp @@ -0,0 +1,566 @@ +/* + * Copyright (c) 2018, Bertold Van den Bergh (vandenbergh@bertold.org) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the author nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const double Inf = std::numeric_limits::infinity(); + +std::unordered_map alpha2ToName; +std::unordered_map tzidToAlpha2; + +void errorFatal(std::string what) +{ + std::cerr<& output, int64_t valueIn, bool handleNeg = true) +{ + uint64_t value = valueIn * 2; + if(valueIn < 0) { + value = -valueIn * 2 + 1; + } + + if(!handleNeg) { + value = valueIn; + } + + int bytesUsed = 0; + do { + uint8_t byteOut = value & 0x7F; + if(value >= 128) { + byteOut |= 0x80; + } + output.push_back(byteOut); + bytesUsed ++; + value >>= 7; + } while(value); + + return bytesUsed; +} + +int64_t doubleToFixedPoint(double input, double scale, unsigned int precision = 32) +{ + double inputScaled = input / scale; + return inputScaled * pow(2, precision-1); + +} + +struct Point { + Point(double lat = 0, double lon = 0) + { + lat_ = lat; + lon_ = lon; + } + + Point operator-(const Point& p) + { + Point result(lat_ - p.lat_, lon_ - p.lon_); + return result; + } + + std::tuple toFixedPoint(unsigned int precision = 32) + { + int64_t latFixedPoint = doubleToFixedPoint(lat_, 90, precision); + int64_t lonFixedPoint = doubleToFixedPoint(lon_, 180, precision); + + return std::make_tuple(latFixedPoint, lonFixedPoint); + } + + int encodePointBinary(std::vector& output, unsigned int precision = 32) + { + int64_t latFixedPoint, lonFixedPoint; + std::tie(latFixedPoint, lonFixedPoint) = toFixedPoint(precision); + + int bytesUsed = encodeVariableLength(output, latFixedPoint); + bytesUsed += encodeVariableLength(output, lonFixedPoint); + + return bytesUsed; + } + + double lat_; + double lon_; +}; + +struct PolygonData { + Point boundingMin; + Point boundingMax; + std::vector points_; + unsigned long fileIndex_ = 0; + unsigned long metadataId_; + + void processPoint(const Point& p) + { + if(p.lat_ < boundingMin.lat_) { + boundingMin.lat_ = p.lat_; + } + if(p.lon_ < boundingMin.lon_) { + boundingMin.lon_ = p.lon_; + } + if(p.lat_ > boundingMax.lat_) { + boundingMax.lat_ = p.lat_; + } + if(p.lon_ > boundingMax.lon_) { + boundingMax.lon_ = p.lon_; + } + + points_.push_back(p); + } + + PolygonData(unsigned long id): + boundingMin(Inf, Inf), + boundingMax(-Inf, -Inf), + metadataId_(id) + { + } + + long encodeBinaryData(std::vector& output, unsigned int precision = 20) + { + long bytesEncoded = 0; + bool first = true; + int64_t latFixedPoint = 0, lonFixedPoint = 0; + int64_t latFixedPointPrev, lonFixedPointPrev; + uint64_t vertices = 0; + + std::vector tmp; + + int64_t diffLatAcc = 0, diffLonAcc = 0, diffLatPrev = 0, diffLonPrev = 0; + + for(Point& point: points_) { + /* The points should first be rounded, and then the integer value is differentiated */ + latFixedPointPrev = latFixedPoint; + lonFixedPointPrev = lonFixedPoint; + std::tie(latFixedPoint, lonFixedPoint) = point.toFixedPoint(precision); + + int64_t diffLat = latFixedPoint - latFixedPointPrev; + int64_t diffLon = lonFixedPoint - lonFixedPointPrev; + + if(first) { + /* First point is always encoded */ + vertices++; + encodeVariableLength(tmp, latFixedPoint); + encodeVariableLength(tmp, lonFixedPoint); + first = false; + } else { + /* Ignore points that are not different */ + if(!diffLon && !diffLat) { + continue; + } + + if(diffLat != diffLatPrev || diffLon != diffLonPrev) { + /* Encode accumulator */ + vertices++; + encodeVariableLength(tmp, diffLatAcc); + encodeVariableLength(tmp, diffLonAcc); + + diffLatAcc = 0; + diffLonAcc = 0; + } + + diffLatAcc += diffLat; + diffLonAcc += diffLon; + } + + diffLatPrev = diffLat; + diffLonPrev = diffLon; + } + + /* Encode final point */ + vertices++; + encodeVariableLength(tmp, diffLatAcc); + encodeVariableLength(tmp, diffLonAcc); + + encodeVariableLength(output, vertices, false); + std::copy(tmp.begin(), tmp.end(), std::back_inserter(output)); + + return bytesEncoded; + } +}; + +void encodeStringToBinary(std::vector& output, std::string& input) +{ + encodeVariableLength(output, input.size(), false); + for(unsigned int i=0; i usedStrings_; + +struct MetaData { + void encodeBinaryData(std::vector& output) + { + for(std::string& str: data_) { + if(str.length() >= 256) { + std::cout << "Metadata string is too long\n"; + exit(1); + } + + if(!usedStrings_.count(str)) { + usedStrings_[str] = output.size(); + encodeStringToBinary(output, str); + } else { + encodeVariableLength(output, usedStrings_[str] + 256, false); + } + } + } + + std::vector data_; + + unsigned long fileIndex_; +}; + + +std::vector polygons_; +std::vector metadata_; +std::vector fieldNames_; + + +unsigned int decodeVariableLength(uint8_t* buffer, int64_t* result, bool handleNeg = true) +{ + int64_t value = 0; + unsigned int i=0, shift = 0; + + do { + value |= (buffer[i] & 0x7F) << shift; + shift += 7; + } while(buffer[i++] & 0x80); + + if(!handleNeg) { + *result = value; + } else { + *result = (value & 1)?-(value/2):(value/2); + } + return i; +} + +void readMetaDataTimezone(DBFHandle dataHandle) +{ + /* Specify field names */ + fieldNames_.push_back("TimezoneIdPrefix"); + fieldNames_.push_back("TimezoneId"); + fieldNames_.push_back("CountryAlpha2"); + fieldNames_.push_back("CountryName"); + + /* Parse attribute names */ + for(int i = 0; i < DBFGetRecordCount(dataHandle); i++) { + metadata_[i].data_.resize(4); + for(int j = 0; j < DBFGetFieldCount(dataHandle); j++) { + char fieldTitle[12]; + int fieldWidth, fieldDecimals; + DBFFieldType eType = DBFGetFieldInfo(dataHandle, j, fieldTitle, &fieldWidth, &fieldDecimals); + + fieldTitle[11] = 0; + std::string fieldTitleStr(fieldTitle); + + if( eType == FTString ) { + if(fieldTitleStr == "tzid") { + std::string data = DBFReadStringAttribute(dataHandle, i, j); + size_t pos = data.find('/'); + if (pos == std::string::npos) { + metadata_[i].data_.at(0) = data; + } else { + metadata_[i].data_.at(0) = data.substr(0, pos) + "/"; + metadata_[i].data_.at(1) = data.substr(pos + 1, std::string::npos); + } + if(tzidToAlpha2.count(data)) { + metadata_[i].data_.at(2) = tzidToAlpha2[data]; + if(alpha2ToName.count(metadata_[i].data_.at(2))) { + metadata_[i].data_.at(3) = alpha2ToName[metadata_[i].data_.at(2)]; + } else { + std::cout< parseAlpha2ToName(DBFHandle dataHandle) +{ + std::unordered_map result; + + for(int i = 0; i < DBFGetRecordCount(dataHandle); i++) { + std::string alpha2, name; + for(int j = 0; j < DBFGetFieldCount(dataHandle); j++) { + char fieldTitle[12]; + int fieldWidth, fieldDecimals; + DBFFieldType eType = DBFGetFieldInfo(dataHandle, j, fieldTitle, &fieldWidth, &fieldDecimals); + + fieldTitle[11] = 0; + std::string fieldTitleStr(fieldTitle); + + if( eType == FTString ) { + if(fieldTitleStr == "ISO_A2" || fieldTitleStr == "WB_A2") { + std::string tmp = DBFReadStringAttribute(dataHandle, i, j); + if(tmp != "-99" && alpha2 == "") { + alpha2 = tmp; + } + } else if(fieldTitleStr == "NAME_LONG") { + name = DBFReadStringAttribute(dataHandle, i, j); + } + } + } + if(alpha2 != "") { + result[alpha2]=name; + } + } + + result["GF"]="French Guiana"; + result["GP"]="Guadeloupe"; + result["BQ"]="Bonaire"; + result["MQ"]="Martinique"; + result["SJ"]="Svalbard and Jan Mayen Islands"; + result["NO"]="Norway"; + result["CX"]="Christmas Island"; + result["CC"]="Cocos Islands"; + result["YT"]="Mayotte"; + result["RE"]="Réunion"; + result["TK"]="Tokelau"; + + return result; +} + +std::unordered_map parseTimezoneToAlpha2(std::string path) +{ + std::unordered_map result; + //TODO: Clean solution... +#include "zoneToAlpha.h" + + return result; +} + +int main(int argc, char ** argv ) +{ + if(argc != 6) { + std::cout << "Wrong number of parameters\n"; + return 1; + } + + tzidToAlpha2 = parseTimezoneToAlpha2("TODO"); + + char tableType = argv[1][0]; + std::string path = argv[2]; + std::string outPath = argv[3]; + unsigned int precision = strtol(argv[4], NULL, 10); + std::string notice = argv[5]; + + DBFHandle dataHandle = DBFOpen("naturalearth/ne_10m_admin_0_countries_lakes", "rb" ); + alpha2ToName = parseAlpha2ToName(dataHandle); + DBFClose(dataHandle); + + dataHandle = DBFOpen(path.c_str(), "rb" ); + if( dataHandle == NULL ) { + errorFatal("Could not open attribute file\n"); + } + + metadata_.resize(DBFGetRecordCount(dataHandle)); + std::cout << "Reading "<nSHPType != 3 && shapeObject->nSHPType != 5 && + shapeObject->nSHPType != 13 && shapeObject->nSHPType != 15) { + std::cout<<"Unsupported shape object ("<< SHPTypeName(shapeObject->nSHPType) <<")\n"; + continue; + } + + int partIndex = 0; + + PolygonData* polygonData = nullptr; + + for(int j = 0; j < shapeObject->nVertices; j++ ) { + if(j == 0 || j == shapeObject->panPartStart[partIndex]) { + totalPolygons++; + + if(polygonData) { + /* Commit it */ + polygons_.push_back(polygonData); + } + polygonData = new PolygonData(i); + + if(partIndex + 1 < shapeObject->nParts) { + partIndex++; + } + } + + Point p(shapeObject->padfY[j], shapeObject->padfX[j]); + polygonData->processPoint(p); + + } + + if(polygonData) { + /* Commit it */ + polygons_.push_back(polygonData); + } + + SHPDestroyObject(shapeObject); + } + } + + SHPClose(shapeHandle); + + std::cout<<"Parsed "<boundingMin.lat_ < b->boundingMin.lat_; + }); + + /* Encode data section and store pointers */ + std::vector outputData; + for(PolygonData* polygon: polygons_) { + polygon->fileIndex_ = outputData.size(); + polygon->encodeBinaryData(outputData, precision); + } + std::cout << "Encoded data section into "< outputMeta; + for(MetaData& metadata: metadata_) { + metadata.fileIndex_ = outputMeta.size(); + metadata.encodeBinaryData(outputMeta); + } + std::cout << "Encoded metadata into "< outputBBox; + int64_t prevFileIndex = 0; + int64_t prevMetaIndex = 0; + for(PolygonData* polygon: polygons_) { + polygon->boundingMin.encodePointBinary(outputBBox, precision); + polygon->boundingMax.encodePointBinary(outputBBox, precision); + + encodeVariableLength(outputBBox, metadata_.at(polygon->metadataId_).fileIndex_ - prevMetaIndex); + prevMetaIndex = metadata_[polygon->metadataId_].fileIndex_; + + encodeVariableLength(outputBBox, polygon->fileIndex_ - prevFileIndex, false); + prevFileIndex = polygon->fileIndex_; + } + std::cout << "Encoded bounding box section into "< outputHeader; + outputHeader.push_back('P'); + outputHeader.push_back('L'); + outputHeader.push_back('B'); + outputHeader.push_back(tableType); + outputHeader.push_back(0); + outputHeader.push_back(precision); + outputHeader.push_back(fieldNames_.size()); + for(unsigned int i=0; i "timezone_boundary_builder_release.txt" +else + exit +fi + +rm -rf naturalearth +rm -rf timezone +rm -rf geeqie_org +rm -f timezone21.bin + +g++ builder.cpp -o builder -lshp + +mkdir naturalearth +cd naturalearth +wget https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_0_countries_lakes.zip +if [ $? -ne 0 ] +then + zenity --error --text="Download of naturalearthdata failed" --title="Generate Geeqie timezone database" --width=300 --window-icon=$current_dir/../../geeqie.png + exit +fi + +unzip ne_10m_admin_0_countries_lakes.zip +cd .. + +mkdir timezone +cd timezone +wget https://github.com/evansiroky/timezone-boundary-builder/releases/download/$timezone_boundary_builder_release/timezones.shapefile.zip +if [ $? -ne 0 ] +then + zenity --error --text="Download of timezone-boundary-builder failed" --title="Generate Geeqie timezone database" --width=300 --window-icon=$current_dir/../../geeqie.png + exit +fi + +unzip timezones.shapefile.zip +cd .. + + +./builder T timezone/dist/combined-shapefile timezone21.bin 21 "Contains data from Natural Earth, placed in the Public Domain. Contains information from https://github.com/evansiroky/timezone-boundary-builder, which is made available here under the Open Database License (ODbL)." + +mkdir geeqie_org +cd geeqie_org +wget http://www.geeqie.org/downloads/timezone21.bin +if [ $? -ne 0 ] +then + zenity --error --text="Download of timezone database from geeqie.org failed" --title="Generate Geeqie timezone database" --width=300 --window-icon=$current_dir/../../geeqie.png + exit +fi +cd .. + +diff ./geeqie_org/timezone21.bin timezone21.bin + +if [ $? != 0 ] +then + zenity --info --title="Generate Geeqie timezone database" --text="Generated file differs from that on geeqie.org\n\nUpload timezone21.bin to:\ngeeqie.org/downloads/" --width=300 --window-icon=$current_dir/../../geeqie.png +else + zenity --info --title="Generate Geeqie timezone database" --text="Generated file is the same as on geeqie.org" --width=300 --window-icon=$current_dir/../../geeqie.png + rm timezone21.bin +fi + +rm -f builder +rm -rf naturalearth +rm -rf timezone +rm -rf geeqie_org diff --git a/scripts/zonedetect/zoneToAlpha.h b/scripts/zonedetect/zoneToAlpha.h new file mode 100644 index 00000000..aba55aa2 --- /dev/null +++ b/scripts/zonedetect/zoneToAlpha.h @@ -0,0 +1,425 @@ +result["Europe/Andorra"] = "AD"; +result["Asia/Dubai"] = "AE"; +result["Asia/Kabul"] = "AF"; +result["America/Antigua"] = "AG"; +result["America/Anguilla"] = "AI"; +result["Europe/Tirane"] = "AL"; +result["Asia/Yerevan"] = "AM"; +result["Africa/Luanda"] = "AO"; +result["Antarctica/McMurdo"] = "AQ"; +result["Antarctica/Casey"] = "AQ"; +result["Antarctica/Davis"] = "AQ"; +result["Antarctica/DumontDUrville"] = "AQ"; +result["Antarctica/Mawson"] = "AQ"; +result["Antarctica/Palmer"] = "AQ"; +result["Antarctica/Rothera"] = "AQ"; +result["Antarctica/Syowa"] = "AQ"; +result["Antarctica/Troll"] = "AQ"; +result["Antarctica/Vostok"] = "AQ"; +result["America/Argentina/Buenos_Aires"] = "AR"; +result["America/Argentina/Cordoba"] = "AR"; +result["America/Argentina/Salta"] = "AR"; +result["America/Argentina/Jujuy"] = "AR"; +result["America/Argentina/Tucuman"] = "AR"; +result["America/Argentina/Catamarca"] = "AR"; +result["America/Argentina/La_Rioja"] = "AR"; +result["America/Argentina/San_Juan"] = "AR"; +result["America/Argentina/Mendoza"] = "AR"; +result["America/Argentina/San_Luis"] = "AR"; +result["America/Argentina/Rio_Gallegos"] = "AR"; +result["America/Argentina/Ushuaia"] = "AR"; +result["Pacific/Pago_Pago"] = "AS"; +result["Europe/Vienna"] = "AT"; +result["Australia/Lord_Howe"] = "AU"; +result["Antarctica/Macquarie"] = "AU"; +result["Australia/Hobart"] = "AU"; +result["Australia/Currie"] = "AU"; +result["Australia/Melbourne"] = "AU"; +result["Australia/Sydney"] = "AU"; +result["Australia/Broken_Hill"] = "AU"; +result["Australia/Brisbane"] = "AU"; +result["Australia/Lindeman"] = "AU"; +result["Australia/Adelaide"] = "AU"; +result["Australia/Darwin"] = "AU"; +result["Australia/Perth"] = "AU"; +result["Australia/Eucla"] = "AU"; +result["America/Aruba"] = "AW"; +result["Europe/Mariehamn"] = "AX"; +result["Asia/Baku"] = "AZ"; +result["Europe/Sarajevo"] = "BA"; +result["America/Barbados"] = "BB"; +result["Asia/Dhaka"] = "BD"; +result["Europe/Brussels"] = "BE"; +result["Africa/Ouagadougou"] = "BF"; +result["Europe/Sofia"] = "BG"; +result["Asia/Bahrain"] = "BH"; +result["Africa/Bujumbura"] = "BI"; +result["Africa/Porto-Novo"] = "BJ"; +result["America/St_Barthelemy"] = "BL"; +result["Atlantic/Bermuda"] = "BM"; +result["Asia/Brunei"] = "BN"; +result["America/La_Paz"] = "BO"; +result["America/Kralendijk"] = "BQ"; +result["America/Noronha"] = "BR"; +result["America/Belem"] = "BR"; +result["America/Fortaleza"] = "BR"; +result["America/Recife"] = "BR"; +result["America/Araguaina"] = "BR"; +result["America/Maceio"] = "BR"; +result["America/Bahia"] = "BR"; +result["America/Sao_Paulo"] = "BR"; +result["America/Campo_Grande"] = "BR"; +result["America/Cuiaba"] = "BR"; +result["America/Santarem"] = "BR"; +result["America/Porto_Velho"] = "BR"; +result["America/Boa_Vista"] = "BR"; +result["America/Manaus"] = "BR"; +result["America/Eirunepe"] = "BR"; +result["America/Rio_Branco"] = "BR"; +result["America/Nassau"] = "BS"; +result["Asia/Thimphu"] = "BT"; +result["Africa/Gaborone"] = "BW"; +result["Europe/Minsk"] = "BY"; +result["America/Belize"] = "BZ"; +result["America/St_Johns"] = "CA"; +result["America/Halifax"] = "CA"; +result["America/Glace_Bay"] = "CA"; +result["America/Moncton"] = "CA"; +result["America/Goose_Bay"] = "CA"; +result["America/Blanc-Sablon"] = "CA"; +result["America/Toronto"] = "CA"; +result["America/Nipigon"] = "CA"; +result["America/Thunder_Bay"] = "CA"; +result["America/Iqaluit"] = "CA"; +result["America/Pangnirtung"] = "CA"; +result["America/Atikokan"] = "CA"; +result["America/Winnipeg"] = "CA"; +result["America/Rainy_River"] = "CA"; +result["America/Resolute"] = "CA"; +result["America/Rankin_Inlet"] = "CA"; +result["America/Regina"] = "CA"; +result["America/Swift_Current"] = "CA"; +result["America/Edmonton"] = "CA"; +result["America/Cambridge_Bay"] = "CA"; +result["America/Yellowknife"] = "CA"; +result["America/Inuvik"] = "CA"; +result["America/Creston"] = "CA"; +result["America/Dawson_Creek"] = "CA"; +result["America/Fort_Nelson"] = "CA"; +result["America/Vancouver"] = "CA"; +result["America/Whitehorse"] = "CA"; +result["America/Dawson"] = "CA"; +result["Indian/Cocos"] = "CC"; +result["Africa/Kinshasa"] = "CD"; +result["Africa/Lubumbashi"] = "CD"; +result["Africa/Bangui"] = "CF"; +result["Africa/Brazzaville"] = "CG"; +result["Europe/Zurich"] = "CH"; +result["Africa/Abidjan"] = "CI"; +result["Pacific/Rarotonga"] = "CK"; +result["America/Santiago"] = "CL"; +result["America/Punta_Arenas"] = "CL"; +result["Pacific/Easter"] = "CL"; +result["Africa/Douala"] = "CM"; +result["Asia/Shanghai"] = "CN"; +result["Asia/Urumqi"] = "CN"; +result["America/Bogota"] = "CO"; +result["America/Costa_Rica"] = "CR"; +result["America/Havana"] = "CU"; +result["Atlantic/Cape_Verde"] = "CV"; +result["America/Curacao"] = "CW"; +result["Indian/Christmas"] = "CX"; +result["Asia/Nicosia"] = "CY"; +result["Asia/Famagusta"] = "CY"; +result["Europe/Prague"] = "CZ"; +result["Europe/Berlin"] = "DE"; +result["Europe/Busingen"] = "DE"; +result["Africa/Djibouti"] = "DJ"; +result["Europe/Copenhagen"] = "DK"; +result["America/Dominica"] = "DM"; +result["America/Santo_Domingo"] = "DO"; +result["Africa/Algiers"] = "DZ"; +result["America/Guayaquil"] = "EC"; +result["Pacific/Galapagos"] = "EC"; +result["Europe/Tallinn"] = "EE"; +result["Africa/Cairo"] = "EG"; +result["Africa/El_Aaiun"] = "EH"; +result["Africa/Asmara"] = "ER"; +result["Europe/Madrid"] = "ES"; +result["Africa/Ceuta"] = "ES"; +result["Atlantic/Canary"] = "ES"; +result["Africa/Addis_Ababa"] = "ET"; +result["Europe/Helsinki"] = "FI"; +result["Pacific/Fiji"] = "FJ"; +result["Atlantic/Stanley"] = "FK"; +result["Pacific/Chuuk"] = "FM"; +result["Pacific/Pohnpei"] = "FM"; +result["Pacific/Kosrae"] = "FM"; +result["Atlantic/Faroe"] = "FO"; +result["Europe/Paris"] = "FR"; +result["Africa/Libreville"] = "GA"; +result["Europe/London"] = "GB"; +result["America/Grenada"] = "GD"; +result["Asia/Tbilisi"] = "GE"; +result["America/Cayenne"] = "GF"; +result["Europe/Guernsey"] = "GG"; +result["Africa/Accra"] = "GH"; +result["Europe/Gibraltar"] = "GI"; +result["America/Godthab"] = "GL"; +result["America/Danmarkshavn"] = "GL"; +result["America/Scoresbysund"] = "GL"; +result["America/Thule"] = "GL"; +result["Africa/Banjul"] = "GM"; +result["Africa/Conakry"] = "GN"; +result["America/Guadeloupe"] = "GP"; +result["Africa/Malabo"] = "GQ"; +result["Europe/Athens"] = "GR"; +result["Atlantic/South_Georgia"] = "GS"; +result["America/Guatemala"] = "GT"; +result["Pacific/Guam"] = "GU"; +result["Africa/Bissau"] = "GW"; +result["America/Guyana"] = "GY"; +result["Asia/Hong_Kong"] = "HK"; +result["America/Tegucigalpa"] = "HN"; +result["Europe/Zagreb"] = "HR"; +result["America/Port-au-Prince"] = "HT"; +result["Europe/Budapest"] = "HU"; +result["Asia/Jakarta"] = "ID"; +result["Asia/Pontianak"] = "ID"; +result["Asia/Makassar"] = "ID"; +result["Asia/Jayapura"] = "ID"; +result["Europe/Dublin"] = "IE"; +result["Asia/Jerusalem"] = "IL"; +result["Europe/Isle_of_Man"] = "IM"; +result["Asia/Kolkata"] = "IN"; +result["Indian/Chagos"] = "IO"; +result["Asia/Baghdad"] = "IQ"; +result["Asia/Tehran"] = "IR"; +result["Atlantic/Reykjavik"] = "IS"; +result["Europe/Rome"] = "IT"; +result["Europe/Jersey"] = "JE"; +result["America/Jamaica"] = "JM"; +result["Asia/Amman"] = "JO"; +result["Asia/Tokyo"] = "JP"; +result["Africa/Nairobi"] = "KE"; +result["Asia/Bishkek"] = "KG"; +result["Asia/Phnom_Penh"] = "KH"; +result["Pacific/Tarawa"] = "KI"; +result["Pacific/Enderbury"] = "KI"; +result["Pacific/Kiritimati"] = "KI"; +result["Indian/Comoro"] = "KM"; +result["America/St_Kitts"] = "KN"; +result["Asia/Pyongyang"] = "KP"; +result["Asia/Seoul"] = "KR"; +result["Asia/Kuwait"] = "KW"; +result["America/Cayman"] = "KY"; +result["Asia/Almaty"] = "KZ"; +result["Asia/Qyzylorda"] = "KZ"; +result["Asia/Aqtobe"] = "KZ"; +result["Asia/Aqtau"] = "KZ"; +result["Asia/Atyrau"] = "KZ"; +result["Asia/Oral"] = "KZ"; +result["Asia/Vientiane"] = "LA"; +result["Asia/Beirut"] = "LB"; +result["America/St_Lucia"] = "LC"; +result["Europe/Vaduz"] = "LI"; +result["Asia/Colombo"] = "LK"; +result["Africa/Monrovia"] = "LR"; +result["Africa/Maseru"] = "LS"; +result["Europe/Vilnius"] = "LT"; +result["Europe/Luxembourg"] = "LU"; +result["Europe/Riga"] = "LV"; +result["Africa/Tripoli"] = "LY"; +result["Africa/Casablanca"] = "MA"; +result["Europe/Monaco"] = "MC"; +result["Europe/Chisinau"] = "MD"; +result["Europe/Podgorica"] = "ME"; +result["America/Marigot"] = "MF"; +result["Indian/Antananarivo"] = "MG"; +result["Pacific/Majuro"] = "MH"; +result["Pacific/Kwajalein"] = "MH"; +result["Europe/Skopje"] = "MK"; +result["Africa/Bamako"] = "ML"; +result["Asia/Yangon"] = "MM"; +result["Asia/Ulaanbaatar"] = "MN"; +result["Asia/Hovd"] = "MN"; +result["Asia/Choibalsan"] = "MN"; +result["Asia/Macau"] = "MO"; +result["Pacific/Saipan"] = "MP"; +result["America/Martinique"] = "MQ"; +result["Africa/Nouakchott"] = "MR"; +result["America/Montserrat"] = "MS"; +result["Europe/Malta"] = "MT"; +result["Indian/Mauritius"] = "MU"; +result["Indian/Maldives"] = "MV"; +result["Africa/Blantyre"] = "MW"; +result["America/Mexico_City"] = "MX"; +result["America/Cancun"] = "MX"; +result["America/Merida"] = "MX"; +result["America/Monterrey"] = "MX"; +result["America/Matamoros"] = "MX"; +result["America/Mazatlan"] = "MX"; +result["America/Chihuahua"] = "MX"; +result["America/Ojinaga"] = "MX"; +result["America/Hermosillo"] = "MX"; +result["America/Tijuana"] = "MX"; +result["America/Bahia_Banderas"] = "MX"; +result["Asia/Kuala_Lumpur"] = "MY"; +result["Asia/Qostanay"] = "KZ"; +result["Asia/Kuching"] = "MY"; +result["Africa/Maputo"] = "MZ"; +result["Africa/Windhoek"] = "NA"; +result["Pacific/Noumea"] = "NC"; +result["Africa/Niamey"] = "NE"; +result["Pacific/Norfolk"] = "NF"; +result["Africa/Lagos"] = "NG"; +result["America/Managua"] = "NI"; +result["Europe/Amsterdam"] = "NL"; +result["Europe/Oslo"] = "NO"; +result["Asia/Kathmandu"] = "NP"; +result["Pacific/Nauru"] = "NR"; +result["Pacific/Niue"] = "NU"; +result["Pacific/Auckland"] = "NZ"; +result["Pacific/Chatham"] = "NZ"; +result["Asia/Muscat"] = "OM"; +result["America/Panama"] = "PA"; +result["America/Lima"] = "PE"; +result["Pacific/Tahiti"] = "PF"; +result["Pacific/Marquesas"] = "PF"; +result["Pacific/Gambier"] = "PF"; +result["Pacific/Port_Moresby"] = "PG"; +result["Pacific/Bougainville"] = "PG"; +result["Asia/Manila"] = "PH"; +result["Asia/Karachi"] = "PK"; +result["Europe/Warsaw"] = "PL"; +result["America/Miquelon"] = "PM"; +result["Pacific/Pitcairn"] = "PN"; +result["America/Puerto_Rico"] = "PR"; +result["Asia/Gaza"] = "PS"; +result["Asia/Hebron"] = "PS"; +result["Europe/Lisbon"] = "PT"; +result["Atlantic/Madeira"] = "PT"; +result["Atlantic/Azores"] = "PT"; +result["Pacific/Palau"] = "PW"; +result["America/Asuncion"] = "PY"; +result["Asia/Qatar"] = "QA"; +result["Indian/Reunion"] = "RE"; +result["Europe/Bucharest"] = "RO"; +result["Europe/Belgrade"] = "RS"; +result["Europe/Kaliningrad"] = "RU"; +result["Europe/Moscow"] = "RU"; +result["Europe/Simferopol"] = "RU"; +result["Europe/Volgograd"] = "RU"; +result["Europe/Kirov"] = "RU"; +result["Europe/Astrakhan"] = "RU"; +result["Europe/Saratov"] = "RU"; +result["Europe/Ulyanovsk"] = "RU"; +result["Europe/Samara"] = "RU"; +result["Asia/Yekaterinburg"] = "RU"; +result["Asia/Omsk"] = "RU"; +result["Asia/Novosibirsk"] = "RU"; +result["Asia/Barnaul"] = "RU"; +result["Asia/Tomsk"] = "RU"; +result["Asia/Novokuznetsk"] = "RU"; +result["Asia/Krasnoyarsk"] = "RU"; +result["Asia/Irkutsk"] = "RU"; +result["Asia/Chita"] = "RU"; +result["Asia/Yakutsk"] = "RU"; +result["Asia/Khandyga"] = "RU"; +result["Asia/Vladivostok"] = "RU"; +result["Asia/Ust-Nera"] = "RU"; +result["Asia/Magadan"] = "RU"; +result["Asia/Sakhalin"] = "RU"; +result["Asia/Srednekolymsk"] = "RU"; +result["Asia/Kamchatka"] = "RU"; +result["Asia/Anadyr"] = "RU"; +result["Africa/Kigali"] = "RW"; +result["Asia/Riyadh"] = "SA"; +result["Pacific/Guadalcanal"] = "SB"; +result["Indian/Mahe"] = "SC"; +result["Africa/Khartoum"] = "SD"; +result["Europe/Stockholm"] = "SE"; +result["Asia/Singapore"] = "SG"; +result["Atlantic/St_Helena"] = "SH"; +result["Europe/Ljubljana"] = "SI"; +result["Arctic/Longyearbyen"] = "SJ"; +result["Europe/Bratislava"] = "SK"; +result["Africa/Freetown"] = "SL"; +result["Europe/San_Marino"] = "SM"; +result["Africa/Dakar"] = "SN"; +result["Africa/Mogadishu"] = "SO"; +result["America/Paramaribo"] = "SR"; +result["Africa/Juba"] = "SS"; +result["Africa/Sao_Tome"] = "ST"; +result["America/El_Salvador"] = "SV"; +result["America/Lower_Princes"] = "SX"; +result["Asia/Damascus"] = "SY"; +result["Africa/Mbabane"] = "SZ"; +result["America/Grand_Turk"] = "TC"; +result["Africa/Ndjamena"] = "TD"; +result["Indian/Kerguelen"] = "TF"; +result["Africa/Lome"] = "TG"; +result["Asia/Bangkok"] = "TH"; +result["Asia/Dushanbe"] = "TJ"; +result["Pacific/Fakaofo"] = "TK"; +result["Asia/Dili"] = "TL"; +result["Asia/Ashgabat"] = "TM"; +result["Africa/Tunis"] = "TN"; +result["Pacific/Tongatapu"] = "TO"; +result["Europe/Istanbul"] = "TR"; +result["America/Port_of_Spain"] = "TT"; +result["Pacific/Funafuti"] = "TV"; +result["Asia/Taipei"] = "TW"; +result["Africa/Dar_es_Salaam"] = "TZ"; +result["Europe/Kiev"] = "UA"; +result["Europe/Uzhgorod"] = "UA"; +result["Europe/Zaporozhye"] = "UA"; +result["Africa/Kampala"] = "UG"; +result["Pacific/Midway"] = "UM"; +result["Pacific/Wake"] = "UM"; +result["America/New_York"] = "US"; +result["America/Detroit"] = "US"; +result["America/Kentucky/Louisville"] = "US"; +result["America/Kentucky/Monticello"] = "US"; +result["America/Indiana/Indianapolis"] = "US"; +result["America/Indiana/Vincennes"] = "US"; +result["America/Indiana/Winamac"] = "US"; +result["America/Indiana/Marengo"] = "US"; +result["America/Indiana/Petersburg"] = "US"; +result["America/Indiana/Vevay"] = "US"; +result["America/Chicago"] = "US"; +result["America/Indiana/Tell_City"] = "US"; +result["America/Indiana/Knox"] = "US"; +result["America/Menominee"] = "US"; +result["America/North_Dakota/Center"] = "US"; +result["America/North_Dakota/New_Salem"] = "US"; +result["America/North_Dakota/Beulah"] = "US"; +result["America/Denver"] = "US"; +result["America/Boise"] = "US"; +result["America/Phoenix"] = "US"; +result["America/Los_Angeles"] = "US"; +result["America/Anchorage"] = "US"; +result["America/Juneau"] = "US"; +result["America/Sitka"] = "US"; +result["America/Metlakatla"] = "US"; +result["America/Yakutat"] = "US"; +result["America/Nome"] = "US"; +result["America/Adak"] = "US"; +result["Pacific/Honolulu"] = "US"; +result["America/Montevideo"] = "UY"; +result["Asia/Samarkand"] = "UZ"; +result["Asia/Tashkent"] = "UZ"; +result["Europe/Vatican"] = "VA"; +result["America/St_Vincent"] = "VC"; +result["America/Caracas"] = "VE"; +result["America/Tortola"] = "VG"; +result["America/St_Thomas"] = "VI"; +result["Asia/Ho_Chi_Minh"] = "VN"; +result["Pacific/Efate"] = "VU"; +result["Pacific/Wallis"] = "WF"; +result["Pacific/Apia"] = "WS"; +result["Asia/Aden"] = "YE"; +result["Indian/Mayotte"] = "YT"; +result["Africa/Johannesburg"] = "ZA"; +result["Africa/Lusaka"] = "ZM"; +result["Africa/Harare"] = "ZW"; diff --git a/src/exif-common.c b/src/exif-common.c index e0b56edb..9463a5b4 100644 --- a/src/exif-common.c +++ b/src/exif-common.c @@ -684,10 +684,12 @@ static gboolean exif_build_tz_data(ExifData *exif, gchar **exif_date_time, gchar gchar *lat_min; gchar *lon_deg; gchar *lon_min; - gchar *zd_path; + gchar *timezone_path; ZoneDetect *cd; ZoneDetectResult *results; gboolean ret = FALSE; + gchar *basename; + gchar *path; text_latitude = exif_get_data_as_text(exif, "Exif.GPSInfo.GPSLatitude"); text_longitude = exif_get_data_as_text(exif, "Exif.GPSInfo.GPSLongitude"); @@ -713,10 +715,12 @@ static gboolean exif_build_tz_data(ExifData *exif, gchar **exif_date_time, gchar longitude = -longitude; } - zd_path = g_build_filename(GQ_BIN_DIR, TIMEZONE_DATABASE, NULL); - if (g_file_test(zd_path, G_FILE_TEST_EXISTS)) + path = path_from_utf8(TIMEZONE_DATABASE); + basename = g_path_get_basename(path); + timezone_path = g_build_filename(get_rc_dir(), basename, NULL); + if (g_file_test(timezone_path, G_FILE_TEST_EXISTS)) { - cd = ZDOpenDatabase(zd_path); + cd = ZDOpenDatabase(timezone_path); if (cd) { results = ZDLookup(cd, latitude, longitude, NULL); @@ -728,11 +732,13 @@ static gboolean exif_build_tz_data(ExifData *exif, gchar **exif_date_time, gchar } else { - log_printf("Error: Init of timezone database %s failed\n", zd_path); + log_printf("Error: Init of timezone database %s failed\n", timezone_path); } ZDCloseDatabase(cd); } - g_free(zd_path); + g_free(path); + g_free(timezone_path); + g_free(basename); } if (ret && text_date && text_time) diff --git a/src/main.h b/src/main.h index 1048fd24..b1976e42 100644 --- a/src/main.h +++ b/src/main.h @@ -125,7 +125,7 @@ #define DESKTOP_FILE_TEMPLATE GQ_APP_DIR "/template.desktop" -#define TIMEZONE_DATABASE "timezone21.bin" +#define TIMEZONE_DATABASE GQ_WEBSITE"downloads/timezone21.bin" #define HELP_SEARCH_ENGINE "https://duckduckgo.com/?q=site:geeqie.org/help " diff --git a/src/preferences.c b/src/preferences.c index 6bcbed47..5d6db5dc 100644 --- a/src/preferences.c +++ b/src/preferences.c @@ -1691,6 +1691,19 @@ static void star_rating_rejected_test_cb(GtkWidget *widget, gpointer data) } /* general options tab */ +static void timezone_database_install_cb(GtkWidget *widget, gpointer data); +typedef struct _TZData TZData; +struct _TZData +{ + GenericDialog *gd; + GCancellable *cancellable; + + GtkWidget *progress; + GFile *tmp_g_file; + GFile *timezone_database_gq; + gchar *timezone_database_user; +}; + static void config_tab_general(GtkWidget *notebook) { GtkWidget *vbox; @@ -1707,6 +1720,12 @@ static void config_tab_general(GtkWidget *notebook) GtkWidget *star_rating_entry; GString *str; gchar *rating_symbol; + gchar *path; + gchar *basename; + GNetworkMonitor *net_mon; + GSocketConnectable *geeqie_org; + gboolean internet_available; + TZData *tz; vbox = scrolled_notebook_page(notebook, _("General")); @@ -1760,6 +1779,8 @@ static void config_tab_general(GtkWidget *notebook) // options->thumbnails.use_ft_metadata_small, &c_options->thumbnails.use_ft_metadata_small); #endif + pref_spacer(group, PREF_PAD_GROUP); + group = pref_group_new(vbox, FALSE, _("Star Rating"), GTK_ORIENTATION_VERTICAL); c_options->star_rating.star = options->star_rating.star; @@ -1829,6 +1850,8 @@ static void config_tab_general(GtkWidget *notebook) g_string_free(str, TRUE); g_free(rating_symbol); + pref_spacer(group, PREF_PAD_GROUP); + group = pref_group_new(vbox, FALSE, _("Slide show"), GTK_ORIENTATION_VERTICAL); c_options->slideshow.delay = options->slideshow.delay; @@ -1859,6 +1882,8 @@ static void config_tab_general(GtkWidget *notebook) pref_checkbox_new_int(group, _("Random"), options->slideshow.random, &c_options->slideshow.random); pref_checkbox_new_int(group, _("Repeat"), options->slideshow.repeat, &c_options->slideshow.repeat); + pref_spacer(group, PREF_PAD_GROUP); + group = pref_group_new(vbox, FALSE, _("Image loading and caching"), GTK_ORIENTATION_VERTICAL); pref_spin_new_int(group, _("Decoded image cache size (Mb):"), NULL, @@ -1869,6 +1894,8 @@ static void config_tab_general(GtkWidget *notebook) pref_checkbox_new_int(group, _("Refresh on file change"), options->update_on_time_change, &c_options->update_on_time_change); + pref_spacer(group, PREF_PAD_GROUP); + group = pref_group_new(vbox, FALSE, _("Info sidebar heights"), GTK_ORIENTATION_VERTICAL); pref_label_new(group, _("NOTE! Geeqie must be restarted for changes to take effect")); hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE); @@ -1885,11 +1912,57 @@ static void config_tab_general(GtkWidget *notebook) 1, 9999, 1, options->info_rating.height, &c_options->info_rating.height); + pref_spacer(group, PREF_PAD_GROUP); + group = pref_group_new(vbox, FALSE, _("Show predefined keyword tree"), GTK_ORIENTATION_VERTICAL); pref_checkbox_new_int(group, _("Show predefined keyword tree (NOTE! Geeqie must be restarted for change to take effect)"), options->show_predefined_keyword_tree, &c_options->show_predefined_keyword_tree); + pref_spacer(group, PREF_PAD_GROUP); + + net_mon = g_network_monitor_get_default(); + geeqie_org = g_network_address_parse_uri(GQ_WEBSITE, 80, NULL); + internet_available = g_network_monitor_can_reach(net_mon, geeqie_org, NULL, NULL); + g_object_unref(geeqie_org); + + group = pref_group_new(vbox, FALSE, _("Timezone database"), GTK_ORIENTATION_VERTICAL); + hbox = pref_box_new(group, TRUE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE); + + if (!internet_available) + { + gtk_widget_set_sensitive(group, FALSE); + } + + tz = g_new0(TZData, 1); + + path = path_from_utf8(TIMEZONE_DATABASE); + basename = g_path_get_basename(path); + tz->timezone_database_user = g_build_filename(get_rc_dir(), basename, NULL); + g_free(path); + g_free(basename); + + if (isfile(tz->timezone_database_user)) + { + button = pref_button_new(GTK_WIDGET(hbox), NULL, _("Update"), FALSE, G_CALLBACK(timezone_database_install_cb), tz); + } + else + { + button = pref_button_new(GTK_WIDGET(hbox), NULL, _("Install"), FALSE, G_CALLBACK(timezone_database_install_cb), tz); + } + + if (!internet_available) + { + gtk_widget_set_tooltip_text(button, _("No Internet connection!\nThe timezone database is used to display exif time and date\ncorrected for UTC offset and Daylight Saving Time")); + } + else + { + gtk_widget_set_tooltip_text(button, _("The timezone database is used to display exif time and date\ncorrected for UTC offset and Daylight Saving Time")); + } + gtk_widget_show(button); + + pref_spacer(group, PREF_PAD_GROUP); + group = pref_group_new(vbox, FALSE, _("On-line help search engine"), GTK_ORIENTATION_VERTICAL); help_search_engine_entry = gtk_entry_new(); @@ -3504,4 +3577,98 @@ static void image_overlay_set_text_colours() c_options->image_overlay.background_blue = options->image_overlay.background_blue; c_options->image_overlay.background_alpha = options->image_overlay.background_alpha; } + +/* + *----------------------------------------------------------------------------- + * timezone database routines + *----------------------------------------------------------------------------- + */ + +static void timezone_async_ready_cb(GObject *source_object, GAsyncResult *res, gpointer data) +{ + GError *error = NULL; + TZData *tz = data; + gchar *tmp_filename; + + if (!g_cancellable_is_cancelled(tz->cancellable)) + { + generic_dialog_close(tz->gd); + } + + + if (g_file_copy_finish(G_FILE(source_object), res, &error)) + { + tmp_filename = g_file_get_parse_name(tz->tmp_g_file); + move_file(tmp_filename, tz->timezone_database_user); + g_free(tmp_filename); + } + else + { + file_util_warning_dialog(_("Timezone database download failed"), error->message, GTK_STOCK_DIALOG_ERROR, NULL); + } + + g_file_delete(tz->tmp_g_file, NULL, &error); + g_object_unref(tz->tmp_g_file); + tz->tmp_g_file = NULL; + g_object_unref(tz->cancellable); + g_object_unref(tz->timezone_database_gq); +} + +static void timezone_progress_cb(goffset current_num_bytes, goffset total_num_bytes, gpointer data) +{ + TZData *tz = data; + + if (!g_cancellable_is_cancelled(tz->cancellable)) + { + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(tz->progress), (gdouble)current_num_bytes / total_num_bytes); + } +} + +static void timezone_cancel_button_cb(GenericDialog *gd, gpointer data) +{ + TZData *tz = data; + GError *error = NULL; + + g_cancellable_cancel(tz->cancellable); +} + +static void timezone_database_install_cb(GtkWidget *widget, gpointer data) +{ + TZData *tz = data; + GError *error = NULL; + GFileIOStream *io_stream; + + if (tz->tmp_g_file) + { + return; + } + + tz->tmp_g_file = g_file_new_tmp("geeqie_timezone_XXXXXX", &io_stream, &error); + + if (error) + { + file_util_warning_dialog(_("Timezone database download failed"), error->message, GTK_STOCK_DIALOG_ERROR, NULL); + log_printf("Error: Download timezone database failed:\n%s", error->message); + g_error_free(error); + g_object_unref(tz->tmp_g_file); + } + else + { + tz->timezone_database_gq = g_file_new_for_uri(TIMEZONE_DATABASE); + + tz->gd = generic_dialog_new(_("Timezone database"), "download_timezone_database", NULL, TRUE, timezone_cancel_button_cb, tz); + + generic_dialog_add_message(tz->gd, GTK_STOCK_DIALOG_INFO, _("Downloading timezone database"), NULL, FALSE); + + tz->progress = gtk_progress_bar_new(); + gtk_box_pack_start(GTK_BOX(tz->gd->vbox), tz->progress, FALSE, FALSE, 0); + gtk_widget_show(tz->progress); + + gtk_widget_show(tz->gd->dialog); + tz->cancellable = g_cancellable_new(); + g_file_copy_async(tz->timezone_database_gq, tz->tmp_g_file, G_FILE_COPY_OVERWRITE, G_PRIORITY_LOW, tz->cancellable, timezone_progress_cb, tz, timezone_async_ready_cb, tz); + + gtk_button_set_label(GTK_BUTTON(widget), _("Update")); + } +} /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/zonedetect.c b/src/zonedetect.c index 74bb8f4e..8a7b0eae 100644 --- a/src/zonedetect.c +++ b/src/zonedetect.c @@ -25,54 +25,96 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include +#include +#include +#include #include #include -#include -#include #include +#if defined(_MSC_VER) || defined(__MINGW32__) + #include +#else + #include + #include + #include + #include +#endif #include "zonedetect.h" +enum ZDInternalError { + ZD_OK, + ZD_E_DB_OPEN, + ZD_E_DB_SEEK, + ZD_E_DB_MMAP, +#if defined(_MSC_VER) || defined(__MINGW32__) + ZD_E_DB_MMAP_MSVIEW, + ZD_E_DB_MAP_EXCEPTION, + ZD_E_DB_MUNMAP_MSVIEW, +#endif + ZD_E_DB_MUNMAP, + ZD_E_DB_CLOSE, + ZD_E_PARSE_HEADER +}; + struct ZoneDetectOpaque { +#if defined(_MSC_VER) || defined(__MINGW32__) + HANDLE fd; + HANDLE fdMap; + int32_t length; + int32_t padding; +#else int fd; - uint32_t length; - uint8_t* mapping; + off_t length; +#endif + + uint8_t *mapping; uint8_t tableType; uint8_t version; uint8_t precision; uint8_t numFields; - char* notice; - char** fieldNames; + char *notice; + char **fieldNames; uint32_t bboxOffset; uint32_t metadataOffset; uint32_t dataOffset; }; -static int32_t ZDFloatToFixedPoint(float input, float scale, unsigned int precision) { - float inputScaled = input / scale; - return inputScaled * (float)(1 << (precision-1)); +static void (*zdErrorHandler)(int, int); +static void zdError(enum ZDInternalError errZD, int errNative) +{ + if (zdErrorHandler) zdErrorHandler((int)errZD, errNative); } -static unsigned int ZDDecodeVariableLengthUnsigned(ZoneDetect* library, uint32_t* index, uint32_t* result) { - uint32_t value = 0; - unsigned int i=0, shift = 0; +static int32_t ZDFloatToFixedPoint(float input, float scale, unsigned int precision) +{ + const float inputScaled = input / scale; + return (int32_t)(inputScaled * (float)(1 << (precision - 1))); +} - if(*index >= library->length) { +static unsigned int ZDDecodeVariableLengthUnsigned(const ZoneDetect *library, uint32_t *index, uint32_t *result) +{ + if(*index >= (uint32_t)library->length) { return 0; } - uint8_t* buffer = library->mapping + *index; - uint8_t* bufferEnd = library->mapping + library->length - 1; - + uint32_t value = 0; + unsigned int i = 0; +#if defined(_MSC_VER) + __try { +#endif + uint8_t *const buffer = library->mapping + *index; + uint8_t *const bufferEnd = library->mapping + library->length - 1; + + unsigned int shift = 0; while(1) { - value |= (buffer[i] & 0x7F) << shift; - shift += 7; + value |= (uint32_t)((buffer[i] & UINT8_C(0x7F)) << shift); + shift += 7u; - if(!(buffer[i] & 0x80)) { + if(!(buffer[i] & UINT8_C(0x80))) { break; } @@ -81,6 +123,14 @@ static unsigned int ZDDecodeVariableLengthUnsigned(ZoneDetect* library, uint32_t return 0; } } +#if defined(_MSC_VER) + } __except(GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR + ? EXCEPTION_EXECUTE_HANDLER + : EXCEPTION_CONTINUE_SEARCH) { /* file mapping SEH exception occurred */ + zdError(ZD_E_DB_MAP_EXCEPTION, (int)GetLastError()); + return 0; + } +#endif i++; *result = value; @@ -88,14 +138,16 @@ static unsigned int ZDDecodeVariableLengthUnsigned(ZoneDetect* library, uint32_t return i; } -static unsigned int ZDDecodeVariableLengthSigned(ZoneDetect* library, uint32_t* index, int32_t* result) { +static unsigned int ZDDecodeVariableLengthSigned(const ZoneDetect *library, uint32_t *index, int32_t *result) +{ uint32_t value = 0; - unsigned int retVal = ZDDecodeVariableLengthUnsigned(library, index, &value); - *result = (value & 1)?-(value/2):(value/2); + const unsigned int retVal = ZDDecodeVariableLengthUnsigned(library, index, &value); + *result = (value & 1) ? -(int32_t)(value / 2) : (int32_t)(value / 2); return retVal; } -static char* ZDParseString(ZoneDetect* library, uint32_t* index) { +static char *ZDParseString(const ZoneDetect *library, uint32_t *index) +{ uint32_t strLength; if(!ZDDecodeVariableLengthUnsigned(library, index, &strLength)) { return NULL; @@ -116,13 +168,23 @@ static char* ZDParseString(ZoneDetect* library, uint32_t* index) { } } - char* str = malloc(strLength + 1); + char *const str = malloc(strLength + 1); if(str) { - unsigned int i; - for(i=0; imapping[strOffset+i] ^ 0x80; +#if defined(_MSC_VER) + __try { +#endif + for(size_t i = 0; i < strLength; i++) { + str[i] = (char)(library->mapping[strOffset + i] ^ UINT8_C(0x80)); } +#if defined(_MSC_VER) + } __except(GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR + ? EXCEPTION_EXECUTE_HANDLER + : EXCEPTION_CONTINUE_SEARCH) { /* file mapping SEH exception occurred */ + zdError(ZD_E_DB_MAP_EXCEPTION, (int)GetLastError()); + return 0; + } +#endif str[strLength] = 0; } @@ -133,11 +195,15 @@ static char* ZDParseString(ZoneDetect* library, uint32_t* index) { return str; } -static int ZDParseHeader(ZoneDetect* library) { +static int ZDParseHeader(ZoneDetect *library) +{ if(library->length < 7) { return -1; } +#if defined(_MSC_VER) + __try { +#endif if(memcmp(library->mapping, "PLB", 3)) { return -1; } @@ -146,16 +212,23 @@ static int ZDParseHeader(ZoneDetect* library) { library->version = library->mapping[4]; library->precision = library->mapping[5]; library->numFields = library->mapping[6]; +#if defined(_MSC_VER) + } __except(GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR + ? EXCEPTION_EXECUTE_HANDLER + : EXCEPTION_CONTINUE_SEARCH) { /* file mapping SEH exception occurred */ + zdError(ZD_E_DB_MAP_EXCEPTION, (int)GetLastError()); + return 0; + } +#endif if(library->version != 0) { return -1; } - uint32_t index = 7; + uint32_t index = UINT32_C(7); - library->fieldNames = malloc(library->numFields * sizeof(char*)); - unsigned int i; - for(i=0; inumFields; i++) { + library->fieldNames = malloc(library->numFields * sizeof *library->fieldNames); + for(size_t i = 0; i < library->numFields; i++) { library->fieldNames[i] = ZDParseString(library, &index); } @@ -182,14 +255,15 @@ static int ZDParseHeader(ZoneDetect* library) { library->dataOffset += index; /* Verify file length */ - if(tmp + library->dataOffset != library->length) { + if(tmp + library->dataOffset != (uint32_t)library->length) { return -2; } return 0; } -static int ZDPointInBox(int32_t xl, int32_t x, int32_t xr, int32_t yl, int32_t y, int32_t yr) { +static int ZDPointInBox(int32_t xl, int32_t x, int32_t xr, int32_t yl, int32_t y, int32_t yr) +{ if((xl <= x && x <= xr) || (xr <= x && x <= xl)) { if((yl <= y && y <= yr) || (yr <= y && y <= yl)) { return 1; @@ -199,7 +273,8 @@ static int ZDPointInBox(int32_t xl, int32_t x, int32_t xr, int32_t yl, int32_t y return 0; } -static ZDLookupResult ZDPointInPolygon(ZoneDetect* library, uint32_t polygonIndex, int32_t latFixedPoint, int32_t lonFixedPoint, uint64_t* distanceSqrMin) { +static ZDLookupResult ZDPointInPolygon(const ZoneDetect *library, uint32_t polygonIndex, int32_t latFixedPoint, int32_t lonFixedPoint, uint64_t *distanceSqrMin) +{ uint32_t numVertices; int32_t pointLat = 0, pointLon = 0, diffLat = 0, diffLon = 0, firstLat = 0, firstLon = 0, prevLat = 0, prevLon = 0; lonFixedPoint -= 3; @@ -210,14 +285,13 @@ static ZDLookupResult ZDPointInPolygon(ZoneDetect* library, uint32_t polygonInde int prevQuadrant = 0, winding = 0; - uint32_t i; - for(i=0; i<=numVertices; i++) { - if(i=latFixedPoint) { - if(pointLon>=lonFixedPoint) { + if(pointLat >= latFixedPoint) { + if(pointLon >= lonFixedPoint) { quadrant = 0; } else { quadrant = 1; } } else { - if(pointLon>=lonFixedPoint) { + if(pointLon >= lonFixedPoint) { quadrant = 3; } else { quadrant = 2; } } - if(i>0) { + if(i > 0) { int windingNeedCompare = 0, lineIsStraight = 0; float a = 0, b = 0; @@ -271,26 +345,26 @@ static ZDLookupResult ZDPointInPolygon(ZoneDetect* library, uint32_t polygonInde /* Calculate the parameters of y=ax+b if needed */ if(!lineIsStraight && (distanceSqrMin || windingNeedCompare)) { - a = ((float)pointLat - (float)prevLat)/((float)pointLon - prevLon); - b = (float)pointLat - a*(float)pointLon; + a = ((float)pointLat - (float)prevLat) / ((float)pointLon - (float)prevLon); + b = (float)pointLat - a * (float)pointLon; } /* Jumped two quadrants. */ if(windingNeedCompare) { if(lineIsStraight) { - if(distanceSqrMin) *distanceSqrMin=0; + if(distanceSqrMin) *distanceSqrMin = 0; return ZD_LOOKUP_ON_BORDER_SEGMENT; } /* Check if the target is on the border */ - int32_t intersectLon = ((float)latFixedPoint - b)/a; + const int32_t intersectLon = (int32_t)(((float)latFixedPoint - b) / a); if(intersectLon == lonFixedPoint) { - if(distanceSqrMin) *distanceSqrMin=0; + if(distanceSqrMin) *distanceSqrMin = 0; return ZD_LOOKUP_ON_BORDER_SEGMENT; } /* Ok, it's not. In which direction did we go round the target? */ - int sign = (intersectLon < lonFixedPoint)?2:-2; + const int sign = (intersectLon < lonFixedPoint) ? 2 : -2; if(quadrant == 2 || quadrant == 3) { winding += sign; } else { @@ -302,36 +376,36 @@ static ZDLookupResult ZDPointInPolygon(ZoneDetect* library, uint32_t polygonInde if(distanceSqrMin) { float closestLon, closestLat; if(!lineIsStraight) { - closestLon=((float)lonFixedPoint+a*(float)latFixedPoint-a*b)/(a*a+1); - closestLat=(a*((float)lonFixedPoint+a*(float)latFixedPoint)+b)/(a*a+1); + closestLon = ((float)lonFixedPoint + a * (float)latFixedPoint - a * b) / (a * a + 1); + closestLat = (a * ((float)lonFixedPoint + a * (float)latFixedPoint) + b) / (a * a + 1); } else { if(pointLon == prevLon) { - closestLon=pointLon; - closestLat=latFixedPoint; + closestLon = (float)pointLon; + closestLat = (float)latFixedPoint; } else { - closestLon=lonFixedPoint; - closestLat=pointLat; + closestLon = (float)lonFixedPoint; + closestLat = (float)pointLat; } } - int closestInBox = ZDPointInBox(pointLon, closestLon, prevLon, pointLat, closestLat, prevLat); + const int closestInBox = ZDPointInBox(pointLon, (int32_t)closestLon, prevLon, pointLat, (int32_t)closestLat, prevLat); int64_t diffLat, diffLon; if(closestInBox) { /* Calculate squared distance to segment. */ - diffLat = closestLat - latFixedPoint; - diffLon = (closestLon - lonFixedPoint); + diffLat = (int64_t)(closestLat - (float)latFixedPoint); + diffLon = (int64_t)(closestLon - (float)lonFixedPoint); } else { /* * Calculate squared distance to vertices * It is enough to check the current point since the polygon is closed. */ - diffLat = pointLat - latFixedPoint; - diffLon = (pointLon - lonFixedPoint); + diffLat = (int64_t)(pointLat - latFixedPoint); + diffLon = (int64_t)(pointLon - lonFixedPoint); } /* Note: lon has half scale */ - uint64_t distanceSqr = diffLat*diffLat + diffLon*diffLon*4; + uint64_t distanceSqr = (uint64_t)(diffLat * diffLat) + (uint64_t)(diffLon * diffLon) * 4; if(distanceSqr < *distanceSqrMin) *distanceSqrMin = distanceSqr; } } @@ -350,15 +424,15 @@ static ZDLookupResult ZDPointInPolygon(ZoneDetect* library, uint32_t polygonInde } /* Should not happen */ - if(distanceSqrMin) *distanceSqrMin=0; + if(distanceSqrMin) *distanceSqrMin = 0; return ZD_LOOKUP_ON_BORDER_SEGMENT; } -void ZDCloseDatabase(ZoneDetect* library) { +void ZDCloseDatabase(ZoneDetect *library) +{ if(library) { if(library->fieldNames) { - unsigned int i; - for(i=0; inumFields; i++) { + for(size_t i = 0; i < (size_t)library->numFields; i++) { if(library->fieldNames[i]) { free(library->fieldNames[i]); } @@ -368,40 +442,76 @@ void ZDCloseDatabase(ZoneDetect* library) { if(library->notice) { free(library->notice); } - if(library->mapping) { - munmap(library->mapping, library->length); - } - if(library->fd >= 0) { - close(library->fd); - } + +#if defined(_MSC_VER) || defined(__MINGW32__) + if(!UnmapViewOfFile(library->mapping)) zdError(ZD_E_DB_MUNMAP_MSVIEW, (int)GetLastError()); + if(!CloseHandle(library->fdMap)) zdError(ZD_E_DB_MUNMAP , (int)GetLastError()); + if(!CloseHandle(library->fd)) zdError(ZD_E_DB_CLOSE , (int)GetLastError()); +#else + if(library->mapping && munmap(library->mapping, (size_t)(library->length))) zdError(ZD_E_DB_MUNMAP, errno); + if(library->fd >= 0 && close(library->fd)) zdError(ZD_E_DB_CLOSE , errno); +#endif + free(library); } } -ZoneDetect* ZDOpenDatabase(const char* path) { - ZoneDetect* library = (ZoneDetect*)malloc(sizeof(*library)); +ZoneDetect *ZDOpenDatabase(const char *path) +{ + ZoneDetect *const library = malloc(sizeof *library); if(library) { memset(library, 0, sizeof(*library)); +#if defined(_MSC_VER) || defined(__MINGW32__) + library->fd = CreateFile(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (library->fd == INVALID_HANDLE_VALUE) { + zdError(ZD_E_DB_OPEN, (int)GetLastError()); + goto fail; + } + + const DWORD fsize = GetFileSize(library->fd, NULL); + if (fsize == INVALID_FILE_SIZE) { + zdError(ZD_E_DB_SEEK, (int)GetLastError()); + goto fail; + } + library->length = (int32_t)fsize; + + library->fdMap = CreateFileMappingA(library->fd, NULL, PAGE_READONLY, 0, 0, NULL); + if (!library->fdMap) { + zdError(ZD_E_DB_MMAP, (int)GetLastError()); + goto fail; + } + + library->mapping = MapViewOfFile(library->fdMap, FILE_MAP_READ, 0, 0, 0); + if (!library->mapping) { + zdError(ZD_E_DB_MMAP_MSVIEW, (int)GetLastError()); + goto fail; + } +#else library->fd = open(path, O_RDONLY | O_CLOEXEC); if(library->fd < 0) { + zdError(ZD_E_DB_OPEN, errno); goto fail; } library->length = lseek(library->fd, 0, SEEK_END); if(library->length <= 0) { + zdError(ZD_E_DB_SEEK, errno); goto fail; } lseek(library->fd, 0, SEEK_SET); - library->mapping = mmap(NULL, library->length, PROT_READ, MAP_PRIVATE | MAP_FILE, library->fd, 0); + library->mapping = mmap(NULL, (size_t)library->length, PROT_READ, MAP_PRIVATE | MAP_FILE, library->fd, 0); if(!library->mapping) { + zdError(ZD_E_DB_MMAP, errno); goto fail; } +#endif /* Parse the header */ if(ZDParseHeader(library)) { + zdError(ZD_E_PARSE_HEADER, 0); goto fail; } } @@ -413,18 +523,19 @@ fail: return NULL; } -ZoneDetectResult* ZDLookup(ZoneDetect* library, float lat, float lon, float* safezone) { - int32_t latFixedPoint = ZDFloatToFixedPoint(lat, 90, library->precision); - int32_t lonFixedPoint = ZDFloatToFixedPoint(lon, 180, library->precision); - unsigned int numResults = 0; - uint64_t distanceSqrMin=-1; +ZoneDetectResult *ZDLookup(const ZoneDetect *library, float lat, float lon, float *safezone) +{ + const int32_t latFixedPoint = ZDFloatToFixedPoint(lat, 90, library->precision); + const int32_t lonFixedPoint = ZDFloatToFixedPoint(lon, 180, library->precision); + size_t numResults = 0; + uint64_t distanceSqrMin = (uint64_t)-1; /* Iterate over all polygons */ uint32_t bboxIndex = library->bboxOffset; - int32_t metadataIndex = 0; - int32_t polygonIndex = 0; + uint32_t metadataIndex = 0; + uint32_t polygonIndex = 0; - ZoneDetectResult* results = malloc(sizeof(ZoneDetectResult)); + ZoneDetectResult *results = malloc(sizeof *results); if(!results) { return NULL; } @@ -439,8 +550,8 @@ ZoneDetectResult* ZDLookup(ZoneDetect* library, float lat, float lon, float* saf if(!ZDDecodeVariableLengthSigned(library, &bboxIndex, &metadataIndexDelta)) break; if(!ZDDecodeVariableLengthUnsigned(library, &bboxIndex, &polygonIndexDelta)) break; - metadataIndex+=metadataIndexDelta; - polygonIndex+=polygonIndexDelta; + metadataIndex += (uint32_t)metadataIndexDelta; + polygonIndex += polygonIndexDelta; if(latFixedPoint >= minLat) { if(latFixedPoint <= maxLat && @@ -449,13 +560,13 @@ ZoneDetectResult* ZDLookup(ZoneDetect* library, float lat, float lon, float* saf /* Indices valid? */ if(library->metadataOffset + metadataIndex >= library->dataOffset) continue; - if(library->dataOffset + polygonIndex >= library->length) continue; + if(library->dataOffset + polygonIndex >= (uint32_t)library->length) continue; - ZDLookupResult lookupResult = ZDPointInPolygon(library, library->dataOffset + polygonIndex, latFixedPoint, lonFixedPoint, (safezone)?&distanceSqrMin:NULL); + const ZDLookupResult lookupResult = ZDPointInPolygon(library, library->dataOffset + polygonIndex, latFixedPoint, lonFixedPoint, (safezone) ? &distanceSqrMin : NULL); if(lookupResult == ZD_LOOKUP_PARSE_ERROR) { break; } else if(lookupResult != ZD_LOOKUP_NOT_IN_ZONE) { - ZoneDetectResult* newResults = realloc(results, sizeof(ZoneDetectResult) * (numResults+2)); + ZoneDetectResult *const newResults = realloc(results, sizeof *newResults * (numResults + 2)); if(newResults) { results = newResults; @@ -477,11 +588,10 @@ ZoneDetectResult* ZDLookup(ZoneDetect* library, float lat, float lon, float* saf } /* Clean up results */ - unsigned int i, j; - for(i=0; imetadataOffset + results[i].metaId; - results[i].data = malloc(library->numFields * sizeof(char*)); + results[i].data = malloc(library->numFields * sizeof *results[i].data); if(results[i].data) { - for(j=0; jnumFields; j++) { + for(size_t j = 0; j < library->numFields; j++) { results[i].data[j] = ZDParseString(library, &tmpIndex); } } @@ -536,13 +646,14 @@ ZoneDetectResult* ZDLookup(ZoneDetect* library, float lat, float lon, float* saf results[numResults].data = NULL; if(safezone) { - *safezone = sqrtf(distanceSqrMin) * 90 / (float)(1 << (library->precision-1)); + *safezone = sqrtf((float)distanceSqrMin) * 90 / (float)(1 << (library->precision - 1)); } return results; } -void ZDFreeResults(ZoneDetectResult* results) { +void ZDFreeResults(ZoneDetectResult *results) +{ unsigned int index = 0; if(!results) { @@ -551,8 +662,7 @@ void ZDFreeResults(ZoneDetectResult* results) { while(results[index].lookupResult != ZD_LOOKUP_END) { if(results[index].data) { - unsigned int i; - for(i=0; inotice; } -uint8_t ZDGetTableType(ZoneDetect* library) { +uint8_t ZDGetTableType(const ZoneDetect *library) +{ return library->tableType; } -const char* ZDLookupResultToString(ZDLookupResult result) { +const char *ZDLookupResultToString(ZDLookupResult result) +{ switch(result) { - case ZD_LOOKUP_IGNORE: - return "Ignore"; - case ZD_LOOKUP_END: - return "End"; - case ZD_LOOKUP_PARSE_ERROR: - return "Parsing error"; - case ZD_LOOKUP_NOT_IN_ZONE: - return "Not in zone"; - case ZD_LOOKUP_IN_ZONE: - return "In zone"; - case ZD_LOOKUP_IN_EXCLUDED_ZONE: - return "In excluded zone"; - case ZD_LOOKUP_ON_BORDER_VERTEX: - return "Target point is border vertex"; - case ZD_LOOKUP_ON_BORDER_SEGMENT: - return "Target point is on border"; + case ZD_LOOKUP_IGNORE: + return "Ignore"; + case ZD_LOOKUP_END: + return "End"; + case ZD_LOOKUP_PARSE_ERROR: + return "Parsing error"; + case ZD_LOOKUP_NOT_IN_ZONE: + return "Not in zone"; + case ZD_LOOKUP_IN_ZONE: + return "In zone"; + case ZD_LOOKUP_IN_EXCLUDED_ZONE: + return "In excluded zone"; + case ZD_LOOKUP_ON_BORDER_VERTEX: + return "Target point is border vertex"; + case ZD_LOOKUP_ON_BORDER_SEGMENT: + return "Target point is on border"; } return "Unknown"; } +#define ZD_E_COULD_NOT(msg) "could not " msg + +const char *ZDGetErrorString(int errZD) +{ + switch ((enum ZDInternalError)errZD) { + default: assert(0); + case ZD_OK : return ""; + case ZD_E_DB_OPEN : return ZD_E_COULD_NOT("open database file"); + case ZD_E_DB_SEEK : return ZD_E_COULD_NOT("retrieve database file size"); + case ZD_E_DB_MMAP : return ZD_E_COULD_NOT("map database file to system memory"); +#if defined(_MSC_VER) || defined(__MINGW32__) + case ZD_E_DB_MMAP_MSVIEW : return ZD_E_COULD_NOT("open database file view"); + case ZD_E_DB_MAP_EXCEPTION: return "I/O exception occurred while accessing database file view"; + case ZD_E_DB_MUNMAP_MSVIEW: return ZD_E_COULD_NOT("close database file view"); +#endif + case ZD_E_DB_MUNMAP : return ZD_E_COULD_NOT("unmap database"); + case ZD_E_DB_CLOSE : return ZD_E_COULD_NOT("close database file"); + case ZD_E_PARSE_HEADER : return ZD_E_COULD_NOT("parse database header"); + } +} + +#undef ZD_E_COULD_NOT + +int ZDSetErrorHandler(void (*handler)(int, int)) +{ + zdErrorHandler = handler; + return 0; +} diff --git a/src/zonedetect.h b/src/zonedetect.h index 4be53672..d23bbdfe 100644 --- a/src/zonedetect.h +++ b/src/zonedetect.h @@ -27,8 +27,16 @@ #include -#ifndef _ZONEDETECT_H_ -#define _ZONEDETECT_H_ +#ifndef INCL_ZONEDETECT_H_ +#define INCL_ZONEDETECT_H_ + +#if !defined(ZD_EXPORT) + #if defined(_MSC_VER) + #define ZD_EXPORT __declspec(dllimport) + #else + #define ZD_EXPORT + #endif +#endif typedef enum { ZD_LOOKUP_IGNORE = -3, @@ -46,8 +54,8 @@ typedef struct { uint32_t metaId; uint8_t numFields; - char** fieldNames; - char** data; + char **fieldNames; + char **data; } ZoneDetectResult; struct ZoneDetectOpaque; @@ -57,18 +65,21 @@ typedef struct ZoneDetectOpaque ZoneDetect; extern "C" { #endif -ZoneDetect* ZDOpenDatabase(const char* path); -void ZDCloseDatabase(ZoneDetect* library); +ZD_EXPORT ZoneDetect *ZDOpenDatabase(const char *path); +ZD_EXPORT void ZDCloseDatabase(ZoneDetect *library); -ZoneDetectResult* ZDLookup(ZoneDetect* library, float lat, float lon, float* safezone); -void ZDFreeResults(ZoneDetectResult* results); +ZD_EXPORT ZoneDetectResult *ZDLookup(const ZoneDetect *library, float lat, float lon, float *safezone); +ZD_EXPORT void ZDFreeResults(ZoneDetectResult *results); -const char* ZDGetNotice(ZoneDetect* library); -uint8_t ZDGetTableType(ZoneDetect* library); -const char* ZDLookupResultToString(ZDLookupResult result); +ZD_EXPORT const char *ZDGetNotice(const ZoneDetect *library); +ZD_EXPORT uint8_t ZDGetTableType(const ZoneDetect *library); +ZD_EXPORT const char *ZDLookupResultToString(ZDLookupResult result); + +ZD_EXPORT int ZDSetErrorHandler(void (*handler)(int, int)); +ZD_EXPORT const char *ZDGetErrorString(int errZD); #ifdef __cplusplus } #endif -#endif +#endif // INCL_ZONEDETECT_H_