aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2020-01-22 14:09:14 +0100
committerGitHub <noreply@github.com>2020-01-22 14:09:14 +0100
commitc5fb54168665d41503ef3a08f0534da58949b632 (patch)
tree19e2adc8809290881a6a13ec6001013d03170c7d
parenta6390b59ae2bad2a763e7ab3341ee4c80e708b3d (diff)
parent66fd99a8831955034cb25c8468ecfe1f9d3a7d62 (diff)
downloadPROJ-c5fb54168665d41503ef3a08f0534da58949b632.tar.gz
PROJ-c5fb54168665d41503ef3a08f0534da58949b632.zip
Merge pull request #1839 from rouault/rfc4_utf8
[RFC4_dev] Use Win32 Unicode APIs and expect all strings to be UTF-8 (fixes #1765)
-rw-r--r--CMakeLists.txt2
-rw-r--r--Doxyfile2
-rw-r--r--cmake/ProjTest.cmake27
-rw-r--r--configure.ac13
-rw-r--r--data/Makefile.am1
-rw-r--r--data/proj.ini2
-rw-r--r--data/tests/test_hgrid_with_two_level_of_subgrids_no_grid_name.tifbin0 -> 3504 bytes
-rw-r--r--docs/source/apps/cct.rst9
-rw-r--r--docs/source/conf.py2
-rw-r--r--docs/source/development/reference/cpp/common.rst2
-rw-r--r--docs/source/development/reference/cpp/cpp_general.rst2
-rw-r--r--docs/source/development/reference/cpp/crs.rst2
-rw-r--r--docs/source/development/reference/cpp/cs.rst2
-rw-r--r--docs/source/development/reference/cpp/datum.rst2
-rw-r--r--docs/source/development/reference/cpp/io.rst2
-rw-r--r--docs/source/development/reference/cpp/metadata.rst2
-rw-r--r--docs/source/development/reference/cpp/operation.rst2
-rw-r--r--docs/source/development/reference/cpp/util.rst2
-rw-r--r--docs/source/development/reference/datatypes.rst39
-rw-r--r--docs/source/development/reference/functions.rst50
-rw-r--r--docs/source/operations/transformations/deformation.rst4
-rw-r--r--docs/source/resource_files.rst4
-rw-r--r--docs/source/usage/differences.rst6
-rwxr-xr-xscripts/doxygen.sh10
-rw-r--r--scripts/reference_exported_symbols.txt31
-rw-r--r--src/4D_api.cpp7
-rw-r--r--src/Makefile.am7
-rw-r--r--src/filemanager.cpp3557
-rw-r--r--src/filemanager.hpp22
-rw-r--r--src/grids.cpp24
-rw-r--r--src/grids.hpp135
-rw-r--r--src/iso19111/factory.cpp30
-rw-r--r--src/lib_proj.cmake18
-rw-r--r--src/networkfilemanager.cpp2541
-rw-r--r--src/open_lib.cpp576
-rw-r--r--src/proj.h61
-rw-r--r--src/proj_internal.h21
-rw-r--r--src/sqlite3_utils.cpp (renamed from src/sqlite3.cpp)2
-rw-r--r--src/sqlite3_utils.hpp (renamed from src/sqlite3.hpp)0
-rw-r--r--test/cli/ntv2_out.dist8
-rw-r--r--test/cli/td_out.dist2
-rwxr-xr-xtest/cli/testntv211
-rw-r--r--test/gie/geotiff_grids.gie14
-rw-r--r--test/unit/CMakeLists.txt12
-rw-r--r--test/unit/Makefile.am2
-rw-r--r--test/unit/test_c_api.cpp22
-rw-r--r--test/unit/test_grids.cpp227
-rw-r--r--test/unit/test_network.cpp192
-rwxr-xr-xtravis/mingw32/install.sh1
49 files changed, 4682 insertions, 3030 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0d5524db..41c0c0c8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -84,8 +84,6 @@ elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Intel")
endif()
endif()
-add_definitions(-DNOMINMAX)
-
set(PROJ_C_WARN_FLAGS "${PROJ_C_WARN_FLAGS}"
CACHE STRING "C flags used to compile PROJ targets")
set(PROJ_CXX_WARN_FLAGS "${PROJ_CXX_WARN_FLAGS}"
diff --git a/Doxyfile b/Doxyfile
index 2d263ed7..e1a523b6 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -772,7 +772,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
-INPUT = src/iso19111 include/proj src/proj.h src/proj_experimental.h src/general_doc.dox src/filemanager.cpp
+INPUT = src/iso19111 include/proj src/proj.h src/proj_experimental.h src/general_doc.dox src/filemanager.cpp src/networkfilemanager.cpp
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
diff --git a/cmake/ProjTest.cmake b/cmake/ProjTest.cmake
index a016cd9a..ad612970 100644
--- a/cmake/ProjTest.cmake
+++ b/cmake/ProjTest.cmake
@@ -2,6 +2,16 @@
# add test with sh script
#
+function(proj_test_set_properties TESTNAME)
+ if(MSVC)
+ set_tests_properties( ${TESTNAME}
+ PROPERTIES ENVIRONMENT "PROJ_IGNORE_USER_WRITABLE_DIRECTORY=YES;PROJ_LIB=${PROJECT_BINARY_DIR}/data\\;${PROJECT_SOURCE_DIR}/data")
+ else()
+ set_tests_properties( ${TESTNAME}
+ PROPERTIES ENVIRONMENT "PROJ_IGNORE_USER_WRITABLE_DIRECTORY=YES;PROJ_LIB=${PROJECT_BINARY_DIR}/data:${PROJECT_SOURCE_DIR}/data")
+ endif()
+endfunction()
+
function(proj_add_test_script_sh SH_NAME BIN_USE)
if(UNIX)
get_filename_component(testname ${SH_NAME} NAME_WE)
@@ -26,13 +36,7 @@ function(proj_add_test_script_sh SH_NAME BIN_USE)
COMMAND ${PROJECT_SOURCE_DIR}/test/cli/${SH_NAME}
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${${BIN_USE}}
)
- if(MSVC)
- set_tests_properties( ${testname}
- PROPERTIES ENVIRONMENT "PROJ_IGNORE_USER_WRITABLE_DIRECTORY=YES;PROJ_LIB=${PROJECT_BINARY_DIR}/data\\;${PROJECT_SOURCE_DIR}/data")
- else()
- set_tests_properties( ${testname}
- PROPERTIES ENVIRONMENT "PROJ_IGNORE_USER_WRITABLE_DIRECTORY=YES;PROJ_LIB=${PROJECT_BINARY_DIR}/data:${PROJECT_SOURCE_DIR}/data")
- endif()
+ proj_test_set_properties(${testname})
endif()
endif()
@@ -49,13 +53,6 @@ function(proj_add_gie_test TESTNAME TESTCASE)
${TESTFILE}
)
- if(MSVC)
- set_tests_properties( ${TESTNAME}
- PROPERTIES ENVIRONMENT "PROJ_IGNORE_USER_WRITABLE_DIRECTORY=YES;PROJ_LIB=${PROJECT_BINARY_DIR}/data\\;${PROJECT_SOURCE_DIR}/data")
- else()
- set_tests_properties( ${TESTNAME}
- PROPERTIES ENVIRONMENT "PROJ_IGNORE_USER_WRITABLE_DIRECTORY=YES;PROJ_LIB=${PROJECT_BINARY_DIR}/data:${PROJECT_SOURCE_DIR}/data")
- endif()
-
+ proj_test_set_properties(${TESTNAME})
endfunction()
diff --git a/configure.ac b/configure.ac
index afde2712..2bdcafb8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -168,8 +168,17 @@ AC_SUBST(C_WFLAGS,$C_WFLAGS)
AC_SUBST(CXX_WFLAGS,$CXX_WFLAGS)
AC_SUBST(NO_ZERO_AS_NULL_POINTER_CONSTANT_FLAG,$NO_ZERO_AS_NULL_POINTER_CONSTANT_FLAG)
-CFLAGS="${CFLAGS} -fvisibility=hidden -DNOMINMAX"
-CXXFLAGS="${CXXFLAGS} -fvisibility=hidden -DNOMINMAX"
+CFLAGS="${CFLAGS} -fvisibility=hidden"
+CXXFLAGS="${CXXFLAGS} -fvisibility=hidden"
+
+case "${host_os}" in
+ cygwin* | mingw32* | pw32* | beos* | darwin*)
+ CFLAGS="${CFLAGS} -DNOMINMAX"
+ CXXFLAGS="${CXXFLAGS} -DNOMINMAX"
+ ;;
+ *)
+ ;;
+esac
dnl Checks for libraries.
save_CFLAGS="$CFLAGS"
diff --git a/data/Makefile.am b/data/Makefile.am
index 4bba2649..d8abfe32 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -85,6 +85,7 @@ EXTRA_DIST = proj.ini GL27 nad.lst nad27 nad83 \
tests/nkgrf03vel_realigned_extract.tif \
tests/nkgrf03vel_realigned_xy_extract.ct2 \
tests/nkgrf03vel_realigned_z_extract.gtx \
+ tests/test_hgrid_with_two_level_of_subgrids_no_grid_name.tif \
null \
generate_all_sql_in.cmake sql_filelist.cmake \
$(SQL_ORDERED_LIST)
diff --git a/data/proj.ini b/data/proj.ini
index 2146ce41..0ae33249 100644
--- a/data/proj.ini
+++ b/data/proj.ini
@@ -11,6 +11,6 @@ cdn_endpoint = https://cdn.proj.org
cache_enabled = on
-cache_size_MB = 100
+cache_size_MB = 300
cache_ttl_sec = 86400
diff --git a/data/tests/test_hgrid_with_two_level_of_subgrids_no_grid_name.tif b/data/tests/test_hgrid_with_two_level_of_subgrids_no_grid_name.tif
new file mode 100644
index 00000000..2abb3226
--- /dev/null
+++ b/data/tests/test_hgrid_with_two_level_of_subgrids_no_grid_name.tif
Binary files differ
diff --git a/docs/source/apps/cct.rst b/docs/source/apps/cct.rst
index 33c716fe..c290e1b2 100644
--- a/docs/source/apps/cct.rst
+++ b/docs/source/apps/cct.rst
@@ -91,6 +91,15 @@ cartesian coordinates) and
*Coordinate Transformations*, which are coordinate operations where
input and output datums differ (e.g. change of reference frame).
+Use of remote grids
+*******************
+
+.. versionadded:: 7.0.0
+
+If the :envvar:`PROJ_NETWORK` environment variable is set to ``ON``,
+:program:`cct` will attempt to use remote grids stored on CDN (Content
+Delivery Network) storage, when they are not available locally.
+
Examples
********
diff --git a/docs/source/conf.py b/docs/source/conf.py
index b9e1fa8a..c2142667 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -376,7 +376,7 @@ texinfo_documents = [
#texinfo_no_detailmenu = False
breathe_projects = {
- "cpp_stuff":"../build/xml/",
+ "doxygen_api":"../build/xml/",
}
import redirects
diff --git a/docs/source/development/reference/cpp/common.rst b/docs/source/development/reference/cpp/common.rst
index c1a28d37..4e92f6b2 100644
--- a/docs/source/development/reference/cpp/common.rst
+++ b/docs/source/development/reference/cpp/common.rst
@@ -4,5 +4,5 @@ common namespace
----------------
.. doxygennamespace:: osgeo::proj::common
- :project: cpp_stuff
+ :project: doxygen_api
:members:
diff --git a/docs/source/development/reference/cpp/cpp_general.rst b/docs/source/development/reference/cpp/cpp_general.rst
index 3b47d01e..d54daaa8 100644
--- a/docs/source/development/reference/cpp/cpp_general.rst
+++ b/docs/source/development/reference/cpp/cpp_general.rst
@@ -4,4 +4,4 @@ General documentation
---------------------
.. doxygenfile:: general_doc.dox.reworked.h
- :project: cpp_stuff
+ :project: doxygen_api
diff --git a/docs/source/development/reference/cpp/crs.rst b/docs/source/development/reference/cpp/crs.rst
index 2abea378..f8df118e 100644
--- a/docs/source/development/reference/cpp/crs.rst
+++ b/docs/source/development/reference/cpp/crs.rst
@@ -4,5 +4,5 @@ crs namespace
-------------
.. doxygennamespace:: osgeo::proj::crs
- :project: cpp_stuff
+ :project: doxygen_api
:members:
diff --git a/docs/source/development/reference/cpp/cs.rst b/docs/source/development/reference/cpp/cs.rst
index b168213f..376ae6a6 100644
--- a/docs/source/development/reference/cpp/cs.rst
+++ b/docs/source/development/reference/cpp/cs.rst
@@ -4,5 +4,5 @@ cs namespace
------------
.. doxygennamespace:: osgeo::proj::cs
- :project: cpp_stuff
+ :project: doxygen_api
:members:
diff --git a/docs/source/development/reference/cpp/datum.rst b/docs/source/development/reference/cpp/datum.rst
index 1fee5f8a..0a545a68 100644
--- a/docs/source/development/reference/cpp/datum.rst
+++ b/docs/source/development/reference/cpp/datum.rst
@@ -4,5 +4,5 @@ datum namespace
---------------
.. doxygennamespace:: osgeo::proj::datum
- :project: cpp_stuff
+ :project: doxygen_api
:members:
diff --git a/docs/source/development/reference/cpp/io.rst b/docs/source/development/reference/cpp/io.rst
index 9da0f680..9729af90 100644
--- a/docs/source/development/reference/cpp/io.rst
+++ b/docs/source/development/reference/cpp/io.rst
@@ -4,5 +4,5 @@ io namespace
------------
.. doxygennamespace:: osgeo::proj::io
- :project: cpp_stuff
+ :project: doxygen_api
:members:
diff --git a/docs/source/development/reference/cpp/metadata.rst b/docs/source/development/reference/cpp/metadata.rst
index b3960122..348627e8 100644
--- a/docs/source/development/reference/cpp/metadata.rst
+++ b/docs/source/development/reference/cpp/metadata.rst
@@ -4,5 +4,5 @@ metadata namespace
------------------
.. doxygennamespace:: osgeo::proj::metadata
- :project: cpp_stuff
+ :project: doxygen_api
:members:
diff --git a/docs/source/development/reference/cpp/operation.rst b/docs/source/development/reference/cpp/operation.rst
index bc3f201d..88096cb8 100644
--- a/docs/source/development/reference/cpp/operation.rst
+++ b/docs/source/development/reference/cpp/operation.rst
@@ -4,5 +4,5 @@ operation namespace
-------------------
.. doxygennamespace:: osgeo::proj::operation
- :project: cpp_stuff
+ :project: doxygen_api
:members:
diff --git a/docs/source/development/reference/cpp/util.rst b/docs/source/development/reference/cpp/util.rst
index 1aba2954..bc9676a4 100644
--- a/docs/source/development/reference/cpp/util.rst
+++ b/docs/source/development/reference/cpp/util.rst
@@ -4,5 +4,5 @@ util namespace
--------------
.. doxygennamespace:: osgeo::proj::util
- :project: cpp_stuff
+ :project: doxygen_api
:members:
diff --git a/docs/source/development/reference/datatypes.rst b/docs/source/development/reference/datatypes.rst
index 18bd7efd..7ec17eb1 100644
--- a/docs/source/development/reference/datatypes.rst
+++ b/docs/source/development/reference/datatypes.rst
@@ -797,11 +797,48 @@ Logging
.. versionadded:: 5.1.0
+Setting custom I/O functions
+-------------------------------------------------------------------------------
+
+.. versionadded:: 7.0.0
+
+.. doxygenstruct:: PROJ_FILE_API
+ :project: doxygen_api
+ :members:
+
+.. doxygentypedef:: PROJ_FILE_HANDLE
+ :project: doxygen_api
+
+.. doxygenenum:: PROJ_OPEN_ACCESS
+ :project: doxygen_api
+
+
+Network related functionality
+-------------------------------------------------------------------------------
+
+.. versionadded:: 7.0.0
+
+.. doxygentypedef:: PROJ_NETWORK_HANDLE
+ :project: doxygen_api
+
+.. doxygentypedef:: proj_network_open_cbk_type
+ :project: doxygen_api
+
+.. doxygentypedef:: proj_network_close_cbk_type
+ :project: doxygen_api
+
+.. doxygentypedef:: proj_network_get_header_value_cbk_type
+ :project: doxygen_api
+
+.. doxygentypedef:: proj_network_read_range_type
+ :project: doxygen_api
+
+
C API for ISO-19111 functionality
-------------------------------------------------------------------------------
.. doxygengroup:: iso19111_types
- :project: cpp_stuff
+ :project: doxygen_api
:content-only:
diff --git a/docs/source/development/reference/functions.rst b/docs/source/development/reference/functions.rst
index c010fd13..a2a0c3ae 100644
--- a/docs/source/development/reference/functions.rst
+++ b/docs/source/development/reference/functions.rst
@@ -697,6 +697,54 @@ Various
:returns: :c:type:`int` 1 if output units is expected in radians, otherwise 0
+Setting custom I/O functions
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+.. versionadded:: 7.0.0
+
+.. doxygenfunction:: proj_context_set_fileapi
+ :project: doxygen_api
+
+.. doxygenfunction:: proj_context_set_sqlite3_vfs_name
+ :project: doxygen_api
+
+
+Network related functionality
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+.. versionadded:: 7.0.0
+
+.. doxygenfunction:: proj_context_set_network_callbacks
+ :project: doxygen_api
+
+.. doxygenfunction:: proj_context_set_enable_network
+ :project: doxygen_api
+
+.. doxygenfunction:: proj_context_set_url_endpoint
+ :project: doxygen_api
+
+.. doxygenfunction:: proj_grid_cache_set_enable
+ :project: doxygen_api
+
+.. doxygenfunction:: proj_grid_cache_set_filename
+ :project: doxygen_api
+
+.. doxygenfunction:: proj_grid_cache_set_max_size
+ :project: doxygen_api
+
+.. doxygenfunction:: proj_grid_cache_set_ttl
+ :project: doxygen_api
+
+.. doxygenfunction:: proj_grid_cache_clear
+ :project: doxygen_api
+
+.. doxygenfunction:: proj_is_download_needed
+ :project: doxygen_api
+
+.. doxygenfunction:: proj_download_file
+ :project: doxygen_api
+
+
Cleanup
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@@ -727,6 +775,6 @@ which are not of type CRS (can be tested with :c:func:`proj_is_crs`),
will return an error when used with functions of this section.
.. doxygengroup:: iso19111_functions
- :project: cpp_stuff
+ :project: doxygen_api
:content-only:
diff --git a/docs/source/operations/transformations/deformation.rst b/docs/source/operations/transformations/deformation.rst
index aefda3a6..02924a25 100644
--- a/docs/source/operations/transformations/deformation.rst
+++ b/docs/source/operations/transformations/deformation.rst
@@ -99,6 +99,8 @@ Parameters
Grids for the horizontal component of a deformation model is expected to be
in CTable2 format.
+ .. note:: :option:`+xy_grids` is mutually exclusive with :option:`+grids`
+
.. option:: +z_grids=<list>
Comma-separated list of grids to load. If a grid is prefixed by an `@` the
@@ -108,6 +110,8 @@ Parameters
Grids for the vertical component of a deformation model is expected to be
in either GTX format.
+ .. note:: :option:`+z_grids` is mutually exclusive with :option:`+grids`
+
.. option:: +grids=<list>
.. versionadded:: 7.0.0
diff --git a/docs/source/resource_files.rst b/docs/source/resource_files.rst
index 3684a948..a07d6d6a 100644
--- a/docs/source/resource_files.rst
+++ b/docs/source/resource_files.rst
@@ -67,6 +67,8 @@ A proj installation includes a SQLite database of transformation information
that must be accessible for the library to work properly. The library will
print an error if the database can't be found.
+.. _proj-ini:
+
proj.ini
-------------------------------------------------------------------------------
@@ -92,7 +94,7 @@ Its default content is:
cache_enabled = on
- cache_size_MB = 100
+ cache_size_MB = 300
cache_ttl_sec = 86400
diff --git a/docs/source/usage/differences.rst b/docs/source/usage/differences.rst
index 4d05543a..8fcf51d6 100644
--- a/docs/source/usage/differences.rst
+++ b/docs/source/usage/differences.rst
@@ -114,3 +114,9 @@ exclusive with :option:`+t_epoch`. :option:`+dt` is used when deformation
for a set amount of time is needed and :option:`+t_epoch` is used (in
conjunction with the observation time of the input coordinate) when
deformation from a specific epoch to the observation time is needed.
+
+Version 7.0.0
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+The value of all path, filenames passed to PROJ through function calls, PROJ
+strings or environment variables should be encoded in UTF-8.
diff --git a/scripts/doxygen.sh b/scripts/doxygen.sh
index 9646eed8..380afcfa 100755
--- a/scripts/doxygen.sh
+++ b/scripts/doxygen.sh
@@ -34,7 +34,11 @@ fi
mkdir -p docs/build/tmp_breathe
python scripts/generate_breathe_friendly_general_doc.py
rm -rf docs/build/xml/
-(cat Doxyfile; printf "GENERATE_HTML=NO\nGENERATE_XML=YES\nINPUT= src/iso19111 include/proj src/proj.h src/filemanager.cpp docs/build/tmp_breathe/general_doc.dox.reworked.h") | doxygen - > docs/build/tmp_breathe/docs_log.txt 2>&1
+
+# Ugly hack to workaround a bug of Doxygen 1.8.17 that erroneously detect proj_network_get_header_value_cbk_type/ as a variable
+sed "s/const char\* (\*proj_network_get_header_value_cbk_type/CONST_CHAR\* (\*proj_network_get_header_value_cbk_type/" < src/proj.h > docs/build/tmp_breathe/proj.h
+
+(cat Doxyfile; printf "GENERATE_HTML=NO\nGENERATE_XML=YES\nINPUT= src/iso19111 include/proj docs/build/tmp_breathe/proj.h src/filemanager.cpp src/networkfilemanager.cpp docs/build/tmp_breathe/general_doc.dox.reworked.h") | doxygen - > docs/build/tmp_breathe/docs_log.txt 2>&1
if grep -i warning docs/build/tmp_breathe/docs_log.txt; then
echo "Doxygen warnings found" && cat docs/build/tmp_breathe/docs_log.txt && /bin/false;
else
@@ -56,5 +60,9 @@ done
sed "s/Convention/Convention_/g" < ${TOPDIR}/docs/build/xml/classosgeo_1_1proj_1_1io_1_1WKTFormatter.xml | sed "s/WKT2_2018/_WKT2_2018/g" | sed "s/WKT2_2019/_WKT2_2019/g" | sed "s/WKT2_2015/_WKT2_2015/g" | sed "s/WKT1_GDAL/_WKT1_GDAL/g" | sed "s/WKT1_ESRI/_WKT1_ESRI/g" > ${TOPDIR}/docs/build/xml/classosgeo_1_1proj_1_1io_1_1WKTFormatter.xml.tmp
mv ${TOPDIR}/docs/build/xml/classosgeo_1_1proj_1_1io_1_1WKTFormatter.xml.tmp ${TOPDIR}/docs/build/xml/classosgeo_1_1proj_1_1io_1_1WKTFormatter.xml
+# Ugly hack to workaround a bug of Doxygen 1.8.17 that erroneously detect proj_network_get_header_value_cbk_type/ as a variable
+sed "s/CONST_CHAR/const char/" < ${TOPDIR}/docs/build/xml/proj_8h.xml > ${TOPDIR}/docs/build/xml/proj_8h.xml.tmp
+mv ${TOPDIR}/docs/build/xml/proj_8h.xml.tmp ${TOPDIR}/docs/build/xml/proj_8h.xml
+
popd > /dev/null || exit
diff --git a/scripts/reference_exported_symbols.txt b/scripts/reference_exported_symbols.txt
index d10f3bc1..1774b79b 100644
--- a/scripts/reference_exported_symbols.txt
+++ b/scripts/reference_exported_symbols.txt
@@ -279,6 +279,26 @@ osgeo::proj::datum::TemporalDatum::temporalOrigin() const
osgeo::proj::datum::VerticalReferenceFrame::create(osgeo::proj::util::PropertyMap const&, osgeo::proj::util::optional<std::string> const&, osgeo::proj::util::optional<osgeo::proj::datum::RealizationMethod> const&)
osgeo::proj::datum::VerticalReferenceFrame::realizationMethod() const
osgeo::proj::datum::VerticalReferenceFrame::~VerticalReferenceFrame()
+osgeo::proj::GenericShiftGrid::~GenericShiftGrid()
+osgeo::proj::GenericShiftGrid::GenericShiftGrid(std::string const&, int, int, osgeo::proj::ExtentAndRes const&)
+osgeo::proj::GenericShiftGrid::gridAt(double, double) const
+osgeo::proj::GenericShiftGridSet::~GenericShiftGridSet()
+osgeo::proj::GenericShiftGridSet::GenericShiftGridSet()
+osgeo::proj::GenericShiftGridSet::gridAt(double, double) const
+osgeo::proj::GenericShiftGridSet::open(projCtx_t*, std::string const&)
+osgeo::proj::GenericShiftGridSet::reassign_context(projCtx_t*)
+osgeo::proj::GenericShiftGridSet::reopen(projCtx_t*)
+osgeo::proj::Grid::~Grid()
+osgeo::proj::Grid::Grid(std::string const&, int, int, osgeo::proj::ExtentAndRes const&)
+osgeo::proj::HorizontalShiftGrid::gridAt(double, double) const
+osgeo::proj::HorizontalShiftGrid::~HorizontalShiftGrid()
+osgeo::proj::HorizontalShiftGrid::HorizontalShiftGrid(std::string const&, int, int, osgeo::proj::ExtentAndRes const&)
+osgeo::proj::HorizontalShiftGridSet::gridAt(double, double) const
+osgeo::proj::HorizontalShiftGridSet::~HorizontalShiftGridSet()
+osgeo::proj::HorizontalShiftGridSet::HorizontalShiftGridSet()
+osgeo::proj::HorizontalShiftGridSet::open(projCtx_t*, std::string const&)
+osgeo::proj::HorizontalShiftGridSet::reassign_context(projCtx_t*)
+osgeo::proj::HorizontalShiftGridSet::reopen(projCtx_t*)
osgeo::proj::internal::ci_equal(std::string const&, char const*)
osgeo::proj::internal::ci_equal(std::string const&, std::string const&)
osgeo::proj::internal::ci_find(std::string const&, char const*)
@@ -696,6 +716,15 @@ osgeo::proj::util::PropertyMap::set(std::string const&, std::string const&)
osgeo::proj::util::PropertyMap::set(std::string const&, std::vector<std::string, std::allocator<std::string> > const&)
osgeo::proj::util::UnsupportedOperationException::~UnsupportedOperationException()
osgeo::proj::util::UnsupportedOperationException::UnsupportedOperationException(osgeo::proj::util::UnsupportedOperationException const&)
+osgeo::proj::VerticalShiftGrid::gridAt(double, double) const
+osgeo::proj::VerticalShiftGridSet::gridAt(double, double) const
+osgeo::proj::VerticalShiftGridSet::open(projCtx_t*, std::string const&)
+osgeo::proj::VerticalShiftGridSet::reassign_context(projCtx_t*)
+osgeo::proj::VerticalShiftGridSet::reopen(projCtx_t*)
+osgeo::proj::VerticalShiftGridSet::~VerticalShiftGridSet()
+osgeo::proj::VerticalShiftGridSet::VerticalShiftGridSet()
+osgeo::proj::VerticalShiftGrid::~VerticalShiftGrid()
+osgeo::proj::VerticalShiftGrid::VerticalShiftGrid(std::string const&, int, int, osgeo::proj::ExtentAndRes const&)
pj_acquire_lock
pj_add_type_crs_if_needed(std::string const&)
pj_apply_gridshift
@@ -798,10 +827,12 @@ proj_context_guess_wkt_dialect
proj_context_set_autoclose_database
proj_context_set_database_path
proj_context_set_enable_network
+proj_context_set_fileapi
proj_context_set_file_finder
proj_context_set_network_callbacks
proj_context_set(PJconsts*, projCtx_t*)
proj_context_set_search_paths
+proj_context_set_sqlite3_vfs_name
proj_context_set_url_endpoint
proj_context_use_proj4_init_rules
proj_convert_conversion_to_other_method
diff --git a/src/4D_api.cpp b/src/4D_api.cpp
index 9107723d..6b53363a 100644
--- a/src/4D_api.cpp
+++ b/src/4D_api.cpp
@@ -1461,10 +1461,10 @@ PJ_INFO proj_info (void) {
pj_context_get_user_writable_directory(ctx, false).c_str(),
&buf_size);
}
- const char *envPROJ_LIB = getenv("PROJ_LIB");
- buf = path_append(buf, envPROJ_LIB, &buf_size);
+ const std::string envPROJ_LIB = NS_PROJ::FileManager::getProjLibEnvVar(ctx);
+ buf = path_append(buf, envPROJ_LIB.empty() ? nullptr : envPROJ_LIB.c_str(), &buf_size);
#ifdef PROJ_LIB
- if (envPROJ_LIB == nullptr) {
+ if (envPROJ_LIB.empty()) {
buf = path_append(buf, PROJ_LIB, &buf_size);
}
#endif
@@ -1770,3 +1770,4 @@ PJ_FACTORS proj_factors(PJ *P, PJ_COORD lp) {
return factors;
}
+
diff --git a/src/Makefile.am b/src/Makefile.am
index 39667509..9b513fb3 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -190,7 +190,7 @@ libproj_la_SOURCES = \
deriv.cpp ell_set.cpp ellps.cpp errno.cpp \
factors.cpp fwd.cpp init.cpp inv.cpp \
list.cpp malloc.cpp mlfn.cpp msfn.cpp proj_mdist.cpp \
- open_lib.cpp param.cpp phi2.cpp pr_list.cpp \
+ param.cpp phi2.cpp pr_list.cpp \
qsfn.cpp strerrno.cpp \
tsfn.cpp units.cpp ctx.cpp log.cpp zpoly1.cpp rtodms.cpp \
release.cpp gauss.cpp \
@@ -218,8 +218,9 @@ libproj_la_SOURCES = \
grids.cpp \
filemanager.hpp \
filemanager.cpp \
- sqlite3.hpp \
- sqlite3.cpp
+ networkfilemanager.cpp \
+ sqlite3_utils.hpp \
+ sqlite3_utils.cpp
# The sed hack is to please MSVC
diff --git a/src/filemanager.cpp b/src/filemanager.cpp
index 9acea83e..592bada4 100644
--- a/src/filemanager.cpp
+++ b/src/filemanager.cpp
@@ -30,42 +30,17 @@
#endif
#define LRU11_DO_NOT_DEFINE_OUT_OF_CLASS_METHODS
+#include <errno.h>
#include <stdlib.h>
#include <algorithm>
-#include <codecvt>
-#include <functional>
#include <limits>
-#include <locale>
#include <string>
#include "filemanager.hpp"
#include "proj.h"
#include "proj/internal/internal.hpp"
-#include "proj/internal/lru_cache.hpp"
#include "proj_internal.h"
-#include "sqlite3.hpp"
-
-#ifdef __MINGW32__
-// mingw32-win32 doesn't implement std::mutex
-namespace {
-class MyMutex {
- public:
- // cppcheck-suppress functionStatic
- void lock() { pj_acquire_lock(); }
- // cppcheck-suppress functionStatic
- void unlock() { pj_release_lock(); }
-};
-}
-#else
-#include <mutex>
-#define MyMutex std::mutex
-#endif
-
-#ifdef CURL_ENABLED
-#include <curl/curl.h>
-#include <sqlite3.h> // for sqlite3_snprintf
-#endif
#include <sys/stat.h>
@@ -76,17 +51,6 @@ class MyMutex {
#include <unistd.h>
#endif
-#if defined(_WIN32)
-#include <windows.h>
-#elif defined(__MACH__) && defined(__APPLE__)
-#include <mach-o/dyld.h>
-#elif defined(__FreeBSD__)
-#include <sys/sysctl.h>
-#include <sys/types.h>
-#endif
-
-#include <time.h>
-
//! @cond Doxygen_Suppress
#define STR_HELPER(x) #x
@@ -98,1531 +62,823 @@ NS_PROJ_START
// ---------------------------------------------------------------------------
-static void proj_sleep_ms(int ms) {
-#ifdef _WIN32
- Sleep(ms);
-#else
- usleep(ms * 1000);
-#endif
-}
-
-// ---------------------------------------------------------------------------
-
File::File(const std::string &name) : name_(name) {}
// ---------------------------------------------------------------------------
File::~File() = default;
-// ---------------------------------------------------------------------------
-
-class FileStdio : public File {
- PJ_CONTEXT *m_ctx;
- FILE *m_fp;
+#ifdef _WIN32
- FileStdio(const FileStdio &) = delete;
- FileStdio &operator=(const FileStdio &) = delete;
+/* The bulk of utf8towc()/utf8fromwc() is derived from the utf.c module from
+ * FLTK. It was originally downloaded from:
+ * http://svn.easysw.com/public/fltk/fltk/trunk/src/utf.c
+ * And already used by GDAL
+ */
+/************************************************************************/
+/* ==================================================================== */
+/* UTF.C code from FLTK with some modifications. */
+/* ==================================================================== */
+/************************************************************************/
+
+/* Set to 1 to turn bad UTF8 bytes into ISO-8859-1. If this is to zero
+ they are instead turned into the Unicode REPLACEMENT CHARACTER, of
+ value 0xfffd.
+ If this is on utf8decode will correctly map most (perhaps all)
+ human-readable text that is in ISO-8859-1. This may allow you
+ to completely ignore character sets in your code because virtually
+ everything is either ISO-8859-1 or UTF-8.
+*/
+#define ERRORS_TO_ISO8859_1 1
- protected:
- FileStdio(const std::string &name, PJ_CONTEXT *ctx, FILE *fp)
- : File(name), m_ctx(ctx), m_fp(fp) {}
+/* Set to 1 to turn bad UTF8 bytes in the 0x80-0x9f range into the
+ Unicode index for Microsoft's CP1252 character set. You should
+ also set ERRORS_TO_ISO8859_1. With this a huge amount of more
+ available text (such as all web pages) are correctly converted
+ to Unicode.
+*/
+#define ERRORS_TO_CP1252 1
- public:
- ~FileStdio() override;
+/* A number of Unicode code points are in fact illegal and should not
+ be produced by a UTF-8 converter. Turn this on will replace the
+ bytes in those encodings with errors. If you do this then converting
+ arbitrary 16-bit data to UTF-8 and then back is not an identity,
+ which will probably break a lot of software.
+*/
+#define STRICT_RFC3629 0
+
+#if ERRORS_TO_CP1252
+// Codes 0x80..0x9f from the Microsoft CP1252 character set, translated
+// to Unicode:
+constexpr unsigned short cp1252[32] = {
+ 0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021,
+ 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f,
+ 0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014,
+ 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178};
+#endif
- size_t read(void *buffer, size_t sizeBytes) override;
- bool seek(unsigned long long offset, int whence = SEEK_SET) override;
- unsigned long long tell() override;
- void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; }
+/************************************************************************/
+/* utf8decode() */
+/************************************************************************/
+
+/*
+ Decode a single UTF-8 encoded character starting at \e p. The
+ resulting Unicode value (in the range 0-0x10ffff) is returned,
+ and \e len is set the number of bytes in the UTF-8 encoding
+ (adding \e len to \e p will point at the next character).
+
+ If \a p points at an illegal UTF-8 encoding, including one that
+ would go past \e end, or where a code is uses more bytes than
+ necessary, then *reinterpret_cast<const unsigned char*>(p) is translated as
+though it is
+ in the Microsoft CP1252 character set and \e len is set to 1.
+ Treating errors this way allows this to decode almost any
+ ISO-8859-1 or CP1252 text that has been mistakenly placed where
+ UTF-8 is expected, and has proven very useful.
+
+ If you want errors to be converted to error characters (as the
+ standards recommend), adding a test to see if the length is
+ unexpectedly 1 will work:
+
+\code
+ if( *p & 0x80 )
+ { // What should be a multibyte encoding.
+ code = utf8decode(p, end, &len);
+ if( len<2 ) code = 0xFFFD; // Turn errors into REPLACEMENT CHARACTER.
+ }
+ else
+ { // Handle the 1-byte utf8 encoding:
+ code = *p;
+ len = 1;
+ }
+\endcode
- // We may lie, but the real use case is only for network files
- bool hasChanged() const override { return false; }
+ Direct testing for the 1-byte case (as shown above) will also
+ speed up the scanning of strings where the majority of characters
+ are ASCII.
+*/
+static unsigned utf8decode(const char *p, const char *end, int *len) {
+ unsigned char c = *reinterpret_cast<const unsigned char *>(p);
+ if (c < 0x80) {
+ *len = 1;
+ return c;
+#if ERRORS_TO_CP1252
+ } else if (c < 0xa0) {
+ *len = 1;
+ return cp1252[c - 0x80];
+#endif
+ } else if (c < 0xc2) {
+ goto FAIL;
+ }
+ if (p + 1 >= end || (p[1] & 0xc0) != 0x80)
+ goto FAIL;
+ if (c < 0xe0) {
+ *len = 2;
+ return ((p[0] & 0x1f) << 6) + ((p[1] & 0x3f));
+ } else if (c == 0xe0) {
+ if ((reinterpret_cast<const unsigned char *>(p))[1] < 0xa0)
+ goto FAIL;
+ goto UTF8_3;
+#if STRICT_RFC3629
+ } else if (c == 0xed) {
+ // RFC 3629 says surrogate chars are illegal.
+ if ((reinterpret_cast<const unsigned char *>(p))[1] >= 0xa0)
+ goto FAIL;
+ goto UTF8_3;
+ } else if (c == 0xef) {
+ // 0xfffe and 0xffff are also illegal characters.
+ if ((reinterpret_cast<const unsigned char *>(p))[1] == 0xbf &&
+ (reinterpret_cast<const unsigned char *>(p))[2] >= 0xbe)
+ goto FAIL;
+ goto UTF8_3;
+#endif
+ } else if (c < 0xf0) {
+ UTF8_3:
+ if (p + 2 >= end || (p[2] & 0xc0) != 0x80)
+ goto FAIL;
+ *len = 3;
+ return ((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) + ((p[2] & 0x3f));
+ } else if (c == 0xf0) {
+ if ((reinterpret_cast<const unsigned char *>(p))[1] < 0x90)
+ goto FAIL;
+ goto UTF8_4;
+ } else if (c < 0xf4) {
+ UTF8_4:
+ if (p + 3 >= end || (p[2] & 0xc0) != 0x80 || (p[3] & 0xc0) != 0x80)
+ goto FAIL;
+ *len = 4;
+#if STRICT_RFC3629
+ // RFC 3629 says all codes ending in fffe or ffff are illegal:
+ if ((p[1] & 0xf) == 0xf &&
+ (reinterpret_cast<const unsigned char *>(p))[2] == 0xbf &&
+ (reinterpret_cast<const unsigned char *>(p))[3] >= 0xbe)
+ goto FAIL;
+#endif
+ return ((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) +
+ ((p[2] & 0x3f) << 6) + ((p[3] & 0x3f));
+ } else if (c == 0xf4) {
+ if ((reinterpret_cast<const unsigned char *>(p))[1] > 0x8f)
+ goto FAIL; // After 0x10ffff.
+ goto UTF8_4;
+ } else {
+ FAIL:
+ *len = 1;
+#if ERRORS_TO_ISO8859_1
+ return c;
+#else
+ return 0xfffd; // Unicode REPLACEMENT CHARACTER
+#endif
+ }
+}
- static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename);
-};
+/************************************************************************/
+/* utf8towc() */
+/************************************************************************/
-// ---------------------------------------------------------------------------
+/* Convert a UTF-8 sequence into an array of wchar_t. These
+ are used by some system calls, especially on Windows.
-FileStdio::~FileStdio() { fclose(m_fp); }
+ \a src points at the UTF-8, and \a srclen is the number of bytes to
+ convert.
-// ---------------------------------------------------------------------------
+ \a dst points at an array to write, and \a dstlen is the number of
+ locations in this array. At most \a dstlen-1 words will be
+ written there, plus a 0 terminating word. Thus this function
+ will never overwrite the buffer and will always return a
+ zero-terminated string. If \a dstlen is zero then \a dst can be
+ null and no data is written, but the length is returned.
-size_t FileStdio::read(void *buffer, size_t sizeBytes) {
- return fread(buffer, 1, sizeBytes, m_fp);
-}
+ The return value is the number of words that \e would be written
+ to \a dst if it were long enough, not counting the terminating
+ zero. If the return value is greater or equal to \a dstlen it
+ indicates truncation, you can then allocate a new array of size
+ return+1 and call this again.
-// ---------------------------------------------------------------------------
+ Errors in the UTF-8 are converted as though each byte in the
+ erroneous string is in the Microsoft CP1252 encoding. This allows
+ ISO-8859-1 text mistakenly identified as UTF-8 to be printed
+ correctly.
-bool FileStdio::seek(unsigned long long offset, int whence) {
- // TODO one day: use 64-bit offset compatible API
- if (offset != static_cast<unsigned long long>(static_cast<long>(offset))) {
- pj_log(m_ctx, PJ_LOG_ERROR,
- "Attempt at seeking to a 64 bit offset. Not supported yet");
- return false;
+ Notice that sizeof(wchar_t) is 2 on Windows and is 4 on Linux
+ and most other systems. Where wchar_t is 16 bits, Unicode
+ characters in the range 0x10000 to 0x10ffff are converted to
+ "surrogate pairs" which take two words each (this is called UTF-16
+ encoding). If wchar_t is 32 bits this rather nasty problem is
+ avoided.
+*/
+static unsigned utf8towc(const char *src, unsigned srclen, wchar_t *dst,
+ unsigned dstlen) {
+ const char *p = src;
+ const char *e = src + srclen;
+ unsigned count = 0;
+ if (dstlen)
+ while (true) {
+ if (p >= e) {
+ dst[count] = 0;
+ return count;
+ }
+ if (!(*p & 0x80)) {
+ // ASCII
+ dst[count] = *p++;
+ } else {
+ int len = 0;
+ unsigned ucs = utf8decode(p, e, &len);
+ p += len;
+#ifdef _WIN32
+ if (ucs < 0x10000) {
+ dst[count] = static_cast<wchar_t>(ucs);
+ } else {
+ // Make a surrogate pair:
+ if (count + 2 >= dstlen) {
+ dst[count] = 0;
+ count += 2;
+ break;
+ }
+ dst[count] = static_cast<wchar_t>(
+ (((ucs - 0x10000u) >> 10) & 0x3ff) | 0xd800);
+ dst[++count] = static_cast<wchar_t>((ucs & 0x3ff) | 0xdc00);
+ }
+#else
+ dst[count] = static_cast<wchar_t>(ucs);
+#endif
+ }
+ if (++count == dstlen) {
+ dst[count - 1] = 0;
+ break;
+ }
+ }
+ // We filled dst, measure the rest:
+ while (p < e) {
+ if (!(*p & 0x80)) {
+ p++;
+ } else {
+ int len = 0;
+#ifdef _WIN32
+ const unsigned ucs = utf8decode(p, e, &len);
+ p += len;
+ if (ucs >= 0x10000)
+ ++count;
+#else
+ utf8decode(p, e, &len);
+ p += len;
+#endif
+ }
+ ++count;
}
- return fseek(m_fp, static_cast<long>(offset), whence) == 0;
-}
-// ---------------------------------------------------------------------------
-
-unsigned long long FileStdio::tell() {
- // TODO one day: use 64-bit offset compatible API
- return ftell(m_fp);
+ return count;
}
// ---------------------------------------------------------------------------
-std::unique_ptr<File> FileStdio::open(PJ_CONTEXT *ctx, const char *filename) {
- auto fp = fopen(filename, "rb");
- return std::unique_ptr<File>(fp ? new FileStdio(filename, ctx, fp)
- : nullptr);
+struct NonValidUTF8Exception : public std::exception {};
+
+// May throw exceptions
+static std::wstring UTF8ToWString(const std::string &str) {
+ std::wstring wstr;
+ wstr.resize(str.size());
+ wstr.resize(utf8towc(str.data(), static_cast<unsigned>(str.size()),
+ &wstr[0], static_cast<unsigned>(wstr.size()) + 1));
+ for (const auto ch : wstr) {
+ if (ch == 0xfffd) {
+ throw NonValidUTF8Exception();
+ }
+ }
+ return wstr;
}
// ---------------------------------------------------------------------------
-#ifndef REMOVE_LEGACY_SUPPORT
-
-class FileLegacyAdapter : public File {
- PJ_CONTEXT *m_ctx;
- PAFile m_fp;
-
- FileLegacyAdapter(const FileLegacyAdapter &) = delete;
- FileLegacyAdapter &operator=(const FileLegacyAdapter &) = delete;
-
- protected:
- FileLegacyAdapter(const std::string &name, PJ_CONTEXT *ctx, PAFile fp)
- : File(name), m_ctx(ctx), m_fp(fp) {}
-
- public:
- ~FileLegacyAdapter() override;
-
- size_t read(void *buffer, size_t sizeBytes) override;
- bool seek(unsigned long long offset, int whence = SEEK_SET) override;
- unsigned long long tell() override;
- void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; }
-
- // We may lie, but the real use case is only for network files
- bool hasChanged() const override { return false; }
+/************************************************************************/
+/* utf8fromwc() */
+/************************************************************************/
+/* Turn "wide characters" as returned by some system calls
+ (especially on Windows) into UTF-8.
- static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename);
-};
+ Up to \a dstlen bytes are written to \a dst, including a null
+ terminator. The return value is the number of bytes that would be
+ written, not counting the null terminator. If greater or equal to
+ \a dstlen then if you malloc a new array of size n+1 you will have
+ the space needed for the entire string. If \a dstlen is zero then
+ nothing is written and this call just measures the storage space
+ needed.
-// ---------------------------------------------------------------------------
+ \a srclen is the number of words in \a src to convert. On Windows
+ this is not necessarily the number of characters, due to there
+ possibly being "surrogate pairs" in the UTF-16 encoding used.
+ On Unix wchar_t is 32 bits and each location is a character.
-FileLegacyAdapter::~FileLegacyAdapter() { pj_ctx_fclose(m_ctx, m_fp); }
+ On Unix if a src word is greater than 0x10ffff then this is an
+ illegal character according to RFC 3629. These are converted as
+ though they are 0xFFFD (REPLACEMENT CHARACTER). Characters in the
+ range 0xd800 to 0xdfff, or ending with 0xfffe or 0xffff are also
+ illegal according to RFC 3629. However I encode these as though
+ they are legal, so that utf8towc will return the original data.
-// ---------------------------------------------------------------------------
-
-size_t FileLegacyAdapter::read(void *buffer, size_t sizeBytes) {
- return pj_ctx_fread(m_ctx, buffer, 1, sizeBytes, m_fp);
-}
-
-// ---------------------------------------------------------------------------
+ On Windows "surrogate pairs" are converted to a single character
+ and UTF-8 encoded (as 4 bytes). Mismatched halves of surrogate
+ pairs are converted as though they are individual characters.
+*/
+static unsigned int utf8fromwc(char *dst, unsigned dstlen, const wchar_t *src,
+ unsigned srclen) {
+ unsigned int i = 0;
+ unsigned int count = 0;
+ if (dstlen)
+ while (true) {
+ if (i >= srclen) {
+ dst[count] = 0;
+ return count;
+ }
+ unsigned int ucs = src[i++];
+ if (ucs < 0x80U) {
+ dst[count++] = static_cast<char>(ucs);
+ if (count >= dstlen) {
+ dst[count - 1] = 0;
+ break;
+ }
+ } else if (ucs < 0x800U) {
+ // 2 bytes.
+ if (count + 2 >= dstlen) {
+ dst[count] = 0;
+ count += 2;
+ break;
+ }
+ dst[count++] = 0xc0 | static_cast<char>(ucs >> 6);
+ dst[count++] = 0x80 | static_cast<char>(ucs & 0x3F);
+#ifdef _WIN32
+ } else if (ucs >= 0xd800 && ucs <= 0xdbff && i < srclen &&
+ src[i] >= 0xdc00 && src[i] <= 0xdfff) {
+ // Surrogate pair.
+ unsigned int ucs2 = src[i++];
+ ucs = 0x10000U + ((ucs & 0x3ff) << 10) + (ucs2 & 0x3ff);
+// All surrogate pairs turn into 4-byte utf8.
+#else
+ } else if (ucs >= 0x10000) {
+ if (ucs > 0x10ffff) {
+ ucs = 0xfffd;
+ goto J1;
+ }
+#endif
+ if (count + 4 >= dstlen) {
+ dst[count] = 0;
+ count += 4;
+ break;
+ }
+ dst[count++] = 0xf0 | static_cast<char>(ucs >> 18);
+ dst[count++] = 0x80 | static_cast<char>((ucs >> 12) & 0x3F);
+ dst[count++] = 0x80 | static_cast<char>((ucs >> 6) & 0x3F);
+ dst[count++] = 0x80 | static_cast<char>(ucs & 0x3F);
+ } else {
+#ifndef _WIN32
+ J1:
+#endif
+ // All others are 3 bytes:
+ if (count + 3 >= dstlen) {
+ dst[count] = 0;
+ count += 3;
+ break;
+ }
+ dst[count++] = 0xe0 | static_cast<char>(ucs >> 12);
+ dst[count++] = 0x80 | static_cast<char>((ucs >> 6) & 0x3F);
+ dst[count++] = 0x80 | static_cast<char>(ucs & 0x3F);
+ }
+ }
-bool FileLegacyAdapter::seek(unsigned long long offset, int whence) {
- if (offset != static_cast<unsigned long long>(static_cast<long>(offset))) {
- pj_log(m_ctx, PJ_LOG_ERROR,
- "Attempt at seeking to a 64 bit offset. Not supported yet");
- return false;
+ // We filled dst, measure the rest:
+ while (i < srclen) {
+ unsigned int ucs = src[i++];
+ if (ucs < 0x80U) {
+ count++;
+ } else if (ucs < 0x800U) {
+ // 2 bytes.
+ count += 2;
+#ifdef _WIN32
+ } else if (ucs >= 0xd800 && ucs <= 0xdbff && i < srclen - 1 &&
+ src[i + 1] >= 0xdc00 && src[i + 1] <= 0xdfff) {
+ // Surrogate pair.
+ ++i;
+#else
+ } else if (ucs >= 0x10000 && ucs <= 0x10ffff) {
+#endif
+ count += 4;
+ } else {
+ count += 3;
+ }
}
- return pj_ctx_fseek(m_ctx, m_fp, static_cast<long>(offset), whence) == 0;
+ return count;
}
// ---------------------------------------------------------------------------
-unsigned long long FileLegacyAdapter::tell() {
- return pj_ctx_ftell(m_ctx, m_fp);
-}
-
-// ---------------------------------------------------------------------------
-
-std::unique_ptr<File> FileLegacyAdapter::open(PJ_CONTEXT *ctx,
- const char *filename) {
- auto fid = pj_ctx_fopen(ctx, filename, "rb");
- return std::unique_ptr<File>(fid ? new FileLegacyAdapter(filename, ctx, fid)
- : nullptr);
+static std::string WStringToUTF8(const std::wstring &wstr) {
+ std::string str;
+ str.resize(wstr.size());
+ str.resize(utf8fromwc(&str[0], static_cast<unsigned>(str.size() + 1),
+ wstr.data(), static_cast<unsigned>(wstr.size())));
+ return str;
}
-#endif // REMOVE_LEGACY_SUPPORT
-
// ---------------------------------------------------------------------------
-constexpr size_t DOWNLOAD_CHUNK_SIZE = 16 * 1024;
-constexpr int MAX_CHUNKS = 64;
-
-struct FileProperties {
- unsigned long long size = 0;
- time_t lastChecked = 0;
- std::string lastModified{};
- std::string etag{};
-};
-
-class NetworkChunkCache {
- public:
- void insert(PJ_CONTEXT *ctx, const std::string &url,
- unsigned long long chunkIdx, std::vector<unsigned char> &&data);
-
- std::shared_ptr<std::vector<unsigned char>>
- get(PJ_CONTEXT *ctx, const std::string &url, unsigned long long chunkIdx);
+static std::string Win32Recode(const char *src, unsigned src_code_page,
+ unsigned dst_code_page) {
+ // Convert from source code page to Unicode.
- std::shared_ptr<std::vector<unsigned char>> get(PJ_CONTEXT *ctx,
- const std::string &url,
- unsigned long long chunkIdx,
- FileProperties &props);
+ // Compute the length in wide characters.
+ int wlen = MultiByteToWideChar(src_code_page, MB_ERR_INVALID_CHARS, src, -1,
+ nullptr, 0);
+ if (wlen == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION) {
+ return std::string();
+ }
- void clearMemoryCache();
+ // Do the actual conversion.
+ std::wstring wbuf;
+ wbuf.resize(wlen);
+ MultiByteToWideChar(src_code_page, 0, src, -1, &wbuf[0], wlen);
- static void clearDiskChunkCache(PJ_CONTEXT *ctx);
+ // Convert from Unicode to destination code page.
- private:
- struct Key {
- std::string url;
- unsigned long long chunkIdx;
+ // Compute the length in chars.
+ int len = WideCharToMultiByte(dst_code_page, 0, &wbuf[0], -1, nullptr, 0,
+ nullptr, nullptr);
- Key(const std::string &urlIn, unsigned long long chunkIdxIn)
- : url(urlIn), chunkIdx(chunkIdxIn) {}
- bool operator==(const Key &other) const {
- return url == other.url && chunkIdx == other.chunkIdx;
- }
- };
+ // Do the actual conversion.
+ std::string out;
+ out.resize(len);
+ WideCharToMultiByte(dst_code_page, 0, &wbuf[0], -1, &out[0], len, nullptr,
+ nullptr);
+ out.resize(strlen(out.c_str()));
- struct KeyHasher {
- std::size_t operator()(const Key &k) const {
- return std::hash<std::string>{}(k.url) ^
- (std::hash<unsigned long long>{}(k.chunkIdx) << 1);
- }
- };
-
- lru11::Cache<
- Key, std::shared_ptr<std::vector<unsigned char>>, MyMutex,
- std::unordered_map<
- Key,
- typename std::list<lru11::KeyValuePair<
- Key, std::shared_ptr<std::vector<unsigned char>>>>::iterator,
- KeyHasher>>
- cache_{MAX_CHUNKS};
-};
+ return out;
+}
// ---------------------------------------------------------------------------
-static NetworkChunkCache gNetworkChunkCache{};
+class FileWin32 : public File {
+ PJ_CONTEXT *m_ctx;
+ HANDLE m_handle;
-// ---------------------------------------------------------------------------
+ FileWin32(const FileWin32 &) = delete;
+ FileWin32 &operator=(const FileWin32 &) = delete;
+
+ protected:
+ FileWin32(const std::string &name, PJ_CONTEXT *ctx, HANDLE handle)
+ : File(name), m_ctx(ctx), m_handle(handle) {}
-class NetworkFilePropertiesCache {
public:
- void insert(PJ_CONTEXT *ctx, const std::string &url, FileProperties &props);
+ ~FileWin32() override;
- bool tryGet(PJ_CONTEXT *ctx, const std::string &url, FileProperties &props);
+ size_t read(void *buffer, size_t sizeBytes) override;
+ size_t write(const void *buffer, size_t sizeBytes) override;
+ bool seek(unsigned long long offset, int whence = SEEK_SET) override;
+ unsigned long long tell() override;
+ void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; }
- void clearMemoryCache();
+ // We may lie, but the real use case is only for network files
+ bool hasChanged() const override { return false; }
- private:
- lru11::Cache<std::string, FileProperties, MyMutex> cache_{};
+ static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename,
+ FileAccess access);
};
// ---------------------------------------------------------------------------
-static NetworkFilePropertiesCache gNetworkFileProperties{};
+FileWin32::~FileWin32() { CloseHandle(m_handle); }
// ---------------------------------------------------------------------------
-class DiskChunkCache {
- PJ_CONTEXT *ctx_ = nullptr;
- std::string path_{};
- sqlite3 *hDB_ = nullptr;
- std::string thisNamePtr_{};
- std::unique_ptr<SQLite3VFS> vfs_{};
-
- explicit DiskChunkCache(PJ_CONTEXT *ctx, const std::string &path);
-
- bool createDBStructure();
- bool checkConsistency();
- bool get_links(sqlite3_int64 chunk_id, sqlite3_int64 &link_id,
- sqlite3_int64 &prev, sqlite3_int64 &next,
- sqlite3_int64 &head, sqlite3_int64 &tail);
- bool update_links_of_prev_and_next_links(sqlite3_int64 prev,
- sqlite3_int64 next);
- bool update_linked_chunks(sqlite3_int64 link_id, sqlite3_int64 prev,
- sqlite3_int64 next);
- bool update_linked_chunks_head_tail(sqlite3_int64 head, sqlite3_int64 tail);
-
- DiskChunkCache(const DiskChunkCache &) = delete;
- DiskChunkCache &operator=(const DiskChunkCache &) = delete;
-
- public:
- static std::unique_ptr<DiskChunkCache> open(PJ_CONTEXT *ctx);
- ~DiskChunkCache();
-
- sqlite3 *handle() { return hDB_; }
- std::unique_ptr<SQLiteStatement> prepare(const char *sql);
- bool move_to_head(sqlite3_int64 chunk_id);
- bool move_to_tail(sqlite3_int64 chunk_id);
- void closeAndUnlink();
-};
+size_t FileWin32::read(void *buffer, size_t sizeBytes) {
+ DWORD dwSizeRead = 0;
+ size_t nResult = 0;
-// ---------------------------------------------------------------------------
+ if (!ReadFile(m_handle, buffer, static_cast<DWORD>(sizeBytes), &dwSizeRead,
+ nullptr))
+ nResult = 0;
+ else
+ nResult = dwSizeRead;
-static bool pj_context_get_grid_cache_is_enabled(PJ_CONTEXT *ctx) {
- pj_load_ini(ctx);
- return ctx->gridChunkCache.enabled;
+ return nResult;
}
// ---------------------------------------------------------------------------
-static long long pj_context_get_grid_cache_max_size(PJ_CONTEXT *ctx) {
- pj_load_ini(ctx);
- return ctx->gridChunkCache.max_size;
-}
+size_t FileWin32::write(const void *buffer, size_t sizeBytes) {
+ DWORD dwSizeWritten = 0;
+ size_t nResult = 0;
-// ---------------------------------------------------------------------------
+ if (!WriteFile(m_handle, buffer, static_cast<DWORD>(sizeBytes),
+ &dwSizeWritten, nullptr))
+ nResult = 0;
+ else
+ nResult = dwSizeWritten;
-static int pj_context_get_grid_cache_ttl(PJ_CONTEXT *ctx) {
- pj_load_ini(ctx);
- return ctx->gridChunkCache.ttl;
+ return nResult;
}
// ---------------------------------------------------------------------------
-std::unique_ptr<DiskChunkCache> DiskChunkCache::open(PJ_CONTEXT *ctx) {
- if (!pj_context_get_grid_cache_is_enabled(ctx)) {
- return nullptr;
- }
- const auto cachePath = pj_context_get_grid_cache_filename(ctx);
- if (cachePath.empty()) {
- return nullptr;
+bool FileWin32::seek(unsigned long long offset, int whence) {
+ LONG dwMoveMethod, dwMoveHigh;
+ uint32_t nMoveLow;
+ LARGE_INTEGER li;
+
+ switch (whence) {
+ case SEEK_CUR:
+ dwMoveMethod = FILE_CURRENT;
+ break;
+ case SEEK_END:
+ dwMoveMethod = FILE_END;
+ break;
+ case SEEK_SET:
+ default:
+ dwMoveMethod = FILE_BEGIN;
+ break;
}
- auto diskCache =
- std::unique_ptr<DiskChunkCache>(new DiskChunkCache(ctx, cachePath));
- if (!diskCache->hDB_)
- diskCache.reset();
- return diskCache;
+ li.QuadPart = offset;
+ nMoveLow = li.LowPart;
+ dwMoveHigh = li.HighPart;
+
+ SetLastError(0);
+ SetFilePointer(m_handle, nMoveLow, &dwMoveHigh, dwMoveMethod);
+
+ return GetLastError() == NO_ERROR;
}
// ---------------------------------------------------------------------------
-DiskChunkCache::DiskChunkCache(PJ_CONTEXT *ctx, const std::string &path)
- : ctx_(ctx), path_(path), vfs_(SQLite3VFS::create(true, false, false)) {
- if (vfs_ == nullptr) {
- return;
- }
- sqlite3_open_v2(path.c_str(), &hDB_,
- SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, vfs_->name());
- if (!hDB_) {
- return;
- }
- for (int i = 0;; i++) {
- int ret =
- sqlite3_exec(hDB_, "BEGIN EXCLUSIVE", nullptr, nullptr, nullptr);
- if (ret == SQLITE_OK) {
- break;
- }
- if (ret != SQLITE_BUSY) {
- pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
- sqlite3_close(hDB_);
- hDB_ = nullptr;
- return;
- }
- const char *max_iters = getenv("PROJ_LOCK_MAX_ITERS");
- if (i >= (max_iters && max_iters[0] ? atoi(max_iters)
- : 30)) { // A bit more than 1 second
- pj_log(ctx_, PJ_LOG_ERROR, "Cannot take exclusive lock on %s",
- path.c_str());
- sqlite3_close(hDB_);
- hDB_ = nullptr;
- return;
- }
- pj_log(ctx, PJ_LOG_TRACE, "Lock taken on cache. Waiting a bit...");
- // Retry every 5 ms for 50 ms, then every 10 ms for 100 ms, then
- // every 100 ms
- proj_sleep_ms(i < 10 ? 5 : i < 20 ? 10 : 100);
- }
- char **pasResult = nullptr;
- int nRows = 0;
- int nCols = 0;
- sqlite3_get_table(hDB_,
- "SELECT 1 FROM sqlite_master WHERE name = 'properties'",
- &pasResult, &nRows, &nCols, nullptr);
- sqlite3_free_table(pasResult);
- if (nRows == 0) {
- if (!createDBStructure()) {
- sqlite3_close(hDB_);
- hDB_ = nullptr;
- return;
- }
- }
+unsigned long long FileWin32::tell() {
+ LARGE_INTEGER li;
- if (getenv("PROJ_CHECK_CACHE_CONSISTENCY")) {
- checkConsistency();
- }
-}
+ li.HighPart = 0;
+ li.LowPart = SetFilePointer(m_handle, 0, &(li.HighPart), FILE_CURRENT);
+ return static_cast<unsigned long long>(li.QuadPart);
+}
// ---------------------------------------------------------------------------
-static const char *cache_db_structure_sql =
- "CREATE TABLE properties("
- " url TEXT PRIMARY KEY NOT NULL,"
- " lastChecked TIMESTAMP NOT NULL,"
- " fileSize INTEGER NOT NULL,"
- " lastModified TEXT,"
- " etag TEXT"
- ");"
- "CREATE TABLE downloaded_file_properties("
- " url TEXT PRIMARY KEY NOT NULL,"
- " lastChecked TIMESTAMP NOT NULL,"
- " fileSize INTEGER NOT NULL,"
- " lastModified TEXT,"
- " etag TEXT"
- ");"
- "CREATE TABLE chunk_data("
- " id INTEGER PRIMARY KEY AUTOINCREMENT CHECK (id > 0),"
- " data BLOB NOT NULL"
- ");"
- "CREATE TABLE chunks("
- " id INTEGER PRIMARY KEY AUTOINCREMENT CHECK (id > 0),"
- " url TEXT NOT NULL,"
- " offset INTEGER NOT NULL,"
- " data_id INTEGER NOT NULL,"
- " data_size INTEGER NOT NULL,"
- " CONSTRAINT fk_chunks_url FOREIGN KEY (url) REFERENCES properties(url),"
- " CONSTRAINT fk_chunks_data FOREIGN KEY (data_id) REFERENCES chunk_data(id)"
- ");"
- "CREATE INDEX idx_chunks ON chunks(url, offset);"
- "CREATE TABLE linked_chunks("
- " id INTEGER PRIMARY KEY AUTOINCREMENT CHECK (id > 0),"
- " chunk_id INTEGER NOT NULL,"
- " prev INTEGER,"
- " next INTEGER,"
- " CONSTRAINT fk_links_chunkid FOREIGN KEY (chunk_id) REFERENCES chunks(id),"
- " CONSTRAINT fk_links_prev FOREIGN KEY (prev) REFERENCES linked_chunks(id),"
- " CONSTRAINT fk_links_next FOREIGN KEY (next) REFERENCES linked_chunks(id)"
- ");"
- "CREATE INDEX idx_linked_chunks_chunk_id ON linked_chunks(chunk_id);"
- "CREATE TABLE linked_chunks_head_tail("
- " head INTEGER,"
- " tail INTEGER,"
- " CONSTRAINT lht_head FOREIGN KEY (head) REFERENCES linked_chunks(id),"
- " CONSTRAINT lht_tail FOREIGN KEY (tail) REFERENCES linked_chunks(id)"
- ");"
- "INSERT INTO linked_chunks_head_tail VALUES (NULL, NULL);";
-
-bool DiskChunkCache::createDBStructure() {
-
- pj_log(ctx_, PJ_LOG_TRACE, "Creating cache DB structure");
- if (sqlite3_exec(hDB_, cache_db_structure_sql, nullptr, nullptr, nullptr) !=
- SQLITE_OK) {
- pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
- return false;
+std::unique_ptr<File> FileWin32::open(PJ_CONTEXT *ctx, const char *filename,
+ FileAccess access) {
+ DWORD dwDesiredAccess = access == FileAccess::READ_ONLY
+ ? GENERIC_READ
+ : GENERIC_READ | GENERIC_WRITE;
+ DWORD dwCreationDisposition =
+ access == FileAccess::CREATE ? CREATE_ALWAYS : OPEN_EXISTING;
+ DWORD dwFlagsAndAttributes = (dwDesiredAccess == GENERIC_READ)
+ ? FILE_ATTRIBUTE_READONLY
+ : FILE_ATTRIBUTE_NORMAL;
+ try {
+ HANDLE hFile = CreateFileW(
+ UTF8ToWString(std::string(filename)).c_str(), dwDesiredAccess,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
+ dwCreationDisposition, dwFlagsAndAttributes, nullptr);
+ return std::unique_ptr<File>(hFile != INVALID_HANDLE_VALUE
+ ? new FileWin32(filename, ctx, hFile)
+ : nullptr);
+ } catch (const std::exception &e) {
+ pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
+ return nullptr;
}
- return true;
}
+#else
// ---------------------------------------------------------------------------
-#define INVALIDATED_SQL_LITERAL "'invalidated'"
-
-bool DiskChunkCache::checkConsistency() {
-
- auto stmt = prepare("SELECT * FROM chunk_data WHERE id NOT IN (SELECT "
- "data_id FROM chunks)");
- if (!stmt) {
- return false;
- }
- if (stmt->execute() != SQLITE_DONE) {
- fprintf(stderr, "Rows in chunk_data not referenced by chunks.\n");
- return false;
- }
+class FileStdio : public File {
+ PJ_CONTEXT *m_ctx;
+ FILE *m_fp;
- stmt = prepare("SELECT * FROM chunks WHERE id NOT IN (SELECT chunk_id FROM "
- "linked_chunks)");
- if (!stmt) {
- return false;
- }
- if (stmt->execute() != SQLITE_DONE) {
- fprintf(stderr, "Rows in chunks not referenced by linked_chunks.\n");
- return false;
- }
+ FileStdio(const FileStdio &) = delete;
+ FileStdio &operator=(const FileStdio &) = delete;
- stmt = prepare("SELECT * FROM chunks WHERE url <> " INVALIDATED_SQL_LITERAL
- " AND url "
- "NOT IN (SELECT url FROM properties)");
- if (!stmt) {
- return false;
- }
- if (stmt->execute() != SQLITE_DONE) {
- fprintf(stderr, "url values in chunks not referenced by properties.\n");
- return false;
- }
+ protected:
+ FileStdio(const std::string &name, PJ_CONTEXT *ctx, FILE *fp)
+ : File(name), m_ctx(ctx), m_fp(fp) {}
- stmt = prepare("SELECT head, tail FROM linked_chunks_head_tail");
- if (!stmt) {
- return false;
- }
- if (stmt->execute() != SQLITE_ROW) {
- fprintf(stderr, "linked_chunks_head_tail empty.\n");
- return false;
- }
- const auto head = stmt->getInt64();
- const auto tail = stmt->getInt64();
- if (stmt->execute() != SQLITE_DONE) {
- fprintf(stderr, "linked_chunks_head_tail has more than one row.\n");
- return false;
- }
+ public:
+ ~FileStdio() override;
- stmt = prepare("SELECT COUNT(*) FROM linked_chunks");
- if (!stmt) {
- return false;
- }
- if (stmt->execute() != SQLITE_ROW) {
- fprintf(stderr, "linked_chunks_head_tail empty.\n");
- return false;
- }
- const auto count_linked_chunks = stmt->getInt64();
-
- if (head) {
- auto id = head;
- std::set<sqlite3_int64> visitedIds;
- stmt = prepare("SELECT next FROM linked_chunks WHERE id = ?");
- if (!stmt) {
- return false;
- }
- while (true) {
- visitedIds.insert(id);
- stmt->reset();
- stmt->bindInt64(id);
- if (stmt->execute() != SQLITE_ROW) {
- fprintf(stderr, "cannot find linked_chunks.id = %d.\n",
- static_cast<int>(id));
- return false;
- }
- auto next = stmt->getInt64();
- if (next == 0) {
- if (id != tail) {
- fprintf(stderr,
- "last item when following next is not tail.\n");
- return false;
- }
- break;
- }
- if (visitedIds.find(next) != visitedIds.end()) {
- fprintf(stderr, "found cycle on linked_chunks.next = %d.\n",
- static_cast<int>(next));
- return false;
- }
- id = next;
- }
- if (visitedIds.size() != static_cast<size_t>(count_linked_chunks)) {
- fprintf(stderr,
- "ghost items in linked_chunks when following next.\n");
- return false;
- }
- } else if (count_linked_chunks) {
- fprintf(stderr, "linked_chunks_head_tail.head = NULL but linked_chunks "
- "not empty.\n");
- return false;
- }
+ size_t read(void *buffer, size_t sizeBytes) override;
+ size_t write(const void *buffer, size_t sizeBytes) override;
+ bool seek(unsigned long long offset, int whence = SEEK_SET) override;
+ unsigned long long tell() override;
+ void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; }
- if (tail) {
- auto id = tail;
- std::set<sqlite3_int64> visitedIds;
- stmt = prepare("SELECT prev FROM linked_chunks WHERE id = ?");
- if (!stmt) {
- return false;
- }
- while (true) {
- visitedIds.insert(id);
- stmt->reset();
- stmt->bindInt64(id);
- if (stmt->execute() != SQLITE_ROW) {
- fprintf(stderr, "cannot find linked_chunks.id = %d.\n",
- static_cast<int>(id));
- return false;
- }
- auto prev = stmt->getInt64();
- if (prev == 0) {
- if (id != head) {
- fprintf(stderr,
- "last item when following prev is not head.\n");
- return false;
- }
- break;
- }
- if (visitedIds.find(prev) != visitedIds.end()) {
- fprintf(stderr, "found cycle on linked_chunks.prev = %d.\n",
- static_cast<int>(prev));
- return false;
- }
- id = prev;
- }
- if (visitedIds.size() != static_cast<size_t>(count_linked_chunks)) {
- fprintf(stderr,
- "ghost items in linked_chunks when following prev.\n");
- return false;
- }
- } else if (count_linked_chunks) {
- fprintf(stderr, "linked_chunks_head_tail.tail = NULL but linked_chunks "
- "not empty.\n");
- return false;
- }
+ // We may lie, but the real use case is only for network files
+ bool hasChanged() const override { return false; }
- fprintf(stderr, "check ok\n");
- return true;
-}
+ static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename,
+ FileAccess access);
+};
// ---------------------------------------------------------------------------
-DiskChunkCache::~DiskChunkCache() {
- if (hDB_) {
- sqlite3_exec(hDB_, "COMMIT", nullptr, nullptr, nullptr);
- sqlite3_close(hDB_);
- }
-}
+FileStdio::~FileStdio() { fclose(m_fp); }
// ---------------------------------------------------------------------------
-void DiskChunkCache::closeAndUnlink() {
- if (hDB_) {
- sqlite3_exec(hDB_, "COMMIT", nullptr, nullptr, nullptr);
- sqlite3_close(hDB_);
- hDB_ = nullptr;
- }
- if (vfs_) {
- vfs_->raw()->xDelete(vfs_->raw(), path_.c_str(), 0);
- }
+size_t FileStdio::read(void *buffer, size_t sizeBytes) {
+ return fread(buffer, 1, sizeBytes, m_fp);
}
// ---------------------------------------------------------------------------
-std::unique_ptr<SQLiteStatement> DiskChunkCache::prepare(const char *sql) {
- sqlite3_stmt *hStmt = nullptr;
- sqlite3_prepare_v2(hDB_, sql, -1, &hStmt, nullptr);
- if (!hStmt) {
- pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
- return nullptr;
- }
- return std::unique_ptr<SQLiteStatement>(new SQLiteStatement(hStmt));
+size_t FileStdio::write(const void *buffer, size_t sizeBytes) {
+ return fwrite(buffer, 1, sizeBytes, m_fp);
}
// ---------------------------------------------------------------------------
-bool DiskChunkCache::get_links(sqlite3_int64 chunk_id, sqlite3_int64 &link_id,
- sqlite3_int64 &prev, sqlite3_int64 &next,
- sqlite3_int64 &head, sqlite3_int64 &tail) {
- auto stmt =
- prepare("SELECT id, prev, next FROM linked_chunks WHERE chunk_id = ?");
- if (!stmt)
+bool FileStdio::seek(unsigned long long offset, int whence) {
+ // TODO one day: use 64-bit offset compatible API
+ if (offset != static_cast<unsigned long long>(static_cast<long>(offset))) {
+ pj_log(m_ctx, PJ_LOG_ERROR,
+ "Attempt at seeking to a 64 bit offset. Not supported yet");
return false;
- stmt->bindInt64(chunk_id);
- {
- const auto ret = stmt->execute();
- if (ret != SQLITE_ROW) {
- pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
- return false;
- }
}
- link_id = stmt->getInt64();
- prev = stmt->getInt64();
- next = stmt->getInt64();
-
- stmt = prepare("SELECT head, tail FROM linked_chunks_head_tail");
- {
- const auto ret = stmt->execute();
- if (ret != SQLITE_ROW) {
- pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
- return false;
- }
- }
- head = stmt->getInt64();
- tail = stmt->getInt64();
- return true;
+ return fseek(m_fp, static_cast<long>(offset), whence) == 0;
}
// ---------------------------------------------------------------------------
-bool DiskChunkCache::update_links_of_prev_and_next_links(sqlite3_int64 prev,
- sqlite3_int64 next) {
- if (prev) {
- auto stmt = prepare("UPDATE linked_chunks SET next = ? WHERE id = ?");
- if (!stmt)
- return false;
- if (next)
- stmt->bindInt64(next);
- else
- stmt->bindNull();
- stmt->bindInt64(prev);
- const auto ret = stmt->execute();
- if (ret != SQLITE_DONE) {
- pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
- return false;
- }
- }
-
- if (next) {
- auto stmt = prepare("UPDATE linked_chunks SET prev = ? WHERE id = ?");
- if (!stmt)
- return false;
- if (prev)
- stmt->bindInt64(prev);
- else
- stmt->bindNull();
- stmt->bindInt64(next);
- const auto ret = stmt->execute();
- if (ret != SQLITE_DONE) {
- pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
- return false;
- }
- }
- return true;
+unsigned long long FileStdio::tell() {
+ // TODO one day: use 64-bit offset compatible API
+ return ftell(m_fp);
}
// ---------------------------------------------------------------------------
-bool DiskChunkCache::update_linked_chunks(sqlite3_int64 link_id,
- sqlite3_int64 prev,
- sqlite3_int64 next) {
- auto stmt =
- prepare("UPDATE linked_chunks SET prev = ?, next = ? WHERE id = ?");
- if (!stmt)
- return false;
- if (prev)
- stmt->bindInt64(prev);
- else
- stmt->bindNull();
- if (next)
- stmt->bindInt64(next);
- else
- stmt->bindNull();
- stmt->bindInt64(link_id);
- const auto ret = stmt->execute();
- if (ret != SQLITE_DONE) {
- pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
- return false;
- }
- return true;
+std::unique_ptr<File> FileStdio::open(PJ_CONTEXT *ctx, const char *filename,
+ FileAccess access) {
+ auto fp = fopen(filename,
+ access == FileAccess::READ_ONLY
+ ? "rb"
+ : access == FileAccess::READ_UPDATE ? "r+b" : "w+b");
+ return std::unique_ptr<File>(fp ? new FileStdio(filename, ctx, fp)
+ : nullptr);
}
-// ---------------------------------------------------------------------------
-
-bool DiskChunkCache::update_linked_chunks_head_tail(sqlite3_int64 head,
- sqlite3_int64 tail) {
- auto stmt =
- prepare("UPDATE linked_chunks_head_tail SET head = ?, tail = ?");
- if (!stmt)
- return false;
- if (head)
- stmt->bindInt64(head);
- else
- stmt->bindNull(); // shouldn't happen normally
- if (tail)
- stmt->bindInt64(tail);
- else
- stmt->bindNull(); // shouldn't happen normally
- const auto ret = stmt->execute();
- if (ret != SQLITE_DONE) {
- pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
- return false;
- }
- return true;
-}
+#endif // _WIN32
// ---------------------------------------------------------------------------
-bool DiskChunkCache::move_to_head(sqlite3_int64 chunk_id) {
-
- sqlite3_int64 link_id = 0;
- sqlite3_int64 prev = 0;
- sqlite3_int64 next = 0;
- sqlite3_int64 head = 0;
- sqlite3_int64 tail = 0;
- if (!get_links(chunk_id, link_id, prev, next, head, tail)) {
- return false;
- }
-
- if (link_id == head) {
- return true;
- }
-
- if (!update_links_of_prev_and_next_links(prev, next)) {
- return false;
- }
-
- if (head) {
- auto stmt = prepare("UPDATE linked_chunks SET prev = ? WHERE id = ?");
- if (!stmt)
- return false;
- stmt->bindInt64(link_id);
- stmt->bindInt64(head);
- const auto ret = stmt->execute();
- if (ret != SQLITE_DONE) {
- pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
- return false;
- }
- }
+#ifndef REMOVE_LEGACY_SUPPORT
- return update_linked_chunks(link_id, 0, head) &&
- update_linked_chunks_head_tail(link_id,
- (link_id == tail) ? prev : tail);
-}
+class FileLegacyAdapter : public File {
+ PJ_CONTEXT *m_ctx;
+ PAFile m_fp;
-// ---------------------------------------------------------------------------
+ FileLegacyAdapter(const FileLegacyAdapter &) = delete;
+ FileLegacyAdapter &operator=(const FileLegacyAdapter &) = delete;
-bool DiskChunkCache::move_to_tail(sqlite3_int64 chunk_id) {
- sqlite3_int64 link_id = 0;
- sqlite3_int64 prev = 0;
- sqlite3_int64 next = 0;
- sqlite3_int64 head = 0;
- sqlite3_int64 tail = 0;
- if (!get_links(chunk_id, link_id, prev, next, head, tail)) {
- return false;
- }
+ protected:
+ FileLegacyAdapter(const std::string &name, PJ_CONTEXT *ctx, PAFile fp)
+ : File(name), m_ctx(ctx), m_fp(fp) {}
- if (link_id == tail) {
- return true;
- }
+ public:
+ ~FileLegacyAdapter() override;
- if (!update_links_of_prev_and_next_links(prev, next)) {
- return false;
- }
+ size_t read(void *buffer, size_t sizeBytes) override;
+ size_t write(const void *, size_t) override { return 0; }
+ bool seek(unsigned long long offset, int whence = SEEK_SET) override;
+ unsigned long long tell() override;
+ void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; }
- if (tail) {
- auto stmt = prepare("UPDATE linked_chunks SET next = ? WHERE id = ?");
- if (!stmt)
- return false;
- stmt->bindInt64(link_id);
- stmt->bindInt64(tail);
- const auto ret = stmt->execute();
- if (ret != SQLITE_DONE) {
- pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
- return false;
- }
- }
+ // We may lie, but the real use case is only for network files
+ bool hasChanged() const override { return false; }
- return update_linked_chunks(link_id, tail, 0) &&
- update_linked_chunks_head_tail((link_id == head) ? next : head,
- link_id);
-}
+ static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename,
+ FileAccess access);
+};
// ---------------------------------------------------------------------------
-void NetworkChunkCache::insert(PJ_CONTEXT *ctx, const std::string &url,
- unsigned long long chunkIdx,
- std::vector<unsigned char> &&data) {
- auto dataPtr(std::make_shared<std::vector<unsigned char>>(std::move(data)));
- cache_.insert(Key(url, chunkIdx), dataPtr);
-
- auto diskCache = DiskChunkCache::open(ctx);
- if (!diskCache)
- return;
- auto hDB = diskCache->handle();
-
- // Always insert DOWNLOAD_CHUNK_SIZE bytes to avoid fragmentation
- std::vector<unsigned char> blob(*dataPtr);
- assert(blob.size() <= DOWNLOAD_CHUNK_SIZE);
- blob.resize(DOWNLOAD_CHUNK_SIZE);
-
- // Check if there is an existing entry for that URL and offset
- auto stmt = diskCache->prepare(
- "SELECT id, data_id FROM chunks WHERE url = ? AND offset = ?");
- if (!stmt)
- return;
- stmt->bindText(url.c_str());
- stmt->bindInt64(chunkIdx * DOWNLOAD_CHUNK_SIZE);
-
- const auto mainRet = stmt->execute();
- if (mainRet == SQLITE_ROW) {
- const auto chunk_id = stmt->getInt64();
- const auto data_id = stmt->getInt64();
- stmt =
- diskCache->prepare("UPDATE chunk_data SET data = ? WHERE id = ?");
- if (!stmt)
- return;
- stmt->bindBlob(blob.data(), blob.size());
- stmt->bindInt64(data_id);
- {
- const auto ret = stmt->execute();
- if (ret != SQLITE_DONE) {
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- return;
- }
- }
-
- diskCache->move_to_head(chunk_id);
-
- return;
- } else if (mainRet != SQLITE_DONE) {
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- return;
- }
-
- // Lambda to recycle an existing entry that was either invalidated, or
- // least recently used.
- const auto reuseExistingEntry = [ctx, &blob, &diskCache, hDB, &url,
- chunkIdx, &dataPtr](
- std::unique_ptr<SQLiteStatement> &stmtIn) {
- const auto chunk_id = stmtIn->getInt64();
- const auto data_id = stmtIn->getInt64();
- if (data_id <= 0) {
- pj_log(ctx, PJ_LOG_ERROR, "data_id <= 0");
- return;
- }
-
- auto l_stmt =
- diskCache->prepare("UPDATE chunk_data SET data = ? WHERE id = ?");
- if (!l_stmt)
- return;
- l_stmt->bindBlob(blob.data(), blob.size());
- l_stmt->bindInt64(data_id);
- {
- const auto ret2 = l_stmt->execute();
- if (ret2 != SQLITE_DONE) {
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- return;
- }
- }
-
- l_stmt = diskCache->prepare("UPDATE chunks SET url = ?, "
- "offset = ?, data_size = ?, data_id = ? "
- "WHERE id = ?");
- if (!l_stmt)
- return;
- l_stmt->bindText(url.c_str());
- l_stmt->bindInt64(chunkIdx * DOWNLOAD_CHUNK_SIZE);
- l_stmt->bindInt64(dataPtr->size());
- l_stmt->bindInt64(data_id);
- l_stmt->bindInt64(chunk_id);
- {
- const auto ret2 = l_stmt->execute();
- if (ret2 != SQLITE_DONE) {
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- return;
- }
- }
-
- diskCache->move_to_head(chunk_id);
- };
-
- // Find if there is an invalidated chunk we can reuse
- stmt = diskCache->prepare(
- "SELECT id, data_id FROM chunks "
- "WHERE id = (SELECT tail FROM linked_chunks_head_tail) AND "
- "url = " INVALIDATED_SQL_LITERAL);
- if (!stmt)
- return;
- {
- const auto ret = stmt->execute();
- if (ret == SQLITE_ROW) {
- reuseExistingEntry(stmt);
- return;
- } else if (ret != SQLITE_DONE) {
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- return;
- }
- }
-
- // Check if we have not reached the max size of the cache
- stmt = diskCache->prepare("SELECT COUNT(*) FROM chunks");
- if (!stmt)
- return;
- {
- const auto ret = stmt->execute();
- if (ret != SQLITE_ROW) {
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- return;
- }
- }
-
- const auto max_size = pj_context_get_grid_cache_max_size(ctx);
- if (max_size > 0 &&
- static_cast<long long>(stmt->getInt64() * DOWNLOAD_CHUNK_SIZE) >=
- max_size) {
- stmt = diskCache->prepare(
- "SELECT id, data_id FROM chunks "
- "WHERE id = (SELECT tail FROM linked_chunks_head_tail)");
- if (!stmt)
- return;
-
- const auto ret = stmt->execute();
- if (ret != SQLITE_ROW) {
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- return;
- }
- reuseExistingEntry(stmt);
- return;
- }
-
- // Otherwise just append a new entry
- stmt = diskCache->prepare("INSERT INTO chunk_data(data) VALUES (?)");
- if (!stmt)
- return;
- stmt->bindBlob(blob.data(), blob.size());
- {
- const auto ret = stmt->execute();
- if (ret != SQLITE_DONE) {
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- return;
- }
- }
-
- const auto chunk_data_id = sqlite3_last_insert_rowid(hDB);
-
- stmt = diskCache->prepare("INSERT INTO chunks(url, offset, data_id, "
- "data_size) VALUES (?,?,?,?)");
- if (!stmt)
- return;
- stmt->bindText(url.c_str());
- stmt->bindInt64(chunkIdx * DOWNLOAD_CHUNK_SIZE);
- stmt->bindInt64(chunk_data_id);
- stmt->bindInt64(dataPtr->size());
- {
- const auto ret = stmt->execute();
- if (ret != SQLITE_DONE) {
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- return;
- }
- }
-
- const auto chunk_id = sqlite3_last_insert_rowid(hDB);
-
- stmt = diskCache->prepare(
- "INSERT INTO linked_chunks(chunk_id, prev, next) VALUES (?,NULL,NULL)");
- if (!stmt)
- return;
- stmt->bindInt64(chunk_id);
- if (stmt->execute() != SQLITE_DONE) {
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- return;
- }
-
- stmt = diskCache->prepare("SELECT head FROM linked_chunks_head_tail");
- if (!stmt)
- return;
- if (stmt->execute() != SQLITE_ROW) {
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- return;
- }
- if (stmt->getInt64() == 0) {
- stmt = diskCache->prepare(
- "UPDATE linked_chunks_head_tail SET head = ?, tail = ?");
- if (!stmt)
- return;
- stmt->bindInt64(chunk_id);
- stmt->bindInt64(chunk_id);
- if (stmt->execute() != SQLITE_DONE) {
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- return;
- }
- }
-
- diskCache->move_to_head(chunk_id);
-}
+FileLegacyAdapter::~FileLegacyAdapter() { pj_ctx_fclose(m_ctx, m_fp); }
// ---------------------------------------------------------------------------
-std::shared_ptr<std::vector<unsigned char>>
-NetworkChunkCache::get(PJ_CONTEXT *ctx, const std::string &url,
- unsigned long long chunkIdx) {
- std::shared_ptr<std::vector<unsigned char>> ret;
- if (cache_.tryGet(Key(url, chunkIdx), ret)) {
- return ret;
- }
-
- auto diskCache = DiskChunkCache::open(ctx);
- if (!diskCache)
- return ret;
- auto hDB = diskCache->handle();
-
- auto stmt = diskCache->prepare(
- "SELECT chunks.id, chunks.data_size, chunk_data.data FROM chunks "
- "JOIN chunk_data ON chunks.id = chunk_data.id "
- "WHERE chunks.url = ? AND chunks.offset = ?");
- if (!stmt)
- return ret;
-
- stmt->bindText(url.c_str());
- stmt->bindInt64(chunkIdx * DOWNLOAD_CHUNK_SIZE);
-
- const auto mainRet = stmt->execute();
- if (mainRet == SQLITE_ROW) {
- const auto chunk_id = stmt->getInt64();
- const auto data_size = stmt->getInt64();
- int blob_size = 0;
- const void *blob = stmt->getBlob(blob_size);
- if (blob_size < data_size) {
- pj_log(ctx, PJ_LOG_ERROR,
- "blob_size=%d < data_size for chunk_id=%d", blob_size,
- static_cast<int>(chunk_id));
- return ret;
- }
- if (data_size > static_cast<sqlite3_int64>(DOWNLOAD_CHUNK_SIZE)) {
- pj_log(ctx, PJ_LOG_ERROR, "data_size > DOWNLOAD_CHUNK_SIZE");
- return ret;
- }
- ret.reset(new std::vector<unsigned char>());
- ret->assign(reinterpret_cast<const unsigned char *>(blob),
- reinterpret_cast<const unsigned char *>(blob) +
- static_cast<size_t>(data_size));
- cache_.insert(Key(url, chunkIdx), ret);
-
- if (!diskCache->move_to_head(chunk_id))
- return ret;
- } else if (mainRet != SQLITE_DONE) {
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- }
-
- return ret;
+size_t FileLegacyAdapter::read(void *buffer, size_t sizeBytes) {
+ return pj_ctx_fread(m_ctx, buffer, 1, sizeBytes, m_fp);
}
// ---------------------------------------------------------------------------
-std::shared_ptr<std::vector<unsigned char>>
-NetworkChunkCache::get(PJ_CONTEXT *ctx, const std::string &url,
- unsigned long long chunkIdx, FileProperties &props) {
- if (!gNetworkFileProperties.tryGet(ctx, url, props)) {
- return nullptr;
+bool FileLegacyAdapter::seek(unsigned long long offset, int whence) {
+ if (offset != static_cast<unsigned long long>(static_cast<long>(offset))) {
+ pj_log(m_ctx, PJ_LOG_ERROR,
+ "Attempt at seeking to a 64 bit offset. Not supported yet");
+ return false;
}
-
- return get(ctx, url, chunkIdx);
-}
-
-// ---------------------------------------------------------------------------
-
-void NetworkChunkCache::clearMemoryCache() { cache_.clear(); }
-
-// ---------------------------------------------------------------------------
-
-void NetworkChunkCache::clearDiskChunkCache(PJ_CONTEXT *ctx) {
- auto diskCache = DiskChunkCache::open(ctx);
- if (!diskCache)
- return;
- diskCache->closeAndUnlink();
+ return pj_ctx_fseek(m_ctx, m_fp, static_cast<long>(offset), whence) == 0;
}
// ---------------------------------------------------------------------------
-void NetworkFilePropertiesCache::insert(PJ_CONTEXT *ctx, const std::string &url,
- FileProperties &props) {
- time(&props.lastChecked);
- cache_.insert(url, props);
-
- auto diskCache = DiskChunkCache::open(ctx);
- if (!diskCache)
- return;
- auto hDB = diskCache->handle();
- auto stmt = diskCache->prepare("SELECT fileSize, lastModified, etag "
- "FROM properties WHERE url = ?");
- if (!stmt)
- return;
- stmt->bindText(url.c_str());
- if (stmt->execute() == SQLITE_ROW) {
- FileProperties cachedProps;
- cachedProps.size = stmt->getInt64();
- const char *lastModified = stmt->getText();
- cachedProps.lastModified = lastModified ? lastModified : std::string();
- const char *etag = stmt->getText();
- cachedProps.etag = etag ? etag : std::string();
- if (props.size != cachedProps.size ||
- props.lastModified != cachedProps.lastModified ||
- props.etag != cachedProps.etag) {
-
- // If cached properties don't match recent fresh ones, invalidate
- // cached chunks
- stmt = diskCache->prepare("SELECT id FROM chunks WHERE url = ?");
- if (!stmt)
- return;
- stmt->bindText(url.c_str());
- std::vector<sqlite3_int64> ids;
- while (stmt->execute() == SQLITE_ROW) {
- ids.emplace_back(stmt->getInt64());
- stmt->resetResIndex();
- }
-
- for (const auto id : ids) {
- diskCache->move_to_tail(id);
- }
-
- stmt = diskCache->prepare(
- "UPDATE chunks SET url = " INVALIDATED_SQL_LITERAL ", "
- "offset = -1, data_size = 0 WHERE url = ?");
- if (!stmt)
- return;
- stmt->bindText(url.c_str());
- if (stmt->execute() != SQLITE_DONE) {
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- return;
- }
- }
-
- stmt = diskCache->prepare("UPDATE properties SET lastChecked = ?, "
- "fileSize = ?, lastModified = ?, etag = ? "
- "WHERE url = ?");
- if (!stmt)
- return;
- stmt->bindInt64(props.lastChecked);
- stmt->bindInt64(props.size);
- if (props.lastModified.empty())
- stmt->bindNull();
- else
- stmt->bindText(props.lastModified.c_str());
- if (props.etag.empty())
- stmt->bindNull();
- else
- stmt->bindText(props.etag.c_str());
- stmt->bindText(url.c_str());
- if (stmt->execute() != SQLITE_DONE) {
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- return;
- }
- } else {
- stmt = diskCache->prepare("INSERT INTO properties (url, lastChecked, "
- "fileSize, lastModified, etag) VALUES "
- "(?,?,?,?,?)");
- if (!stmt)
- return;
- stmt->bindText(url.c_str());
- stmt->bindInt64(props.lastChecked);
- stmt->bindInt64(props.size);
- if (props.lastModified.empty())
- stmt->bindNull();
- else
- stmt->bindText(props.lastModified.c_str());
- if (props.etag.empty())
- stmt->bindNull();
- else
- stmt->bindText(props.etag.c_str());
- if (stmt->execute() != SQLITE_DONE) {
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- return;
- }
- }
+unsigned long long FileLegacyAdapter::tell() {
+ return pj_ctx_ftell(m_ctx, m_fp);
}
// ---------------------------------------------------------------------------
-bool NetworkFilePropertiesCache::tryGet(PJ_CONTEXT *ctx, const std::string &url,
- FileProperties &props) {
- if (cache_.tryGet(url, props)) {
- return true;
- }
-
- auto diskCache = DiskChunkCache::open(ctx);
- if (!diskCache)
- return false;
- auto stmt =
- diskCache->prepare("SELECT lastChecked, fileSize, lastModified, etag "
- "FROM properties WHERE url = ?");
- if (!stmt)
- return false;
- stmt->bindText(url.c_str());
- if (stmt->execute() != SQLITE_ROW) {
- return false;
- }
- props.lastChecked = stmt->getInt64();
- props.size = stmt->getInt64();
- const char *lastModified = stmt->getText();
- props.lastModified = lastModified ? lastModified : std::string();
- const char *etag = stmt->getText();
- props.etag = etag ? etag : std::string();
-
- const auto ttl = pj_context_get_grid_cache_ttl(ctx);
- if (ttl > 0) {
- time_t curTime;
- time(&curTime);
- if (curTime > props.lastChecked + ttl) {
- props = FileProperties();
- return false;
- }
- }
- cache_.insert(url, props);
- return true;
+std::unique_ptr<File>
+FileLegacyAdapter::open(PJ_CONTEXT *ctx, const char *filename, FileAccess) {
+ auto fid = pj_ctx_fopen(ctx, filename, "rb");
+ return std::unique_ptr<File>(fid ? new FileLegacyAdapter(filename, ctx, fid)
+ : nullptr);
}
-// ---------------------------------------------------------------------------
-
-void NetworkFilePropertiesCache::clearMemoryCache() { cache_.clear(); }
+#endif // REMOVE_LEGACY_SUPPORT
// ---------------------------------------------------------------------------
-class NetworkFile : public File {
+class FileApiAdapter : public File {
PJ_CONTEXT *m_ctx;
- std::string m_url;
- PROJ_NETWORK_HANDLE *m_handle;
- unsigned long long m_pos = 0;
- size_t m_nBlocksToDownload = 1;
- unsigned long long m_lastDownloadedOffset;
- FileProperties m_props;
- proj_network_close_cbk_type m_closeCbk;
- bool m_hasChanged = false;
-
- NetworkFile(const NetworkFile &) = delete;
- NetworkFile &operator=(const NetworkFile &) = delete;
+ PROJ_FILE_HANDLE *m_fp;
+
+ FileApiAdapter(const FileApiAdapter &) = delete;
+ FileApiAdapter &operator=(const FileApiAdapter &) = delete;
protected:
- NetworkFile(PJ_CONTEXT *ctx, const std::string &url,
- PROJ_NETWORK_HANDLE *handle,
- unsigned long long lastDownloadOffset,
- const FileProperties &props)
- : File(url), m_ctx(ctx), m_url(url), m_handle(handle),
- m_lastDownloadedOffset(lastDownloadOffset), m_props(props),
- m_closeCbk(ctx->networking.close) {}
+ FileApiAdapter(const std::string &name, PJ_CONTEXT *ctx,
+ PROJ_FILE_HANDLE *fp)
+ : File(name), m_ctx(ctx), m_fp(fp) {}
public:
- ~NetworkFile() override;
+ ~FileApiAdapter() override;
size_t read(void *buffer, size_t sizeBytes) override;
- bool seek(unsigned long long offset, int whence) override;
+ size_t write(const void *, size_t) override;
+ bool seek(unsigned long long offset, int whence = SEEK_SET) override;
unsigned long long tell() override;
- void reassign_context(PJ_CONTEXT *ctx) override;
- bool hasChanged() const override { return m_hasChanged; }
+ void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; }
- static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename);
+ // We may lie, but the real use case is only for network files
+ bool hasChanged() const override { return false; }
- static bool get_props_from_headers(PJ_CONTEXT *ctx,
- PROJ_NETWORK_HANDLE *handle,
- FileProperties &props);
+ static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename,
+ FileAccess access);
};
// ---------------------------------------------------------------------------
-bool NetworkFile::get_props_from_headers(PJ_CONTEXT *ctx,
- PROJ_NETWORK_HANDLE *handle,
- FileProperties &props) {
- const char *contentRange = ctx->networking.get_header_value(
- ctx, handle, "Content-Range", ctx->networking.user_data);
- if (contentRange) {
- const char *slash = strchr(contentRange, '/');
- if (slash) {
- props.size = std::stoull(slash + 1);
-
- const char *lastModified = ctx->networking.get_header_value(
- ctx, handle, "Last-Modified", ctx->networking.user_data);
- if (lastModified)
- props.lastModified = lastModified;
-
- const char *etag = ctx->networking.get_header_value(
- ctx, handle, "ETag", ctx->networking.user_data);
- if (etag)
- props.etag = etag;
-
- return true;
- }
- }
- return false;
+FileApiAdapter::~FileApiAdapter() {
+ m_ctx->fileApi.close_cbk(m_ctx, m_fp, m_ctx->fileApi.user_data);
}
// ---------------------------------------------------------------------------
-std::unique_ptr<File> NetworkFile::open(PJ_CONTEXT *ctx, const char *filename) {
- FileProperties props;
- if (gNetworkChunkCache.get(ctx, filename, 0, props)) {
- return std::unique_ptr<File>(new NetworkFile(
- ctx, filename, nullptr,
- std::numeric_limits<unsigned long long>::max(), props));
- } else {
- std::vector<unsigned char> buffer(DOWNLOAD_CHUNK_SIZE);
- size_t size_read = 0;
- std::string errorBuffer;
- errorBuffer.resize(1024);
-
- auto handle = ctx->networking.open(
- ctx, filename, 0, buffer.size(), &buffer[0], &size_read,
- errorBuffer.size(), &errorBuffer[0], ctx->networking.user_data);
- buffer.resize(size_read);
- if (!handle) {
- errorBuffer.resize(strlen(errorBuffer.data()));
- pj_log(ctx, PJ_LOG_ERROR, "Cannot open %s: %s", filename,
- errorBuffer.c_str());
- }
-
- bool ok = false;
- if (handle) {
- if (get_props_from_headers(ctx, handle, props)) {
- ok = true;
- gNetworkFileProperties.insert(ctx, filename, props);
- gNetworkChunkCache.insert(ctx, filename, 0, std::move(buffer));
- }
- }
-
- return std::unique_ptr<File>(
- ok ? new NetworkFile(ctx, filename, handle, size_read, props)
- : nullptr);
- }
+size_t FileApiAdapter::read(void *buffer, size_t sizeBytes) {
+ return m_ctx->fileApi.read_cbk(m_ctx, m_fp, buffer, sizeBytes,
+ m_ctx->fileApi.user_data);
}
// ---------------------------------------------------------------------------
-size_t NetworkFile::read(void *buffer, size_t sizeBytes) {
-
- if (sizeBytes == 0)
- return 0;
-
- auto iterOffset = m_pos;
- while (sizeBytes) {
- const auto chunkIdxToDownload = iterOffset / DOWNLOAD_CHUNK_SIZE;
- const auto offsetToDownload = chunkIdxToDownload * DOWNLOAD_CHUNK_SIZE;
- std::vector<unsigned char> region;
- auto pChunk = gNetworkChunkCache.get(m_ctx, m_url, chunkIdxToDownload);
- if (pChunk != nullptr) {
- region = *pChunk;
- } else {
- if (offsetToDownload == m_lastDownloadedOffset) {
- // In case of consecutive reads (of small size), we use a
- // heuristic that we will read the file sequentially, so
- // we double the requested size to decrease the number of
- // client/server roundtrips.
- if (m_nBlocksToDownload < 100)
- m_nBlocksToDownload *= 2;
- } else {
- // Random reads. Cancel the above heuristics.
- m_nBlocksToDownload = 1;
- }
-
- // Ensure that we will request at least the number of blocks
- // to satisfy the remaining buffer size to read.
- const auto endOffsetToDownload =
- ((iterOffset + sizeBytes + DOWNLOAD_CHUNK_SIZE - 1) /
- DOWNLOAD_CHUNK_SIZE) *
- DOWNLOAD_CHUNK_SIZE;
- const auto nMinBlocksToDownload = static_cast<size_t>(
- (endOffsetToDownload - offsetToDownload) / DOWNLOAD_CHUNK_SIZE);
- if (m_nBlocksToDownload < nMinBlocksToDownload)
- m_nBlocksToDownload = nMinBlocksToDownload;
-
- // Avoid reading already cached data.
- // Note: this might get evicted if concurrent reads are done, but
- // this should not cause bugs. Just missed optimization.
- for (size_t i = 1; i < m_nBlocksToDownload; i++) {
- if (gNetworkChunkCache.get(m_ctx, m_url,
- chunkIdxToDownload + i) != nullptr) {
- m_nBlocksToDownload = i;
- break;
- }
- }
-
- if (m_nBlocksToDownload > MAX_CHUNKS)
- m_nBlocksToDownload = MAX_CHUNKS;
-
- region.resize(m_nBlocksToDownload * DOWNLOAD_CHUNK_SIZE);
- size_t nRead = 0;
- std::string errorBuffer;
- errorBuffer.resize(1024);
- if (!m_handle) {
- m_handle = m_ctx->networking.open(
- m_ctx, m_url.c_str(), offsetToDownload,
- m_nBlocksToDownload * DOWNLOAD_CHUNK_SIZE, &region[0],
- &nRead, errorBuffer.size(), &errorBuffer[0],
- m_ctx->networking.user_data);
- if (!m_handle) {
- return 0;
- }
- } else {
- nRead = m_ctx->networking.read_range(
- m_ctx, m_handle, offsetToDownload,
- m_nBlocksToDownload * DOWNLOAD_CHUNK_SIZE, &region[0],
- errorBuffer.size(), &errorBuffer[0],
- m_ctx->networking.user_data);
- }
- if (nRead == 0) {
- errorBuffer.resize(strlen(errorBuffer.data()));
- if (!errorBuffer.empty()) {
- pj_log(m_ctx, PJ_LOG_ERROR, "Cannot read in %s: %s",
- m_url.c_str(), errorBuffer.c_str());
- }
- return 0;
- }
-
- if (!m_hasChanged) {
- FileProperties props;
- if (get_props_from_headers(m_ctx, m_handle, props)) {
- if (props.size != m_props.size ||
- props.lastModified != m_props.lastModified ||
- props.etag != m_props.etag) {
- gNetworkFileProperties.insert(m_ctx, m_url, props);
- gNetworkChunkCache.clearMemoryCache();
- m_hasChanged = true;
- }
- }
- }
-
- region.resize(nRead);
- m_lastDownloadedOffset = offsetToDownload + nRead;
-
- const auto nChunks =
- (region.size() + DOWNLOAD_CHUNK_SIZE - 1) / DOWNLOAD_CHUNK_SIZE;
- for (size_t i = 0; i < nChunks; i++) {
- std::vector<unsigned char> chunk(
- region.data() + i * DOWNLOAD_CHUNK_SIZE,
- region.data() +
- std::min((i + 1) * DOWNLOAD_CHUNK_SIZE, region.size()));
- gNetworkChunkCache.insert(m_ctx, m_url, chunkIdxToDownload + i,
- std::move(chunk));
- }
- }
- const size_t nToCopy = static_cast<size_t>(
- std::min(static_cast<unsigned long long>(sizeBytes),
- region.size() - (iterOffset - offsetToDownload)));
- memcpy(buffer, region.data() + iterOffset - offsetToDownload, nToCopy);
- buffer = static_cast<char *>(buffer) + nToCopy;
- iterOffset += nToCopy;
- sizeBytes -= nToCopy;
- if (region.size() < static_cast<size_t>(DOWNLOAD_CHUNK_SIZE) &&
- sizeBytes != 0) {
- break;
- }
- }
-
- size_t nRead = static_cast<size_t>(iterOffset - m_pos);
- m_pos = iterOffset;
- return nRead;
+size_t FileApiAdapter::write(const void *buffer, size_t sizeBytes) {
+ return m_ctx->fileApi.write_cbk(m_ctx, m_fp, buffer, sizeBytes,
+ m_ctx->fileApi.user_data);
}
// ---------------------------------------------------------------------------
-bool NetworkFile::seek(unsigned long long offset, int whence) {
- if (whence == SEEK_SET) {
- m_pos = offset;
- } else if (whence == SEEK_CUR) {
- m_pos += offset;
- } else {
- if (offset != 0)
- return false;
- m_pos = m_props.size;
- }
- return true;
+bool FileApiAdapter::seek(unsigned long long offset, int whence) {
+ return m_ctx->fileApi.seek_cbk(m_ctx, m_fp, static_cast<long long>(offset),
+ whence, m_ctx->fileApi.user_data) != 0;
}
// ---------------------------------------------------------------------------
-unsigned long long NetworkFile::tell() { return m_pos; }
-
-// ---------------------------------------------------------------------------
-
-NetworkFile::~NetworkFile() {
- if (m_handle) {
- m_ctx->networking.close(m_ctx, m_handle, m_ctx->networking.user_data);
- }
+unsigned long long FileApiAdapter::tell() {
+ return m_ctx->fileApi.tell_cbk(m_ctx, m_fp, m_ctx->fileApi.user_data);
}
// ---------------------------------------------------------------------------
-void NetworkFile::reassign_context(PJ_CONTEXT *ctx) {
- m_ctx = ctx;
- if (m_closeCbk != m_ctx->networking.close) {
- pj_log(m_ctx, PJ_LOG_ERROR,
- "Networking close callback has changed following context "
- "reassignment ! This is highly suspicious");
+std::unique_ptr<File> FileApiAdapter::open(PJ_CONTEXT *ctx,
+ const char *filename,
+ FileAccess eAccess) {
+ PROJ_OPEN_ACCESS eCAccess = PROJ_OPEN_ACCESS_READ_ONLY;
+ switch (eAccess) {
+ case FileAccess::READ_ONLY:
+ // Initialized above
+ break;
+ case FileAccess::READ_UPDATE:
+ eCAccess = PROJ_OPEN_ACCESS_READ_UPDATE;
+ break;
+ case FileAccess::CREATE:
+ eCAccess = PROJ_OPEN_ACCESS_CREATE;
+ break;
}
+ auto fp =
+ ctx->fileApi.open_cbk(ctx, filename, eCAccess, ctx->fileApi.user_data);
+ return std::unique_ptr<File>(fp ? new FileApiAdapter(filename, ctx, fp)
+ : nullptr);
}
// ---------------------------------------------------------------------------
-std::unique_ptr<File> FileManager::open(PJ_CONTEXT *ctx, const char *filename) {
-#ifndef REMOVE_LEGACY_SUPPORT
- // If the user has specified a legacy fileapi, use it
- if (ctx->fileapi_legacy != pj_get_default_fileapi()) {
- return FileLegacyAdapter::open(ctx, filename);
- }
-#endif
+std::unique_ptr<File> FileManager::open(PJ_CONTEXT *ctx, const char *filename,
+ FileAccess access) {
if (starts_with(filename, "http://") || starts_with(filename, "https://")) {
if (!pj_context_is_network_enabled(ctx)) {
pj_log(
@@ -1632,671 +888,232 @@ std::unique_ptr<File> FileManager::open(PJ_CONTEXT *ctx, const char *filename) {
"proj_context_set_enable_network(ctx, TRUE)");
return nullptr;
}
- return NetworkFile::open(ctx, filename);
- }
- return FileStdio::open(ctx, filename);
-}
-
-// ---------------------------------------------------------------------------
-
-#ifdef CURL_ENABLED
-
-struct CurlFileHandle {
- std::string m_url;
- CURL *m_handle;
- std::string m_headers{};
- std::string m_lastval{};
- std::string m_useragent{};
- char m_szCurlErrBuf[CURL_ERROR_SIZE + 1] = {};
-
- CurlFileHandle(const CurlFileHandle &) = delete;
- CurlFileHandle &operator=(const CurlFileHandle &) = delete;
-
- explicit CurlFileHandle(const char *url, CURL *handle);
- ~CurlFileHandle();
-
- static PROJ_NETWORK_HANDLE *
- open(PJ_CONTEXT *, const char *url, unsigned long long offset,
- size_t size_to_read, void *buffer, size_t *out_size_read,
- size_t error_string_max_size, char *out_error_string, void *);
-};
-
-// ---------------------------------------------------------------------------
-
-static std::string GetExecutableName() {
-#if defined(__linux)
- std::string path;
- path.resize(1024);
- const auto ret = readlink("/proc/self/exe", &path[0], path.size());
- if (ret > 0) {
- path.resize(ret);
- const auto pos = path.rfind('/');
- if (pos != std::string::npos) {
- path = path.substr(pos + 1);
- }
- return path;
- }
-#elif defined(_WIN32)
- std::string path;
- path.resize(1024);
- if (GetModuleFileNameA(nullptr, &path[0],
- static_cast<DWORD>(path.size()))) {
- path.resize(strlen(path.c_str()));
- const auto pos = path.rfind('\\');
- if (pos != std::string::npos) {
- path = path.substr(pos + 1);
- }
- return path;
+ return pj_network_file_open(ctx, filename);
}
-#elif defined(__MACH__) && defined(__APPLE__)
- std::string path;
- path.resize(1024);
- uint32_t size = static_cast<uint32_t>(path.size());
- if (_NSGetExecutablePath(&path[0], &size) == 0) {
- path.resize(strlen(path.c_str()));
- const auto pos = path.rfind('/');
- if (pos != std::string::npos) {
- path = path.substr(pos + 1);
- }
- return path;
+#ifndef REMOVE_LEGACY_SUPPORT
+ // If the user has specified a legacy fileapi, use it
+ if (ctx->fileapi_legacy != pj_get_default_fileapi()) {
+ return FileLegacyAdapter::open(ctx, filename, access);
}
-#elif defined(__FreeBSD__)
- int mib[4];
- mib[0] = CTL_KERN;
- mib[1] = KERN_PROC;
- mib[2] = KERN_PROC_PATHNAME;
- mib[3] = -1;
- std::string path;
- path.resize(1024);
- size_t size = path.size();
- if (sysctl(mib, 4, &path[0], &size, nullptr, 0) == 0) {
- path.resize(strlen(path.c_str()));
- const auto pos = path.rfind('/');
- if (pos != std::string::npos) {
- path = path.substr(pos + 1);
- }
- return path;
+#endif
+ if (ctx->fileApi.open_cbk != nullptr) {
+ return FileApiAdapter::open(ctx, filename, access);
}
+#ifdef _WIN32
+ return FileWin32::open(ctx, filename, access);
+#else
+ return FileStdio::open(ctx, filename, access);
#endif
-
- return std::string();
}
// ---------------------------------------------------------------------------
-CurlFileHandle::CurlFileHandle(const char *url, CURL *handle)
- : m_url(url), m_handle(handle) {
- curl_easy_setopt(handle, CURLOPT_URL, m_url.c_str());
-
- if (getenv("PROJ_CURL_VERBOSE"))
- curl_easy_setopt(handle, CURLOPT_VERBOSE, 1);
-
-// CURLOPT_SUPPRESS_CONNECT_HEADERS is defined in curl 7.54.0 or newer.
-#if LIBCURL_VERSION_NUM >= 0x073600
- curl_easy_setopt(handle, CURLOPT_SUPPRESS_CONNECT_HEADERS, 1L);
-#endif
-
- // Enable following redirections. Requires libcurl 7.10.1 at least.
- curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1);
- curl_easy_setopt(handle, CURLOPT_MAXREDIRS, 10);
-
- if (getenv("PROJ_UNSAFE_SSL")) {
- curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L);
- curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L);
+bool FileManager::exists(PJ_CONTEXT *ctx, const char *filename) {
+ if (ctx->fileApi.exists_cbk) {
+ return ctx->fileApi.exists_cbk(ctx, filename, ctx->fileApi.user_data) !=
+ 0;
}
- curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, m_szCurlErrBuf);
-
- if (getenv("PROJ_NO_USERAGENT") == nullptr) {
- m_useragent = "PROJ " STR(PROJ_VERSION_MAJOR) "." STR(
- PROJ_VERSION_MINOR) "." STR(PROJ_VERSION_PATCH);
- const auto exeName = GetExecutableName();
- if (!exeName.empty()) {
- m_useragent = exeName + " using " + m_useragent;
- }
- curl_easy_setopt(handle, CURLOPT_USERAGENT, m_useragent.data());
+#ifdef _WIN32
+ struct __stat64 buf;
+ try {
+ return _wstat64(UTF8ToWString(filename).c_str(), &buf) == 0;
+ } catch (const std::exception &e) {
+ pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
+ return false;
}
+#else
+ (void)ctx;
+ struct stat sStat;
+ return stat(filename, &sStat) == 0;
+#endif
}
// ---------------------------------------------------------------------------
-CurlFileHandle::~CurlFileHandle() { curl_easy_cleanup(m_handle); }
-
-// ---------------------------------------------------------------------------
-
-static size_t pj_curl_write_func(void *buffer, size_t count, size_t nmemb,
- void *req) {
- const size_t nSize = count * nmemb;
- auto pStr = static_cast<std::string *>(req);
- if (pStr->size() + nSize > pStr->capacity()) {
- // to avoid servers not honouring Range to cause excessive memory
- // allocation
- return 0;
+bool FileManager::mkdir(PJ_CONTEXT *ctx, const char *filename) {
+ if (ctx->fileApi.mkdir_cbk) {
+ return ctx->fileApi.mkdir_cbk(ctx, filename, ctx->fileApi.user_data) !=
+ 0;
}
- pStr->append(static_cast<const char *>(buffer), nSize);
- return nmemb;
-}
-// ---------------------------------------------------------------------------
-
-static double GetNewRetryDelay(int response_code, double dfOldDelay,
- const char *pszErrBuf,
- const char *pszCurlError) {
- if (response_code == 429 || response_code == 500 ||
- (response_code >= 502 && response_code <= 504) ||
- // S3 sends some client timeout errors as 400 Client Error
- (response_code == 400 && pszErrBuf &&
- strstr(pszErrBuf, "RequestTimeout")) ||
- (pszCurlError && strstr(pszCurlError, "Connection timed out"))) {
- // Use an exponential backoff factor of 2 plus some random jitter
- // We don't care about cryptographic quality randomness, hence:
- // coverity[dont_call]
- return dfOldDelay * (2 + rand() * 0.5 / RAND_MAX);
- } else {
- return 0;
+#ifdef _WIN32
+ try {
+ return _wmkdir(UTF8ToWString(filename).c_str()) == 0;
+ } catch (const std::exception &e) {
+ pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
+ return false;
}
+#else
+ (void)ctx;
+ return ::mkdir(filename, 0755) == 0;
+#endif
}
// ---------------------------------------------------------------------------
-constexpr double MIN_RETRY_DELAY_MS = 500;
-constexpr double MAX_RETRY_DELAY_MS = 60000;
-
-PROJ_NETWORK_HANDLE *CurlFileHandle::open(PJ_CONTEXT *ctx, const char *url,
- unsigned long long offset,
- size_t size_to_read, void *buffer,
- size_t *out_size_read,
- size_t error_string_max_size,
- char *out_error_string, void *) {
- CURL *hCurlHandle = curl_easy_init();
- if (!hCurlHandle)
- return nullptr;
-
- auto file =
- std::unique_ptr<CurlFileHandle>(new CurlFileHandle(url, hCurlHandle));
-
- double oldDelay = MIN_RETRY_DELAY_MS;
- std::string headers;
- std::string body;
-
- char szBuffer[128];
- sqlite3_snprintf(sizeof(szBuffer), szBuffer, "%llu-%llu", offset,
- offset + size_to_read - 1);
-
- while (true) {
- curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, szBuffer);
-
- headers.clear();
- headers.reserve(16 * 1024);
- curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, &headers);
- curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
- pj_curl_write_func);
-
- body.clear();
- body.reserve(size_to_read);
- curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &body);
- curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
- pj_curl_write_func);
-
- file->m_szCurlErrBuf[0] = '\0';
-
- curl_easy_perform(hCurlHandle);
-
- long response_code = 0;
- curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
-
- curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, nullptr);
- curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION, nullptr);
-
- curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, nullptr);
- curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, nullptr);
-
- if (response_code == 0 || response_code >= 300) {
- const double delay =
- GetNewRetryDelay(static_cast<int>(response_code), oldDelay,
- body.c_str(), file->m_szCurlErrBuf);
- if (delay != 0 && delay < MAX_RETRY_DELAY_MS) {
- pj_log(ctx, PJ_LOG_TRACE,
- "Got a HTTP %ld error. Retrying in %d ms", response_code,
- static_cast<int>(delay));
- proj_sleep_ms(static_cast<int>(delay));
- oldDelay = delay;
- } else {
- if (out_error_string) {
- if (file->m_szCurlErrBuf[0]) {
- snprintf(out_error_string, error_string_max_size, "%s",
- file->m_szCurlErrBuf);
- } else {
- snprintf(out_error_string, error_string_max_size,
- "HTTP error %ld: %s", response_code,
- body.c_str());
- }
- }
- return nullptr;
- }
- } else {
- break;
- }
- }
-
- if (out_error_string && error_string_max_size) {
- out_error_string[0] = '\0';
+bool FileManager::unlink(PJ_CONTEXT *ctx, const char *filename) {
+ if (ctx->fileApi.unlink_cbk) {
+ return ctx->fileApi.unlink_cbk(ctx, filename, ctx->fileApi.user_data) !=
+ 0;
}
- if (!body.empty()) {
- memcpy(buffer, body.data(), std::min(size_to_read, body.size()));
+#ifdef _WIN32
+ try {
+ return _wunlink(UTF8ToWString(filename).c_str()) == 0;
+ } catch (const std::exception &e) {
+ pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
+ return false;
}
- *out_size_read = std::min(size_to_read, body.size());
-
- file->m_headers = std::move(headers);
- return reinterpret_cast<PROJ_NETWORK_HANDLE *>(file.release());
-}
-
-// ---------------------------------------------------------------------------
-
-static void pj_curl_close(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *handle,
- void * /*user_data*/) {
- delete reinterpret_cast<CurlFileHandle *>(handle);
+#else
+ (void)ctx;
+ return ::unlink(filename) == 0;
+#endif
}
// ---------------------------------------------------------------------------
-static size_t pj_curl_read_range(PJ_CONTEXT *ctx,
- PROJ_NETWORK_HANDLE *raw_handle,
- unsigned long long offset, size_t size_to_read,
- void *buffer, size_t error_string_max_size,
- char *out_error_string, void *) {
- auto handle = reinterpret_cast<CurlFileHandle *>(raw_handle);
- auto hCurlHandle = handle->m_handle;
-
- double oldDelay = MIN_RETRY_DELAY_MS;
- std::string headers;
- std::string body;
-
- char szBuffer[128];
- sqlite3_snprintf(sizeof(szBuffer), szBuffer, "%llu-%llu", offset,
- offset + size_to_read - 1);
-
- while (true) {
- curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, szBuffer);
-
- headers.clear();
- headers.reserve(16 * 1024);
- curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, &headers);
- curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
- pj_curl_write_func);
-
- body.clear();
- body.reserve(size_to_read);
- curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &body);
- curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
- pj_curl_write_func);
-
- handle->m_szCurlErrBuf[0] = '\0';
-
- curl_easy_perform(hCurlHandle);
-
- long response_code = 0;
- curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
-
- curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, nullptr);
- curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, nullptr);
-
- if (response_code == 0 || response_code >= 300) {
- const double delay =
- GetNewRetryDelay(static_cast<int>(response_code), oldDelay,
- body.c_str(), handle->m_szCurlErrBuf);
- if (delay != 0 && delay < MAX_RETRY_DELAY_MS) {
- pj_log(ctx, PJ_LOG_TRACE,
- "Got a HTTP %ld error. Retrying in %d ms", response_code,
- static_cast<int>(delay));
- proj_sleep_ms(static_cast<int>(delay));
- oldDelay = delay;
- } else {
- if (out_error_string) {
- if (handle->m_szCurlErrBuf[0]) {
- snprintf(out_error_string, error_string_max_size, "%s",
- handle->m_szCurlErrBuf);
- } else {
- snprintf(out_error_string, error_string_max_size,
- "HTTP error %ld: %s", response_code,
- body.c_str());
- }
- }
- return 0;
- }
- } else {
- break;
- }
- }
- if (out_error_string && error_string_max_size) {
- out_error_string[0] = '\0';
- }
-
- if (!body.empty()) {
- memcpy(buffer, body.data(), std::min(size_to_read, body.size()));
+bool FileManager::rename(PJ_CONTEXT *ctx, const char *oldPath,
+ const char *newPath) {
+ if (ctx->fileApi.rename_cbk) {
+ return ctx->fileApi.rename_cbk(ctx, oldPath, newPath,
+ ctx->fileApi.user_data) != 0;
}
- handle->m_headers = std::move(headers);
- return std::min(size_to_read, body.size());
-}
-
-// ---------------------------------------------------------------------------
-
-static const char *pj_curl_get_header_value(PJ_CONTEXT *,
- PROJ_NETWORK_HANDLE *raw_handle,
- const char *header_name, void *) {
- auto handle = reinterpret_cast<CurlFileHandle *>(raw_handle);
- auto pos = ci_find(handle->m_headers, header_name);
- if (pos == std::string::npos)
- return nullptr;
- pos += strlen(header_name);
- const char *c_str = handle->m_headers.c_str();
- if (c_str[pos] == ':')
- pos++;
- while (c_str[pos] == ' ')
- pos++;
- auto posEnd = pos;
- while (c_str[posEnd] != '\r' && c_str[posEnd] != '\n' &&
- c_str[posEnd] != '\0')
- posEnd++;
- handle->m_lastval = handle->m_headers.substr(pos, posEnd - pos);
- return handle->m_lastval.c_str();
-}
-
-#else
-
-// ---------------------------------------------------------------------------
-
-static PROJ_NETWORK_HANDLE *
-no_op_network_open(PJ_CONTEXT *, const char * /* url */,
- unsigned long long, /* offset */
- size_t, /* size to read */
- void *, /* buffer to update with bytes read*/
- size_t *, /* output: size actually read */
- size_t error_string_max_size, char *out_error_string,
- void * /*user_data*/) {
- if (out_error_string) {
- snprintf(out_error_string, error_string_max_size, "%s",
- "Network functionality not available");
+#ifdef _WIN32
+ try {
+ return _wrename(UTF8ToWString(oldPath).c_str(),
+ UTF8ToWString(newPath).c_str()) == 0;
+ } catch (const std::exception &e) {
+ pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
+ return false;
}
- return nullptr;
-}
-
-// ---------------------------------------------------------------------------
-
-static void no_op_network_close(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *,
- void * /*user_data*/) {}
-
-#endif
-
-// ---------------------------------------------------------------------------
-
-void FileManager::fillDefaultNetworkInterface(PJ_CONTEXT *ctx) {
-#ifdef CURL_ENABLED
- ctx->networking.open = CurlFileHandle::open;
- ctx->networking.close = pj_curl_close;
- ctx->networking.read_range = pj_curl_read_range;
- ctx->networking.get_header_value = pj_curl_get_header_value;
#else
- ctx->networking.open = no_op_network_open;
- ctx->networking.close = no_op_network_close;
+ (void)ctx;
+ return ::rename(oldPath, newPath) == 0;
#endif
}
// ---------------------------------------------------------------------------
-void FileManager::clearMemoryCache() {
- gNetworkChunkCache.clearMemoryCache();
- gNetworkFileProperties.clearMemoryCache();
+std::string FileManager::getProjLibEnvVar(PJ_CONTEXT *ctx) {
+ if (!ctx->env_var_proj_lib.empty()) {
+ return ctx->env_var_proj_lib;
+ }
+ (void)ctx;
+ std::string str;
+ const char *envvar = getenv("PROJ_LIB");
+ if (!envvar)
+ return str;
+ str = envvar;
+#ifdef _WIN32
+ // Assume this is UTF-8. If not try to convert from ANSI page
+ bool looksLikeUTF8 = false;
+ try {
+ UTF8ToWString(envvar);
+ looksLikeUTF8 = true;
+ } catch (const std::exception &) {
+ }
+ if (!looksLikeUTF8 || !exists(ctx, envvar)) {
+ str = Win32Recode(envvar, CP_ACP, CP_UTF8);
+ if (str.empty() || !exists(ctx, str.c_str()))
+ str = envvar;
+ }
+#endif
+ ctx->env_var_proj_lib = str;
+ return str;
}
-// ---------------------------------------------------------------------------
-
NS_PROJ_END
//! @endcond
// ---------------------------------------------------------------------------
-/** Define a custom set of callbacks for network access.
+/** Set a file API
+ *
+ * All callbacks should be provided (non NULL pointers). If read-only usage
+ * is intended, then the callbacks might have a dummy implementation.
*
- * All callbacks should be provided (non NULL pointers).
+ * \note Those callbacks will not be used for SQLite3 database access. If
+ * custom I/O is desired for that, then proj_context_set_sqlite3_vfs_name()
+ * should be used.
*
* @param ctx PROJ context, or NULL
- * @param open_cbk Callback to open a remote file given its URL
- * @param close_cbk Callback to close a remote file.
- * @param get_header_value_cbk Callback to get HTTP headers
- * @param read_range_cbk Callback to read a range of bytes inside a remote file.
+ * @param fileapi Pointer to file API structure (content will be copied).
* @param user_data Arbitrary pointer provided by the user, and passed to the
* above callbacks. May be NULL.
* @return TRUE in case of success.
+ * @since 7.0
*/
-int proj_context_set_network_callbacks(
- PJ_CONTEXT *ctx, proj_network_open_cbk_type open_cbk,
- proj_network_close_cbk_type close_cbk,
- proj_network_get_header_value_cbk_type get_header_value_cbk,
- proj_network_read_range_type read_range_cbk, void *user_data) {
+int proj_context_set_fileapi(PJ_CONTEXT *ctx, const PROJ_FILE_API *fileapi,
+ void *user_data) {
if (ctx == nullptr) {
ctx = pj_get_default_ctx();
}
- if (!open_cbk || !close_cbk || !get_header_value_cbk || !read_range_cbk) {
+ if (!fileapi) {
return false;
}
- ctx->networking.open = open_cbk;
- ctx->networking.close = close_cbk;
- ctx->networking.get_header_value = get_header_value_cbk;
- ctx->networking.read_range = read_range_cbk;
- ctx->networking.user_data = user_data;
- return true;
-}
-
-// ---------------------------------------------------------------------------
-
-/** Enable or disable network access.
-*
-* This overrides the default endpoint in the PROJ configuration file or with
-* the PROJ_NETWORK environment variable.
-*
-* @param ctx PROJ context, or NULL
-* @param enable TRUE if network access is allowed.
-* @return TRUE if network access is possible. That is either libcurl is
-* available, or an alternate interface has been set.
-*/
-int proj_context_set_enable_network(PJ_CONTEXT *ctx, int enable) {
- if (ctx == nullptr) {
- ctx = pj_get_default_ctx();
- }
- // Load ini file, now so as to override its network settings
- pj_load_ini(ctx);
- ctx->networking.enabled_env_variable_checked = true;
- ctx->networking.enabled = enable != FALSE;
-#ifdef CURL_ENABLED
- return ctx->networking.enabled;
-#else
- return ctx->networking.enabled &&
- ctx->networking.open != NS_PROJ::no_op_network_open;
-#endif
-}
-
-// ---------------------------------------------------------------------------
-
-/** Define the URL endpoint to query for remote grids.
-*
-* This overrides the default endpoint in the PROJ configuration file or with
-* the PROJ_NETWORK_ENDPOINT environment variable.
-*
-* @param ctx PROJ context, or NULL
-* @param url Endpoint URL. Must NOT be NULL.
-*/
-void proj_context_set_url_endpoint(PJ_CONTEXT *ctx, const char *url) {
- if (ctx == nullptr) {
- ctx = pj_get_default_ctx();
- }
- // Load ini file, now so as to override its network settings
- pj_load_ini(ctx);
- ctx->endpoint = url;
-}
-
-// ---------------------------------------------------------------------------
-
-/** Enable or disable the local cache of grid chunks
-*
-* This overrides the setting in the PROJ configuration file.
-*
-* @param ctx PROJ context, or NULL
-* @param enabled TRUE if the cache is enabled.
-*/
-void proj_grid_cache_set_enable(PJ_CONTEXT *ctx, int enabled) {
- if (ctx == nullptr) {
- ctx = pj_get_default_ctx();
- }
- // Load ini file, now so as to override its settings
- pj_load_ini(ctx);
- ctx->gridChunkCache.enabled = enabled != FALSE;
-}
-
-// ---------------------------------------------------------------------------
-
-/** Override, for the considered context, the path and file of the local
-* cache of grid chunks.
-*
-* @param ctx PROJ context, or NULL
-* @param fullname Full name to the cache (encoded in UTF-8). If set to NULL,
-* caching will be disabled.
-*/
-void proj_grid_cache_set_filename(PJ_CONTEXT *ctx, const char *fullname) {
- if (ctx == nullptr) {
- ctx = pj_get_default_ctx();
- }
- // Load ini file, now so as to override its settings
- pj_load_ini(ctx);
- ctx->gridChunkCache.filename = fullname ? fullname : std::string();
-}
-
-// ---------------------------------------------------------------------------
-
-/** Override, for the considered context, the maximum size of the local
-* cache of grid chunks.
-*
-* @param ctx PROJ context, or NULL
-* @param max_size_MB Maximum size, in mega-bytes (1024*1024 bytes), or
-* negative value to set unlimited size.
-*/
-void proj_grid_cache_set_max_size(PJ_CONTEXT *ctx, int max_size_MB) {
- if (ctx == nullptr) {
- ctx = pj_get_default_ctx();
- }
- // Load ini file, now so as to override its settings
- pj_load_ini(ctx);
- ctx->gridChunkCache.max_size =
- max_size_MB < 0 ? -1
- : static_cast<long long>(max_size_MB) * 1024 * 1024;
- if (max_size_MB == 0) {
- // For debug purposes only
- const char *env_var = getenv("PROJ_GRID_CACHE_MAX_SIZE_BYTES");
- if (env_var && env_var[0] != '\0') {
- ctx->gridChunkCache.max_size = atoi(env_var);
- }
+ if (fileapi->version != 1) {
+ return false;
}
-}
-
-// ---------------------------------------------------------------------------
-
-/** Override, for the considered context, the time-to-live delay for
-* re-checking if the cached properties of files are still up-to-date.
-*
-* @param ctx PROJ context, or NULL
-* @param ttl_seconds Delay in seconds. Use negative value for no expiration.
-*/
-void proj_grid_cache_set_ttl(PJ_CONTEXT *ctx, int ttl_seconds) {
- if (ctx == nullptr) {
- ctx = pj_get_default_ctx();
+ if (!fileapi->open_cbk || !fileapi->close_cbk || !fileapi->read_cbk ||
+ !fileapi->write_cbk || !fileapi->seek_cbk || !fileapi->tell_cbk ||
+ !fileapi->exists_cbk || !fileapi->mkdir_cbk || !fileapi->unlink_cbk ||
+ !fileapi->rename_cbk) {
+ return false;
}
- // Load ini file, now so as to override its settings
- pj_load_ini(ctx);
- ctx->gridChunkCache.ttl = ttl_seconds;
+ ctx->fileApi.open_cbk = fileapi->open_cbk;
+ ctx->fileApi.close_cbk = fileapi->close_cbk;
+ ctx->fileApi.read_cbk = fileapi->read_cbk;
+ ctx->fileApi.write_cbk = fileapi->write_cbk;
+ ctx->fileApi.seek_cbk = fileapi->seek_cbk;
+ ctx->fileApi.tell_cbk = fileapi->tell_cbk;
+ ctx->fileApi.exists_cbk = fileapi->exists_cbk;
+ ctx->fileApi.mkdir_cbk = fileapi->mkdir_cbk;
+ ctx->fileApi.unlink_cbk = fileapi->unlink_cbk;
+ ctx->fileApi.rename_cbk = fileapi->rename_cbk;
+ ctx->fileApi.user_data = user_data;
+ return true;
}
// ---------------------------------------------------------------------------
-/** Clear the local cache of grid chunks.
-*
-* @param ctx PROJ context, or NULL
-*/
-void proj_grid_cache_clear(PJ_CONTEXT *ctx) {
+/** Set the name of a custom SQLite3 VFS.
+ *
+ * This should be a valid SQLite3 VFS name, such as the one passed to the
+ * sqlite3_vfs_register(). See https://www.sqlite.org/vfs.html
+ *
+ * It will be used to read proj.db or create&access the cache.db file in the
+ * PROJ user writable directory.
+ *
+ * @param ctx PROJ context, or NULL
+ * @param name SQLite3 VFS name. If NULL is passed, default implementation by
+ * SQLite will be used.
+ * @since 7.0
+ */
+void proj_context_set_sqlite3_vfs_name(PJ_CONTEXT *ctx, const char *name) {
if (ctx == nullptr) {
ctx = pj_get_default_ctx();
}
- NS_PROJ::gNetworkChunkCache.clearDiskChunkCache(ctx);
+ ctx->custom_sqlite3_vfs_name = name ? name : std::string();
}
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
-bool pj_context_is_network_enabled(PJ_CONTEXT *ctx) {
- if (ctx == nullptr) {
- ctx = pj_get_default_ctx();
- }
- if (ctx->networking.enabled_env_variable_checked) {
- return ctx->networking.enabled;
- }
- const char *enabled = getenv("PROJ_NETWORK");
- if (enabled && enabled[0] != '\0') {
- ctx->networking.enabled = ci_equal(enabled, "ON") ||
- ci_equal(enabled, "YES") ||
- ci_equal(enabled, "TRUE");
- }
- pj_load_ini(ctx);
- ctx->networking.enabled_env_variable_checked = true;
- return ctx->networking.enabled;
-}
-
// ---------------------------------------------------------------------------
-#ifdef _WIN32
-
-static std::wstring UTF8ToWString(const std::string &str) {
- using convert_typeX = std::codecvt_utf8<wchar_t>;
- std::wstring_convert<convert_typeX, wchar_t> converterX;
-
- return converterX.from_bytes(str);
-}
-
-// ---------------------------------------------------------------------------
-
-static std::string WStringToUTF8(const std::wstring &wstr) {
- using convert_typeX = std::codecvt_utf8<wchar_t>;
- std::wstring_convert<convert_typeX, wchar_t> converterX;
-
- return converterX.to_bytes(wstr);
-}
-#endif
-
-// ---------------------------------------------------------------------------
-
-static void CreateDirectory(const std::string &path) {
-#ifdef _WIN32
- struct __stat64 buf;
- const auto wpath = UTF8ToWString(path);
- if (_wstat64(wpath.c_str(), &buf) == 0)
- return;
- auto pos = path.find_last_of("/\\");
- if (pos == 0 || pos == std::string::npos)
- return;
- CreateDirectory(path.substr(0, pos));
- _wmkdir(wpath.c_str());
-#else
- struct stat buf;
- if (stat(path.c_str(), &buf) == 0)
+static void CreateDirectoryRecursively(PJ_CONTEXT *ctx,
+ const std::string &path) {
+ if (NS_PROJ::FileManager::exists(ctx, path.c_str()))
return;
auto pos = path.find_last_of("/\\");
if (pos == 0 || pos == std::string::npos)
return;
- CreateDirectory(path.substr(0, pos));
- mkdir(path.c_str(), 0755);
-#endif
+ CreateDirectoryRecursively(ctx, path.substr(0, pos));
+ NS_PROJ::FileManager::mkdir(ctx, path.c_str());
}
// ---------------------------------------------------------------------------
@@ -2320,7 +1137,7 @@ std::string pj_context_get_user_writable_directory(PJ_CONTEXT *ctx,
if (SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0,
&wPath[0]) == S_OK) {
wPath.resize(wcslen(wPath.data()));
- path = WStringToUTF8(wPath);
+ path = NS_PROJ::WStringToUTF8(wPath);
} else {
const char *local_app_data = getenv("LOCALAPPDATA");
if (!local_app_data) {
@@ -2352,25 +1169,13 @@ std::string pj_context_get_user_writable_directory(PJ_CONTEXT *ctx,
ctx->user_writable_directory = path;
}
if (create) {
- CreateDirectory(ctx->user_writable_directory);
+ CreateDirectoryRecursively(ctx, ctx->user_writable_directory);
}
return ctx->user_writable_directory;
}
// ---------------------------------------------------------------------------
-std::string pj_context_get_grid_cache_filename(PJ_CONTEXT *ctx) {
- pj_load_ini(ctx);
- if (!ctx->gridChunkCache.filename.empty()) {
- return ctx->gridChunkCache.filename;
- }
- const std::string path(pj_context_get_user_writable_directory(ctx, true));
- ctx->gridChunkCache.filename = path + "/cache.db";
- return ctx->gridChunkCache.filename;
-}
-
-// ---------------------------------------------------------------------------
-
#ifdef WIN32
static const char dir_chars[] = "/\\";
#else
@@ -2388,9 +1193,268 @@ static bool is_rel_or_absolute_filename(const char *name) {
(name[0] != '\0' && name[1] == ':' && strchr(dir_chars, name[2]));
}
-static std::string build_url(PJ_CONTEXT *ctx, const char *name) {
- if (!is_tilde_slash(name) && !is_rel_or_absolute_filename(name) &&
- !starts_with(name, "http://") && !starts_with(name, "https://")) {
+// ---------------------------------------------------------------------------
+
+#ifdef _WIN32
+static const char *get_path_from_win32_projlib(PJ_CONTEXT *ctx,
+ const char *name,
+ std::string &out) {
+ /* Check if proj.db lieves in a share/proj dir parallel to bin/proj.dll */
+ /* Based in
+ * https://stackoverflow.com/questions/9112893/how-to-get-path-to-executable-in-c-running-on-windows
+ */
+
+ DWORD path_size = 1024;
+
+ std::wstring wout;
+ for (;;) {
+ wout.clear();
+ wout.resize(path_size);
+ DWORD result = GetModuleFileNameW(nullptr, &wout[0], path_size - 1);
+ DWORD last_error = GetLastError();
+
+ if (result == 0) {
+ return nullptr;
+ } else if (result == path_size - 1) {
+ if (ERROR_INSUFFICIENT_BUFFER != last_error) {
+ return nullptr;
+ }
+ path_size = path_size * 2;
+ } else {
+ break;
+ }
+ }
+ // Now remove the program's name. It was (example)
+ // "C:\programs\gmt6\bin\gdal_translate.exe"
+ wout.resize(wcslen(wout.c_str()));
+ out = NS_PROJ::WStringToUTF8(wout);
+ size_t k = out.size();
+ while (k > 0 && out[--k] != '\\') {
+ }
+ out.resize(k);
+
+ out += "/../share/proj/";
+ out += name;
+
+ return NS_PROJ::FileManager::exists(ctx, out.c_str()) ? out.c_str()
+ : nullptr;
+}
+#endif
+
+/************************************************************************/
+/* pj_open_lib_internal() */
+/************************************************************************/
+
+#ifdef WIN32
+static const char dirSeparator = ';';
+#else
+static const char dirSeparator = ':';
+#endif
+
+static const char *proj_lib_name =
+#ifdef PROJ_LIB
+ PROJ_LIB;
+#else
+ nullptr;
+#endif
+
+static bool ignoreUserWritableDirectory() {
+ // Env var mostly for testing purposes and being independent from
+ // an existing installation
+ const char *envVarIgnoreUserWritableDirectory =
+ getenv("PROJ_IGNORE_USER_WRITABLE_DIRECTORY");
+ return envVarIgnoreUserWritableDirectory != nullptr &&
+ envVarIgnoreUserWritableDirectory[0] != '\0';
+}
+
+static void *
+pj_open_lib_internal(projCtx ctx, const char *name, const char *mode,
+ void *(*open_file)(projCtx, const char *, const char *),
+ char *out_full_filename, size_t out_full_filename_size) {
+ try {
+ std::string fname;
+ const char *sysname = nullptr;
+ void *fid = nullptr;
+ std::string projLib;
+
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+
+ if (out_full_filename != nullptr && out_full_filename_size > 0)
+ out_full_filename[0] = '\0';
+
+ /* check if ~/name */
+ if (is_tilde_slash(name))
+ if ((sysname = getenv("HOME")) != nullptr) {
+ fname = sysname;
+ fname += DIR_CHAR;
+ fname += name;
+ sysname = fname.c_str();
+ } else
+ return nullptr;
+
+ /* or fixed path: /name, ./name or ../name */
+ else if (is_rel_or_absolute_filename(name)) {
+ sysname = name;
+#ifdef _WIN32
+ try {
+ NS_PROJ::UTF8ToWString(name);
+ } catch (const std::exception &) {
+ fname = NS_PROJ::Win32Recode(name, CP_ACP, CP_UTF8);
+ sysname = fname.c_str();
+ }
+#endif
+ }
+
+ else if (starts_with(name, "http://") || starts_with(name, "https://"))
+ sysname = name;
+
+ /* or try to use application provided file finder */
+ else if (ctx->file_finder != nullptr &&
+ (sysname = ctx->file_finder(
+ ctx, name, ctx->file_finder_user_data)) != nullptr)
+ ;
+
+ else if (ctx->file_finder_legacy != nullptr &&
+ (sysname = ctx->file_finder_legacy(name)) != nullptr)
+ ;
+
+ /* The user has search paths set */
+ else if (!ctx->search_paths.empty()) {
+ for (const auto &path : ctx->search_paths) {
+ try {
+ fname = path;
+ fname += DIR_CHAR;
+ fname += name;
+ sysname = fname.c_str();
+ fid = open_file(ctx, sysname, mode);
+ } catch (const std::exception &) {
+ }
+ if (fid)
+ break;
+ }
+ }
+
+ else if (!ignoreUserWritableDirectory() &&
+ (fid = open_file(
+ ctx, (pj_context_get_user_writable_directory(ctx, false) +
+ DIR_CHAR + name)
+ .c_str(),
+ mode)) != nullptr) {
+ fname = pj_context_get_user_writable_directory(ctx, false);
+ fname += DIR_CHAR;
+ fname += name;
+ sysname = fname.c_str();
+ }
+
+ /* if is environment PROJ_LIB defined */
+ else if (!(projLib = NS_PROJ::FileManager::getProjLibEnvVar(ctx))
+ .empty()) {
+ auto paths = NS_PROJ::internal::split(projLib, dirSeparator);
+ for (const auto &path : paths) {
+ fname = path;
+ fname += DIR_CHAR;
+ fname += name;
+ sysname = fname.c_str();
+ fid = open_file(ctx, sysname, mode);
+ if (fid)
+ break;
+ }
+#ifdef _WIN32
+ /* check if it lives in a ../share/proj dir of the proj dll */
+ } else if ((sysname = get_path_from_win32_projlib(ctx, name, fname)) !=
+ nullptr) {
+#endif
+ /* or hardcoded path */
+ } else if ((sysname = proj_lib_name) != nullptr) {
+ fname = sysname;
+ fname += DIR_CHAR;
+ fname += name;
+ sysname = fname.c_str();
+ /* just try it bare bones */
+ } else {
+ sysname = name;
+ }
+
+ assert(sysname); // to make Coverity Scan happy
+ if (fid != nullptr ||
+ (fid = open_file(ctx, sysname, mode)) != nullptr) {
+ if (out_full_filename != nullptr && out_full_filename_size > 0) {
+ // cppcheck-suppress nullPointer
+ strncpy(out_full_filename, sysname, out_full_filename_size);
+ out_full_filename[out_full_filename_size - 1] = '\0';
+ }
+ errno = 0;
+ }
+
+ if (ctx->last_errno == 0 && errno != 0)
+ pj_ctx_set_errno(ctx, errno);
+
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "pj_open_lib(%s): call fopen(%s) - %s",
+ name, sysname, fid == nullptr ? "failed" : "succeeded");
+
+ return (fid);
+ } catch (const std::exception &) {
+
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "pj_open_lib(%s): out of memory", name);
+
+ return nullptr;
+ }
+}
+
+/************************************************************************/
+/* pj_open_file_with_manager() */
+/************************************************************************/
+
+static void *pj_open_file_with_manager(projCtx ctx, const char *name,
+ const char * /* mode */) {
+ return NS_PROJ::FileManager::open(ctx, name, NS_PROJ::FileAccess::READ_ONLY)
+ .release();
+}
+
+/************************************************************************/
+/* FileManager::open_resource_file() */
+/************************************************************************/
+
+std::unique_ptr<NS_PROJ::File>
+NS_PROJ::FileManager::open_resource_file(projCtx ctx, const char *name) {
+
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+
+ auto file = std::unique_ptr<NS_PROJ::File>(
+ reinterpret_cast<NS_PROJ::File *>(pj_open_lib_internal(
+ ctx, name, "rb", pj_open_file_with_manager, nullptr, 0)));
+
+ // Retry with a .tif extension if the file name doesn't end with .tif
+ if (file == nullptr && !is_tilde_slash(name) &&
+ !is_rel_or_absolute_filename(name) && !starts_with(name, "http://") &&
+ !starts_with(name, "https://") && strcmp(name, "proj.db") != 0 &&
+ strstr(name, ".tif") == nullptr) {
+ std::string filename(name);
+ auto pos = filename.rfind('.');
+ if (pos + 4 == filename.size()) {
+ filename = filename.substr(0, pos) + ".tif";
+ file.reset(reinterpret_cast<NS_PROJ::File *>(
+ pj_open_lib_internal(ctx, filename.c_str(), "rb",
+ pj_open_file_with_manager, nullptr, 0)));
+ } else {
+ // For example for resource files like 'alaska'
+ filename += ".tif";
+ file.reset(reinterpret_cast<NS_PROJ::File *>(
+ pj_open_lib_internal(ctx, filename.c_str(), "rb",
+ pj_open_file_with_manager, nullptr, 0)));
+ }
+ if (file) {
+ pj_ctx_set_errno(ctx, 0);
+ }
+ }
+
+ if (file == nullptr && !is_tilde_slash(name) &&
+ !is_rel_or_absolute_filename(name) && !starts_with(name, "http://") &&
+ !starts_with(name, "https://") && pj_context_is_network_enabled(ctx)) {
std::string remote_file(pj_context_get_url_endpoint(ctx));
if (!remote_file.empty()) {
if (remote_file.back() != '/') {
@@ -2400,369 +1464,272 @@ static std::string build_url(PJ_CONTEXT *ctx, const char *name) {
auto pos = remote_file.rfind('.');
if (pos + 4 == remote_file.size()) {
remote_file = remote_file.substr(0, pos) + ".tif";
+ file = open(ctx, remote_file.c_str(),
+ NS_PROJ::FileAccess::READ_ONLY);
+ if (file) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Using %s",
+ remote_file.c_str());
+ pj_ctx_set_errno(ctx, 0);
+ }
} else {
// For example for resource files like 'alaska'
- remote_file += ".tif";
+ auto remote_file_tif = remote_file + ".tif";
+ file = open(ctx, remote_file_tif.c_str(),
+ NS_PROJ::FileAccess::READ_ONLY);
+ if (file) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Using %s",
+ remote_file_tif.c_str());
+ pj_ctx_set_errno(ctx, 0);
+ } else {
+ // Init files
+ file = open(ctx, remote_file.c_str(),
+ NS_PROJ::FileAccess::READ_ONLY);
+ if (file) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Using %s",
+ remote_file.c_str());
+ pj_ctx_set_errno(ctx, 0);
+ }
+ }
}
}
- return remote_file;
}
- return name;
+ return file;
}
-//! @endcond
+/************************************************************************/
+/* pj_open_lib() */
+/************************************************************************/
-// ---------------------------------------------------------------------------
+#ifndef REMOVE_LEGACY_SUPPORT
-/** Return if a file must be downloaded or is already available in the
- * PROJ user-writable directory.
- *
- * The file will be determinted to have to be downloaded if it does not exist
- * yet in the user-writable directory, or if it is determined that a more recent
- * version exists. To determine if a more recent version exists, PROJ will
- * use the "downloaded_file_properties" table of its grid cache database.
- * Consequently files manually placed in the user-writable
- * directory without using this function would be considered as
- * non-existing/obsolete and would be unconditionnaly downloaded again.
- *
- * This function can only be used if networking is enabled, and either
- * the default curl network API or a custom one have been installed.
+// Used by following legacy function
+static void *pj_ctx_fopen_adapter(projCtx ctx, const char *name,
+ const char *mode) {
+ return pj_ctx_fopen(ctx, name, mode);
+}
+
+// Legacy function
+PAFile pj_open_lib(projCtx ctx, const char *name, const char *mode) {
+ return (PAFile)pj_open_lib_internal(ctx, name, mode, pj_ctx_fopen_adapter,
+ nullptr, 0);
+}
+
+#endif // REMOVE_LEGACY_SUPPORT
+
+/************************************************************************/
+/* pj_find_file() */
+/************************************************************************/
+
+/** Returns the full filename corresponding to a proj resource file specified
+ * as a short filename.
*
- * @param ctx PROJ context, or NULL
- * @param url_or_filename URL or filename (without directory component)
- * @param ignore_ttl_setting If set to FALSE, PROJ will only check the
- * recentness of an already downloaded file, if
- * the delay between the last time it has been
- * verified and the current time exceeds the TTL
- * setting. This can save network accesses.
- * If set to TRUE, PROJ will unconditionnally
- * check from the server the recentness of the file.
- * @return TRUE if the file must be downloaded with proj_download_file()
- * @since 7.0
+ * @param ctx context.
+ * @param short_filename short filename (e.g. egm96_15.gtx). Must not be NULL.
+ * @param out_full_filename output buffer, of size out_full_filename_size, that
+ * will receive the full filename on success.
+ * Will be zero-terminated.
+ * @param out_full_filename_size size of out_full_filename.
+ * @return 1 if the file was found, 0 otherwise.
*/
-
-int proj_is_download_needed(PJ_CONTEXT *ctx, const char *url_or_filename,
- int ignore_ttl_setting) {
- if (ctx == nullptr) {
- ctx = pj_get_default_ctx();
- }
- if (!pj_context_is_network_enabled(ctx)) {
- pj_log(ctx, PJ_LOG_ERROR, "Networking capabilities are not enabled");
- return false;
+int pj_find_file(projCtx ctx, const char *short_filename,
+ char *out_full_filename, size_t out_full_filename_size) {
+ auto f = reinterpret_cast<NS_PROJ::File *>(pj_open_lib_internal(
+ ctx, short_filename, "rb", pj_open_file_with_manager, out_full_filename,
+ out_full_filename_size));
+ if (f != nullptr) {
+ delete f;
+ return 1;
}
+ return 0;
+}
- const auto url(build_url(ctx, url_or_filename));
- const char *filename = strrchr(url.c_str(), '/');
- if (filename == nullptr)
- return false;
- const auto localFilename(
- pj_context_get_user_writable_directory(ctx, false) + filename);
+/************************************************************************/
+/* pj_context_get_url_endpoint() */
+/************************************************************************/
- auto f = NS_PROJ::FileManager::open(ctx, localFilename.c_str());
- if (!f) {
- return true;
+std::string pj_context_get_url_endpoint(PJ_CONTEXT *ctx) {
+ if (!ctx->endpoint.empty()) {
+ return ctx->endpoint;
}
- f.reset();
+ pj_load_ini(ctx);
+ return ctx->endpoint;
+}
- auto diskCache = NS_PROJ::DiskChunkCache::open(ctx);
- if (!diskCache)
- return false;
- auto stmt =
- diskCache->prepare("SELECT lastChecked, fileSize, lastModified, etag "
- "FROM downloaded_file_properties WHERE url = ?");
- if (!stmt)
- return true;
- stmt->bindText(url.c_str());
- if (stmt->execute() != SQLITE_ROW) {
- return true;
+/************************************************************************/
+/* trim() */
+/************************************************************************/
+
+static std::string trim(const std::string &s) {
+ const auto first = s.find_first_not_of(' ');
+ const auto last = s.find_last_not_of(' ');
+ if (first == std::string::npos || last == std::string::npos) {
+ return std::string();
}
+ return s.substr(first, last - first + 1);
+}
- NS_PROJ::FileProperties cachedProps;
- cachedProps.lastChecked = stmt->getInt64();
- cachedProps.size = stmt->getInt64();
- const char *lastModified = stmt->getText();
- cachedProps.lastModified = lastModified ? lastModified : std::string();
- const char *etag = stmt->getText();
- cachedProps.etag = etag ? etag : std::string();
-
- if (!ignore_ttl_setting) {
- const auto ttl = NS_PROJ::pj_context_get_grid_cache_ttl(ctx);
- if (ttl > 0) {
- time_t curTime;
- time(&curTime);
- if (curTime > cachedProps.lastChecked + ttl) {
-
- unsigned char dummy;
- size_t size_read = 0;
- std::string errorBuffer;
- errorBuffer.resize(1024);
- auto handle = ctx->networking.open(
- ctx, url.c_str(), 0, 1, &dummy, &size_read,
- errorBuffer.size(), &errorBuffer[0],
- ctx->networking.user_data);
- if (!handle) {
- errorBuffer.resize(strlen(errorBuffer.data()));
- pj_log(ctx, PJ_LOG_ERROR, "Cannot open %s: %s", url.c_str(),
- errorBuffer.c_str());
- return false;
- }
- NS_PROJ::FileProperties props;
- if (!NS_PROJ::NetworkFile::get_props_from_headers(ctx, handle,
- props)) {
- ctx->networking.close(ctx, handle,
- ctx->networking.user_data);
- return false;
- }
- ctx->networking.close(ctx, handle, ctx->networking.user_data);
+/************************************************************************/
+/* pj_load_ini() */
+/************************************************************************/
- if (props.size != cachedProps.size ||
- props.lastModified != cachedProps.lastModified ||
- props.etag != cachedProps.etag) {
- return true;
- }
+void pj_load_ini(projCtx ctx) {
+ if (ctx->iniFileLoaded)
+ return;
+
+ const char *endpoint_from_env = getenv("PROJ_NETWORK_ENDPOINT");
+ if (endpoint_from_env && endpoint_from_env[0] != '\0') {
+ ctx->endpoint = endpoint_from_env;
+ }
+
+ ctx->iniFileLoaded = true;
+ auto file = std::unique_ptr<NS_PROJ::File>(
+ reinterpret_cast<NS_PROJ::File *>(pj_open_lib_internal(
+ ctx, "proj.ini", "rb", pj_open_file_with_manager, nullptr, 0)));
+ if (!file)
+ return;
+ file->seek(0, SEEK_END);
+ const auto filesize = file->tell();
+ if (filesize == 0 || filesize > 100 * 1024U)
+ return;
+ file->seek(0, SEEK_SET);
+ std::string content;
+ content.resize(static_cast<size_t>(filesize));
+ const auto nread = file->read(&content[0], content.size());
+ if (nread != content.size())
+ return;
+ content += '\n';
+ size_t pos = 0;
+ while (pos != std::string::npos) {
+ const auto eol = content.find_first_of("\r\n", pos);
+ if (eol == std::string::npos) {
+ break;
+ }
- stmt = diskCache->prepare(
- "UPDATE downloaded_file_properties SET lastChecked = ? "
- "WHERE url = ?");
- if (!stmt)
- return false;
- stmt->bindInt64(curTime);
- stmt->bindText(url.c_str());
- if (stmt->execute() != SQLITE_DONE) {
- auto hDB = diskCache->handle();
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- return false;
+ const auto equal = content.find('=', pos);
+ if (equal < eol) {
+ const auto key = trim(content.substr(pos, equal - pos));
+ const auto value =
+ trim(content.substr(equal + 1, eol - (equal + 1)));
+ if (ctx->endpoint.empty() && key == "cdn_endpoint") {
+ ctx->endpoint = value;
+ } else if (key == "network") {
+ const char *enabled = getenv("PROJ_NETWORK");
+ if (enabled == nullptr || enabled[0] == '\0') {
+ ctx->networking.enabled = ci_equal(value, "ON") ||
+ ci_equal(value, "YES") ||
+ ci_equal(value, "TRUE");
}
+ } else if (key == "cache_enabled") {
+ ctx->gridChunkCache.enabled = ci_equal(value, "ON") ||
+ ci_equal(value, "YES") ||
+ ci_equal(value, "TRUE");
+ } else if (key == "cache_size_MB") {
+ const int val = atoi(value.c_str());
+ ctx->gridChunkCache.max_size =
+ val > 0 ? static_cast<long long>(val) * 1024 * 1024 : -1;
+ } else if (key == "cache_ttl_sec") {
+ ctx->gridChunkCache.ttl = atoi(value.c_str());
}
}
+
+ pos = content.find_first_not_of("\r\n", eol);
}
+}
+
+//! @endcond
+
+/************************************************************************/
+/* pj_set_finder() */
+/************************************************************************/
- return false;
+void pj_set_finder(const char *(*new_finder)(const char *))
+
+{
+ auto ctx = pj_get_default_ctx();
+ if (ctx) {
+ ctx->file_finder_legacy = new_finder;
+ }
}
-// ---------------------------------------------------------------------------
+/************************************************************************/
+/* proj_context_set_file_finder() */
+/************************************************************************/
-/** Download a file in the PROJ user-writable directory.
+/** \brief Assign a file finder callback to a context.
*
- * The file will only be downloaded if it does not exist yet in the
- * user-writable directory, or if it is determined that a more recent
- * version exists. To determine if a more recent version exists, PROJ will
- * use the "downloaded_file_properties" table of its grid cache database.
- * Consequently files manually placed in the user-writable
- * directory without using this function would be considered as
- * non-existing/obsolete and would be unconditionnaly downloaded again.
+ * This callback will be used whenever PROJ must open one of its resource files
+ * (proj.db database, grids, etc...)
*
- * This function can only be used if networking is enabled, and either
- * the default curl network API or a custom one have been installed.
+ * The callback will be called with the context currently in use at the moment
+ * where it is used (not necessarily the one provided during this call), and
+ * with the provided user_data (which may be NULL).
+ * The user_data must remain valid during the whole lifetime of the context.
*
- * @param ctx PROJ context, or NULL
- * @param url_or_filename URL or filename (without directory component)
- * @param ignore_ttl_setting If set to FALSE, PROJ will only check the
- * recentness of an already downloaded file, if
- * the delay between the last time it has been
- * verified and the current time exceeds the TTL
- * setting. This can save network accesses.
- * If set to TRUE, PROJ will unconditionnally
- * check from the server the recentness of the file.
- * @param progress_cbk Progress callback, or NULL.
- * The passed percentage is in the [0, 1] range.
- * The progress callback must return TRUE
- * if download must be continued.
- * @param user_data User data to provide to the progress callback, or NULL
- * @return TRUE if the download was successful (or not needed)
- * @since 7.0
+ * A finder set on the default context will be inherited by contexts created
+ * later.
+ *
+ * @param ctx PROJ context, or NULL for the default context.
+ * @param finder Finder callback. May be NULL
+ * @param user_data User data provided to the finder callback. May be NULL.
+ *
+ * @since PROJ 6.0
*/
-
-int proj_download_file(PJ_CONTEXT *ctx, const char *url_or_filename,
- int ignore_ttl_setting,
- int (*progress_cbk)(PJ_CONTEXT *, double pct,
- void *user_data),
- void *user_data) {
- if (ctx == nullptr) {
+void proj_context_set_file_finder(PJ_CONTEXT *ctx, proj_file_finder finder,
+ void *user_data) {
+ if (!ctx)
ctx = pj_get_default_ctx();
- }
- if (!pj_context_is_network_enabled(ctx)) {
- pj_log(ctx, PJ_LOG_ERROR, "Networking capabilities are not enabled");
- return false;
- }
- if (!proj_is_download_needed(ctx, url_or_filename, ignore_ttl_setting)) {
- return true;
- }
-
- const auto url(build_url(ctx, url_or_filename));
- const char *filename = strrchr(url.c_str(), '/');
- if (filename == nullptr)
- return false;
- const auto localFilename(pj_context_get_user_writable_directory(ctx, true) +
- filename);
-
-#ifdef _WIN32
- const int nPID = GetCurrentProcessId();
-#else
- const int nPID = getpid();
-#endif
- char szUniqueSuffix[128];
- snprintf(szUniqueSuffix, sizeof(szUniqueSuffix), "%d_%p", nPID, &url);
- const auto localFilenameTmp(localFilename + szUniqueSuffix);
- FILE *f = fopen(localFilenameTmp.c_str(), "wb");
- if (!f) {
- pj_log(ctx, PJ_LOG_ERROR, "Cannot create %s", localFilenameTmp.c_str());
- return false;
- }
-
- constexpr size_t FULL_FILE_CHUNK_SIZE = 1024 * 1024;
- std::vector<unsigned char> buffer(FULL_FILE_CHUNK_SIZE);
- // For testing purposes only
- const char *env_var_PROJ_FULL_FILE_CHUNK_SIZE =
- getenv("PROJ_FULL_FILE_CHUNK_SIZE");
- if (env_var_PROJ_FULL_FILE_CHUNK_SIZE &&
- env_var_PROJ_FULL_FILE_CHUNK_SIZE[0] != '\0') {
- buffer.resize(atoi(env_var_PROJ_FULL_FILE_CHUNK_SIZE));
- }
- size_t size_read = 0;
- std::string errorBuffer;
- errorBuffer.resize(1024);
- auto handle = ctx->networking.open(
- ctx, url.c_str(), 0, buffer.size(), &buffer[0], &size_read,
- errorBuffer.size(), &errorBuffer[0], ctx->networking.user_data);
- if (!handle) {
- errorBuffer.resize(strlen(errorBuffer.data()));
- pj_log(ctx, PJ_LOG_ERROR, "Cannot open %s: %s", url.c_str(),
- errorBuffer.c_str());
- fclose(f);
- unlink(localFilenameTmp.c_str());
- return false;
- }
-
- time_t curTime;
- time(&curTime);
- NS_PROJ::FileProperties props;
- if (!NS_PROJ::NetworkFile::get_props_from_headers(ctx, handle, props)) {
- ctx->networking.close(ctx, handle, ctx->networking.user_data);
- fclose(f);
- unlink(localFilenameTmp.c_str());
- return false;
- }
+ if (!ctx)
+ return;
+ ctx->file_finder = finder;
+ ctx->file_finder_user_data = user_data;
+}
- if (size_read <
- std::min(static_cast<unsigned long long>(buffer.size()), props.size)) {
- pj_log(ctx, PJ_LOG_ERROR, "Did not get as many bytes as expected");
- ctx->networking.close(ctx, handle, ctx->networking.user_data);
- fclose(f);
- unlink(localFilenameTmp.c_str());
- return false;
- }
- if (fwrite(buffer.data(), size_read, 1, f) != 1) {
- pj_log(ctx, PJ_LOG_ERROR, "Write error");
- ctx->networking.close(ctx, handle, ctx->networking.user_data);
- fclose(f);
- unlink(localFilenameTmp.c_str());
- return false;
- }
+/************************************************************************/
+/* proj_context_set_search_paths() */
+/************************************************************************/
- unsigned long long totalDownloaded = size_read;
- while (totalDownloaded < props.size) {
- if (totalDownloaded + buffer.size() > props.size) {
- buffer.resize(static_cast<size_t>(props.size - totalDownloaded));
- }
- errorBuffer.resize(1024);
- size_read = ctx->networking.read_range(
- ctx, handle, totalDownloaded, buffer.size(), &buffer[0],
- errorBuffer.size(), &errorBuffer[0], ctx->networking.user_data);
-
- if (size_read < buffer.size()) {
- pj_log(ctx, PJ_LOG_ERROR, "Did not get as many bytes as expected");
- ctx->networking.close(ctx, handle, ctx->networking.user_data);
- fclose(f);
- unlink(localFilenameTmp.c_str());
- return false;
- }
- if (fwrite(buffer.data(), size_read, 1, f) != 1) {
- pj_log(ctx, PJ_LOG_ERROR, "Write error");
- ctx->networking.close(ctx, handle, ctx->networking.user_data);
- fclose(f);
- unlink(localFilenameTmp.c_str());
- return false;
- }
-
- totalDownloaded += size_read;
- if (progress_cbk &&
- !progress_cbk(ctx, double(totalDownloaded) / props.size,
- user_data)) {
- ctx->networking.close(ctx, handle, ctx->networking.user_data);
- fclose(f);
- unlink(localFilenameTmp.c_str());
- return false;
+/** \brief Sets search paths.
+ *
+ * Those search paths will be used whenever PROJ must open one of its resource
+ * files
+ * (proj.db database, grids, etc...)
+ *
+ * If set on the default context, they will be inherited by contexts created
+ * later.
+ *
+ * Starting with PROJ 7.0, the path(s) should be encoded in UTF-8.
+ *
+ * @param ctx PROJ context, or NULL for the default context.
+ * @param count_paths Number of paths. 0 if paths == NULL.
+ * @param paths Paths. May be NULL.
+ *
+ * @since PROJ 6.0
+ */
+void proj_context_set_search_paths(PJ_CONTEXT *ctx, int count_paths,
+ const char *const *paths) {
+ if (!ctx)
+ ctx = pj_get_default_ctx();
+ if (!ctx)
+ return;
+ try {
+ std::vector<std::string> vector_of_paths;
+ for (int i = 0; i < count_paths; i++) {
+ vector_of_paths.emplace_back(paths[i]);
}
+ ctx->set_search_paths(vector_of_paths);
+ } catch (const std::exception &) {
}
+}
- ctx->networking.close(ctx, handle, ctx->networking.user_data);
- fclose(f);
-
- unlink(localFilename.c_str());
- if (rename(localFilenameTmp.c_str(), localFilename.c_str()) != 0) {
- pj_log(ctx, PJ_LOG_ERROR, "Cannot rename %s to %s",
- localFilenameTmp.c_str(), localFilename.c_str());
- return false;
- }
+/************************************************************************/
+/* pj_set_searchpath() */
+/* */
+/* Path control for callers that can't practically provide */
+/* pj_set_finder() style callbacks. Call with (0,NULL) as args */
+/* to clear the searchpath set. */
+/************************************************************************/
- auto diskCache = NS_PROJ::DiskChunkCache::open(ctx);
- if (!diskCache)
- return false;
- auto stmt =
- diskCache->prepare("SELECT lastChecked, fileSize, lastModified, etag "
- "FROM downloaded_file_properties WHERE url = ?");
- if (!stmt)
- return false;
- stmt->bindText(url.c_str());
-
- props.lastChecked = curTime;
- auto hDB = diskCache->handle();
-
- if (stmt->execute() == SQLITE_ROW) {
- stmt = diskCache->prepare(
- "UPDATE downloaded_file_properties SET lastChecked = ?, "
- "fileSize = ?, lastModified = ?, etag = ? "
- "WHERE url = ?");
- if (!stmt)
- return false;
- stmt->bindInt64(props.lastChecked);
- stmt->bindInt64(props.size);
- if (props.lastModified.empty())
- stmt->bindNull();
- else
- stmt->bindText(props.lastModified.c_str());
- if (props.etag.empty())
- stmt->bindNull();
- else
- stmt->bindText(props.etag.c_str());
- stmt->bindText(url.c_str());
- if (stmt->execute() != SQLITE_DONE) {
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- return false;
- }
- } else {
- stmt = diskCache->prepare(
- "INSERT INTO downloaded_file_properties (url, lastChecked, "
- "fileSize, lastModified, etag) VALUES "
- "(?,?,?,?,?)");
- if (!stmt)
- return false;
- stmt->bindText(url.c_str());
- stmt->bindInt64(props.lastChecked);
- stmt->bindInt64(props.size);
- if (props.lastModified.empty())
- stmt->bindNull();
- else
- stmt->bindText(props.lastModified.c_str());
- if (props.etag.empty())
- stmt->bindNull();
- else
- stmt->bindText(props.etag.c_str());
- if (stmt->execute() != SQLITE_DONE) {
- pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
- return false;
- }
- }
- return true;
+void pj_set_searchpath(int count, const char **path) {
+ proj_context_set_search_paths(nullptr, count,
+ const_cast<const char *const *>(path));
}
diff --git a/src/filemanager.hpp b/src/filemanager.hpp
index 9793267c..b84753ce 100644
--- a/src/filemanager.hpp
+++ b/src/filemanager.hpp
@@ -39,13 +39,26 @@ NS_PROJ_START
class File;
+enum class FileAccess {
+ READ_ONLY, // "rb"
+ READ_UPDATE, // "r+b"
+ CREATE, // "w+b"
+};
+
class FileManager {
private:
FileManager() = delete;
public:
// "Low-level" interface.
- static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename);
+ static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename,
+ FileAccess access);
+ static bool exists(PJ_CONTEXT *ctx, const char *filename);
+ static bool mkdir(PJ_CONTEXT *ctx, const char *filename);
+ static bool unlink(PJ_CONTEXT *ctx, const char *filename);
+ static bool rename(PJ_CONTEXT *ctx, const char *oldPath,
+ const char *newPath);
+ static std::string getProjLibEnvVar(PJ_CONTEXT *ctx);
// "High-level" interface, honoring PROJ_LIB and the like.
static std::unique_ptr<File> open_resource_file(PJ_CONTEXT *ctx,
@@ -66,6 +79,7 @@ class File {
public:
virtual ~File();
virtual size_t read(void *buffer, size_t sizeBytes) = 0;
+ virtual size_t write(const void *buffer, size_t sizeBytes) = 0;
virtual bool seek(unsigned long long offset, int whence = SEEK_SET) = 0;
virtual unsigned long long tell() = 0;
virtual void reassign_context(PJ_CONTEXT *ctx) = 0;
@@ -74,8 +88,12 @@ class File {
const std::string &name() const { return name_; }
};
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<File> pj_network_file_open(PJ_CONTEXT* ctx, const char* filename);
+
NS_PROJ_END
//! @endcond Doxygen_Suppress
-#endif // FILEMANAGER_HPP_INCLUDED \ No newline at end of file
+#endif // FILEMANAGER_HPP_INCLUDED
diff --git a/src/grids.cpp b/src/grids.cpp
index 3007fedc..d5f961f7 100644
--- a/src/grids.cpp
+++ b/src/grids.cpp
@@ -112,9 +112,7 @@ VerticalShiftGrid::VerticalShiftGrid(const std::string &nameIn, int widthIn,
// ---------------------------------------------------------------------------
-bool VerticalShiftGrid::isNodata(float /*val*/, double /* multiplier */) const {
- return false;
-}
+VerticalShiftGrid::~VerticalShiftGrid() = default;
// ---------------------------------------------------------------------------
@@ -2360,12 +2358,14 @@ bool HorizontalShiftGridSet::reopen(PJ_CONTEXT *ctx) {
// ---------------------------------------------------------------------------
+#define REL_TOLERANCE_HGRIDSHIFT 1e-5
+
const HorizontalShiftGrid *HorizontalShiftGrid::gridAt(double lon,
double lat) const {
for (const auto &child : m_children) {
const auto &extentChild = child->extentAndRes();
- const double epsilon =
- (extentChild.resLon + extentChild.resLat) / 10000.0;
+ const double epsilon = (extentChild.resLon + extentChild.resLat) *
+ REL_TOLERANCE_HGRIDSHIFT;
if ((extentChild.fullWorldLongitude() ||
(lon + epsilon >= extentChild.westLon &&
lon - epsilon <= extentChild.eastLon)) &&
@@ -2385,7 +2385,8 @@ const HorizontalShiftGrid *HorizontalShiftGridSet::gridAt(double lon,
return grid.get();
}
const auto &extent = grid->extentAndRes();
- const double epsilon = (extent.resLon + extent.resLat) / 10000.0;
+ const double epsilon =
+ (extent.resLon + extent.resLat) * REL_TOLERANCE_HGRIDSHIFT;
if ((extent.fullWorldLongitude() ||
(lon + epsilon >= extent.westLon &&
lon - epsilon <= extent.eastLon)) &&
@@ -2857,26 +2858,26 @@ static PJ_LP pj_hgrid_interpolate(PJ_LP t, const HorizontalShiftGrid *grid,
frct.phi = t.phi - indx.phi;
val.lam = val.phi = HUGE_VAL;
if (indx.lam < 0) {
- if (indx.lam == -1 && frct.lam > 0.99999999999) {
+ if (indx.lam == -1 && frct.lam > 1 - 10 * REL_TOLERANCE_HGRIDSHIFT) {
++indx.lam;
frct.lam = 0.;
} else
return val;
} else if ((in = indx.lam + 1) >= grid->width()) {
- if (in == grid->width() && frct.lam < 1e-11) {
+ if (in == grid->width() && frct.lam < 10 * REL_TOLERANCE_HGRIDSHIFT) {
--indx.lam;
frct.lam = 1.;
} else
return val;
}
if (indx.phi < 0) {
- if (indx.phi == -1 && frct.phi > 0.99999999999) {
+ if (indx.phi == -1 && frct.phi > 1 - 10 * REL_TOLERANCE_HGRIDSHIFT) {
++indx.phi;
frct.phi = 0.;
} else
return val;
} else if ((in = indx.phi + 1) >= grid->height()) {
- if (in == grid->height() && frct.phi < 1e-11) {
+ if (in == grid->height() && frct.phi < 10 * REL_TOLERANCE_HGRIDSHIFT) {
--indx.phi;
frct.phi = 1.;
} else
@@ -2937,8 +2938,6 @@ static PJ_LP pj_hgrid_apply_internal(projCtx ctx, PJ_LP in,
tb.lam -= extent->westLon;
tb.phi -= extent->southLat;
- tb.lam = adjlon(tb.lam - M_PI) + M_PI;
-
t = pj_hgrid_interpolate(tb, grid, true);
if (grid->hasChanged()) {
shouldRetry = gridset->reopen(ctx);
@@ -2981,7 +2980,6 @@ static PJ_LP pj_hgrid_apply_internal(projCtx ctx, PJ_LP in,
tb = in;
tb.lam -= extent->westLon;
tb.phi -= extent->southLat;
- tb.lam = adjlon(tb.lam - M_PI) + M_PI;
dif.lam = std::numeric_limits<double>::max();
dif.phi = std::numeric_limits<double>::max();
continue;
diff --git a/src/grids.hpp b/src/grids.hpp
index 6b6ee0d2..0fd1b7b0 100644
--- a/src/grids.hpp
+++ b/src/grids.hpp
@@ -51,7 +51,7 @@ struct ExtentAndRes {
// ---------------------------------------------------------------------------
-class Grid {
+class PROJ_GCC_DLL Grid {
protected:
std::string m_name;
int m_width;
@@ -62,40 +62,42 @@ class Grid {
const ExtentAndRes &extentIn);
public:
- virtual ~Grid();
+ PROJ_FOR_TEST virtual ~Grid();
- int width() const { return m_width; }
- int height() const { return m_height; }
- const ExtentAndRes &extentAndRes() const { return m_extent; }
- const std::string &name() const { return m_name; }
+ PROJ_FOR_TEST int width() const { return m_width; }
+ PROJ_FOR_TEST int height() const { return m_height; }
+ PROJ_FOR_TEST const ExtentAndRes &extentAndRes() const { return m_extent; }
+ PROJ_FOR_TEST const std::string &name() const { return m_name; }
- virtual bool isNullGrid() const { return false; }
- virtual bool hasChanged() const = 0;
+ PROJ_FOR_TEST virtual bool isNullGrid() const { return false; }
+ PROJ_FOR_TEST virtual bool hasChanged() const = 0;
};
// ---------------------------------------------------------------------------
-class VerticalShiftGrid : public Grid {
+class PROJ_GCC_DLL VerticalShiftGrid : public Grid {
protected:
std::vector<std::unique_ptr<VerticalShiftGrid>> m_children{};
public:
- VerticalShiftGrid(const std::string &nameIn, int widthIn, int heightIn,
- const ExtentAndRes &extentIn);
+ PROJ_FOR_TEST VerticalShiftGrid(const std::string &nameIn, int widthIn,
+ int heightIn, const ExtentAndRes &extentIn);
+ PROJ_FOR_TEST ~VerticalShiftGrid() override;
- const VerticalShiftGrid *gridAt(double lon, double lat) const;
+ PROJ_FOR_TEST const VerticalShiftGrid *gridAt(double lon, double lat) const;
- virtual bool isNodata(float /*val*/, double /* multiplier */) const;
+ PROJ_FOR_TEST virtual bool isNodata(float /*val*/,
+ double /* multiplier */) const = 0;
// x = 0 is western-most column, y = 0 is southern-most line
- virtual bool valueAt(int x, int y, float &out) const = 0;
+ PROJ_FOR_TEST virtual bool valueAt(int x, int y, float &out) const = 0;
- virtual void reassign_context(PJ_CONTEXT *ctx) = 0;
+ PROJ_FOR_TEST virtual void reassign_context(PJ_CONTEXT *ctx) = 0;
};
// ---------------------------------------------------------------------------
-class VerticalShiftGridSet {
+class PROJ_GCC_DLL VerticalShiftGridSet {
protected:
std::string m_name{};
std::string m_format{};
@@ -104,45 +106,50 @@ class VerticalShiftGridSet {
VerticalShiftGridSet();
public:
- virtual ~VerticalShiftGridSet();
+ PROJ_FOR_TEST virtual ~VerticalShiftGridSet();
- static std::unique_ptr<VerticalShiftGridSet>
+ PROJ_FOR_TEST static std::unique_ptr<VerticalShiftGridSet>
open(PJ_CONTEXT *ctx, const std::string &filename);
- const std::string &name() const { return m_name; }
- const std::string &format() const { return m_format; }
- const std::vector<std::unique_ptr<VerticalShiftGrid>> &grids() const {
+ PROJ_FOR_TEST const std::string &name() const { return m_name; }
+ PROJ_FOR_TEST const std::string &format() const { return m_format; }
+ PROJ_FOR_TEST const std::vector<std::unique_ptr<VerticalShiftGrid>> &
+ grids() const {
return m_grids;
}
- const VerticalShiftGrid *gridAt(double lon, double lat) const;
+ PROJ_FOR_TEST const VerticalShiftGrid *gridAt(double lon, double lat) const;
- virtual void reassign_context(PJ_CONTEXT *ctx);
- virtual bool reopen(PJ_CONTEXT *ctx);
+ PROJ_FOR_TEST virtual void reassign_context(PJ_CONTEXT *ctx);
+ PROJ_FOR_TEST virtual bool reopen(PJ_CONTEXT *ctx);
};
// ---------------------------------------------------------------------------
-class HorizontalShiftGrid : public Grid {
+class PROJ_GCC_DLL HorizontalShiftGrid : public Grid {
protected:
std::vector<std::unique_ptr<HorizontalShiftGrid>> m_children{};
public:
- HorizontalShiftGrid(const std::string &nameIn, int widthIn, int heightIn,
- const ExtentAndRes &extentIn);
- ~HorizontalShiftGrid() override;
+ PROJ_FOR_TEST HorizontalShiftGrid(const std::string &nameIn, int widthIn,
+ int heightIn,
+ const ExtentAndRes &extentIn);
+ PROJ_FOR_TEST ~HorizontalShiftGrid() override;
- const HorizontalShiftGrid *gridAt(double lon, double lat) const;
+ PROJ_FOR_TEST const HorizontalShiftGrid *gridAt(double lon,
+ double lat) const;
// x = 0 is western-most column, y = 0 is southern-most line
- virtual bool valueAt(int x, int y, bool compensateNTConvention,
- float &lonShift, float &latShift) const = 0;
+ PROJ_FOR_TEST virtual bool valueAt(int x, int y,
+ bool compensateNTConvention,
+ float &lonShift,
+ float &latShift) const = 0;
- virtual void reassign_context(PJ_CONTEXT *ctx) = 0;
+ PROJ_FOR_TEST virtual void reassign_context(PJ_CONTEXT *ctx) = 0;
};
// ---------------------------------------------------------------------------
-class HorizontalShiftGridSet {
+class PROJ_GCC_DLL HorizontalShiftGridSet {
protected:
std::string m_name{};
std::string m_format{};
@@ -151,54 +158,57 @@ class HorizontalShiftGridSet {
HorizontalShiftGridSet();
public:
- virtual ~HorizontalShiftGridSet();
+ PROJ_FOR_TEST virtual ~HorizontalShiftGridSet();
- static std::unique_ptr<HorizontalShiftGridSet>
+ PROJ_FOR_TEST static std::unique_ptr<HorizontalShiftGridSet>
open(PJ_CONTEXT *ctx, const std::string &filename);
- const std::string &name() const { return m_name; }
- const std::string &format() const { return m_format; }
- const std::vector<std::unique_ptr<HorizontalShiftGrid>> &grids() const {
+ PROJ_FOR_TEST const std::string &name() const { return m_name; }
+ PROJ_FOR_TEST const std::string &format() const { return m_format; }
+ PROJ_FOR_TEST const std::vector<std::unique_ptr<HorizontalShiftGrid>> &
+ grids() const {
return m_grids;
}
- const HorizontalShiftGrid *gridAt(double lon, double lat) const;
+ PROJ_FOR_TEST const HorizontalShiftGrid *gridAt(double lon,
+ double lat) const;
- virtual void reassign_context(PJ_CONTEXT *ctx);
- virtual bool reopen(PJ_CONTEXT *ctx);
+ PROJ_FOR_TEST virtual void reassign_context(PJ_CONTEXT *ctx);
+ PROJ_FOR_TEST virtual bool reopen(PJ_CONTEXT *ctx);
};
// ---------------------------------------------------------------------------
-class GenericShiftGrid : public Grid {
+class PROJ_GCC_DLL GenericShiftGrid : public Grid {
protected:
std::vector<std::unique_ptr<GenericShiftGrid>> m_children{};
public:
- GenericShiftGrid(const std::string &nameIn, int widthIn, int heightIn,
- const ExtentAndRes &extentIn);
+ PROJ_FOR_TEST GenericShiftGrid(const std::string &nameIn, int widthIn,
+ int heightIn, const ExtentAndRes &extentIn);
- ~GenericShiftGrid() override;
+ PROJ_FOR_TEST ~GenericShiftGrid() override;
- const GenericShiftGrid *gridAt(double lon, double lat) const;
+ PROJ_FOR_TEST const GenericShiftGrid *gridAt(double lon, double lat) const;
- virtual std::string unit(int sample) const = 0;
+ PROJ_FOR_TEST virtual std::string unit(int sample) const = 0;
- virtual std::string description(int sample) const = 0;
+ PROJ_FOR_TEST virtual std::string description(int sample) const = 0;
- virtual std::string metadataItem(const std::string &key,
- int sample = -1) const = 0;
+ PROJ_FOR_TEST virtual std::string metadataItem(const std::string &key,
+ int sample = -1) const = 0;
- virtual int samplesPerPixel() const = 0;
+ PROJ_FOR_TEST virtual int samplesPerPixel() const = 0;
// x = 0 is western-most column, y = 0 is southern-most line
- virtual bool valueAt(int x, int y, int sample, float &out) const = 0;
+ PROJ_FOR_TEST virtual bool valueAt(int x, int y, int sample,
+ float &out) const = 0;
- virtual void reassign_context(PJ_CONTEXT *ctx) = 0;
+ PROJ_FOR_TEST virtual void reassign_context(PJ_CONTEXT *ctx) = 0;
};
// ---------------------------------------------------------------------------
-class GenericShiftGridSet {
+class PROJ_GCC_DLL GenericShiftGridSet {
protected:
std::string m_name{};
std::string m_format{};
@@ -207,20 +217,21 @@ class GenericShiftGridSet {
GenericShiftGridSet();
public:
- virtual ~GenericShiftGridSet();
+ PROJ_FOR_TEST virtual ~GenericShiftGridSet();
- static std::unique_ptr<GenericShiftGridSet>
+ PROJ_FOR_TEST static std::unique_ptr<GenericShiftGridSet>
open(PJ_CONTEXT *ctx, const std::string &filename);
- const std::string &name() const { return m_name; }
- const std::string &format() const { return m_format; }
- const std::vector<std::unique_ptr<GenericShiftGrid>> &grids() const {
+ PROJ_FOR_TEST const std::string &name() const { return m_name; }
+ PROJ_FOR_TEST const std::string &format() const { return m_format; }
+ PROJ_FOR_TEST const std::vector<std::unique_ptr<GenericShiftGrid>> &
+ grids() const {
return m_grids;
}
- const GenericShiftGrid *gridAt(double lon, double lat) const;
+ PROJ_FOR_TEST const GenericShiftGrid *gridAt(double lon, double lat) const;
- virtual void reassign_context(PJ_CONTEXT *ctx);
- virtual bool reopen(PJ_CONTEXT *ctx);
+ PROJ_FOR_TEST virtual void reassign_context(PJ_CONTEXT *ctx);
+ PROJ_FOR_TEST virtual bool reopen(PJ_CONTEXT *ctx);
};
// ---------------------------------------------------------------------------
diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp
index 7fb248c6..96e62c16 100644
--- a/src/iso19111/factory.cpp
+++ b/src/iso19111/factory.cpp
@@ -45,7 +45,7 @@
#include "proj/internal/lru_cache.hpp"
#include "proj/internal/tracing.hpp"
-#include "sqlite3.hpp"
+#include "sqlite3_utils.hpp"
#include <cmath>
#include <cstdlib>
@@ -491,7 +491,10 @@ void DatabaseContext::Private::cache(const std::string &code,
void DatabaseContext::Private::open(const std::string &databasePath,
PJ_CONTEXT *ctx) {
- setPjCtxt(ctx ? ctx : pj_get_default_ctx());
+ if (!ctx) {
+ ctx = pj_get_default_ctx();
+ }
+ setPjCtxt(ctx);
std::string path(databasePath);
if (path.empty()) {
path.resize(2048);
@@ -503,23 +506,26 @@ void DatabaseContext::Private::open(const std::string &databasePath,
}
}
+ std::string vfsName;
#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
- vfs_ = SQLite3VFS::create(false, true, true);
- if (vfs_ == nullptr ||
- sqlite3_open_v2(path.c_str(), &sqlite_handle_,
- SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX,
- vfs_->name()) != SQLITE_OK ||
- !sqlite_handle_) {
- throw FactoryException("Open of " + path + " failed");
+ if (ctx->custom_sqlite3_vfs_name.empty()) {
+ vfs_ = SQLite3VFS::create(false, true, true);
+ if (vfs_ == nullptr) {
+ throw FactoryException("Open of " + path + " failed");
+ }
+ vfsName = vfs_->name();
+ } else
+#endif
+ {
+ vfsName = ctx->custom_sqlite3_vfs_name;
}
-#else
if (sqlite3_open_v2(path.c_str(), &sqlite_handle_,
SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX,
- nullptr) != SQLITE_OK ||
+ vfsName.empty() ? nullptr : vfsName.c_str()) !=
+ SQLITE_OK ||
!sqlite_handle_) {
throw FactoryException("Open of " + path + " failed");
}
-#endif
databasePath_ = path;
registerFunctions();
diff --git a/src/lib_proj.cmake b/src/lib_proj.cmake
index fdb59434..d5a4d63e 100644
--- a/src/lib_proj.cmake
+++ b/src/lib_proj.cmake
@@ -247,7 +247,6 @@ set(SRC_LIBPROJ_CORE
mlfn.cpp
msfn.cpp
mutex.cpp
- open_lib.cpp
param.cpp
phi2.cpp
pipeline.cpp
@@ -282,8 +281,9 @@ set(SRC_LIBPROJ_CORE
grids.cpp
filemanager.hpp
filemanager.cpp
- sqlite3.hpp
- sqlite3.cpp
+ networkfilemanager.cpp
+ sqlite3_utils.hpp
+ sqlite3_utils.cpp
${CMAKE_CURRENT_BINARY_DIR}/proj_config.h
)
@@ -367,6 +367,10 @@ target_compile_options(${PROJ_CORE_TARGET}
PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${PROJ_CXX_WARN_FLAGS}>
)
+if(MSVC OR MINGW)
+ target_compile_definitions(${PROJ_CORE_TARGET} PRIVATE -DNOMINMAX)
+endif()
+
# Tell Intel compiler to do arithmetic accurately. This is needed to stop the
# compiler from ignoring parentheses in expressions like (a + b) + c and from
# simplifying 0.0 + x to x (which is wrong if x = -0.0).
@@ -438,16 +442,16 @@ if(USE_THREAD AND Threads_FOUND AND CMAKE_USE_PTHREADS_INIT)
target_link_libraries(${PROJ_CORE_TARGET} ${CMAKE_THREAD_LIBS_INIT})
endif()
-include_directories(${SQLITE3_INCLUDE_DIR})
+target_include_directories(${PROJ_CORE_TARGET} PRIVATE ${SQLITE3_INCLUDE_DIR})
target_link_libraries(${PROJ_CORE_TARGET} ${SQLITE3_LIBRARY})
-if(NOT DISABLE_TIFF_IS_STRONGLY_DISCOURAGED)
- include_directories(${TIFF_INCLUDE_DIR})
+if(NOT DISABLE_TIFF)
+ target_include_directories(${PROJ_CORE_TARGET} PRIVATE ${TIFF_INCLUDE_DIR})
target_link_libraries(${PROJ_CORE_TARGET} ${TIFF_LIBRARY})
endif()
if(CURL_FOUND)
- include_directories(${CURL_INCLUDE_DIR})
+ target_include_directories(${PROJ_CORE_TARGET} PRIVATE ${CURL_INCLUDE_DIR})
target_link_libraries(${PROJ_CORE_TARGET} ${CURL_LIBRARY})
endif()
diff --git a/src/networkfilemanager.cpp b/src/networkfilemanager.cpp
new file mode 100644
index 00000000..5d15c5af
--- /dev/null
+++ b/src/networkfilemanager.cpp
@@ -0,0 +1,2541 @@
+/******************************************************************************
+ * Project: PROJ
+ * Purpose: Functionality related to network access and caching
+ * Author: Even Rouault, <even.rouault at spatialys.com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2019-2020, Even Rouault, <even.rouault at spatialys.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *****************************************************************************/
+
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
+#endif
+#define LRU11_DO_NOT_DEFINE_OUT_OF_CLASS_METHODS
+
+#include <stdlib.h>
+
+#include <algorithm>
+#include <limits>
+#include <string>
+
+#include "filemanager.hpp"
+#include "proj.h"
+#include "proj/internal/internal.hpp"
+#include "proj/internal/lru_cache.hpp"
+#include "proj_internal.h"
+#include "sqlite3_utils.hpp"
+
+#ifdef __MINGW32__
+// mingw32-win32 doesn't implement std::mutex
+namespace {
+class MyMutex {
+ public:
+ // cppcheck-suppress functionStatic
+ void lock() { pj_acquire_lock(); }
+ // cppcheck-suppress functionStatic
+ void unlock() { pj_release_lock(); }
+};
+}
+#else
+#include <mutex>
+#define MyMutex std::mutex
+#endif
+
+#ifdef CURL_ENABLED
+#include <curl/curl.h>
+#include <sqlite3.h> // for sqlite3_snprintf
+#endif
+
+#include <sys/stat.h>
+
+#ifdef _WIN32
+#include <shlobj.h>
+#else
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+#if defined(_WIN32)
+#include <windows.h>
+#elif defined(__MACH__) && defined(__APPLE__)
+#include <mach-o/dyld.h>
+#elif defined(__FreeBSD__)
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#endif
+
+#include <time.h>
+
+//! @cond Doxygen_Suppress
+
+#define STR_HELPER(x) #x
+#define STR(x) STR_HELPER(x)
+
+using namespace NS_PROJ::internal;
+
+NS_PROJ_START
+
+// ---------------------------------------------------------------------------
+
+static void sleep_ms(int ms) {
+#ifdef _WIN32
+ Sleep(ms);
+#else
+ usleep(ms * 1000);
+#endif
+}
+
+// ---------------------------------------------------------------------------
+
+constexpr size_t DOWNLOAD_CHUNK_SIZE = 16 * 1024;
+constexpr int MAX_CHUNKS = 64;
+
+struct FileProperties {
+ unsigned long long size = 0;
+ time_t lastChecked = 0;
+ std::string lastModified{};
+ std::string etag{};
+};
+
+class NetworkChunkCache {
+ public:
+ void insert(PJ_CONTEXT *ctx, const std::string &url,
+ unsigned long long chunkIdx, std::vector<unsigned char> &&data);
+
+ std::shared_ptr<std::vector<unsigned char>>
+ get(PJ_CONTEXT *ctx, const std::string &url, unsigned long long chunkIdx);
+
+ std::shared_ptr<std::vector<unsigned char>> get(PJ_CONTEXT *ctx,
+ const std::string &url,
+ unsigned long long chunkIdx,
+ FileProperties &props);
+
+ void clearMemoryCache();
+
+ static void clearDiskChunkCache(PJ_CONTEXT *ctx);
+
+ private:
+ struct Key {
+ std::string url;
+ unsigned long long chunkIdx;
+
+ Key(const std::string &urlIn, unsigned long long chunkIdxIn)
+ : url(urlIn), chunkIdx(chunkIdxIn) {}
+ bool operator==(const Key &other) const {
+ return url == other.url && chunkIdx == other.chunkIdx;
+ }
+ };
+
+ struct KeyHasher {
+ std::size_t operator()(const Key &k) const {
+ return std::hash<std::string>{}(k.url) ^
+ (std::hash<unsigned long long>{}(k.chunkIdx) << 1);
+ }
+ };
+
+ lru11::Cache<
+ Key, std::shared_ptr<std::vector<unsigned char>>, MyMutex,
+ std::unordered_map<
+ Key,
+ typename std::list<lru11::KeyValuePair<
+ Key, std::shared_ptr<std::vector<unsigned char>>>>::iterator,
+ KeyHasher>>
+ cache_{MAX_CHUNKS};
+};
+
+// ---------------------------------------------------------------------------
+
+static NetworkChunkCache gNetworkChunkCache{};
+
+// ---------------------------------------------------------------------------
+
+class NetworkFilePropertiesCache {
+ public:
+ void insert(PJ_CONTEXT *ctx, const std::string &url, FileProperties &props);
+
+ bool tryGet(PJ_CONTEXT *ctx, const std::string &url, FileProperties &props);
+
+ void clearMemoryCache();
+
+ private:
+ lru11::Cache<std::string, FileProperties, MyMutex> cache_{};
+};
+
+// ---------------------------------------------------------------------------
+
+static NetworkFilePropertiesCache gNetworkFileProperties{};
+
+// ---------------------------------------------------------------------------
+
+class DiskChunkCache {
+ PJ_CONTEXT *ctx_ = nullptr;
+ std::string path_{};
+ sqlite3 *hDB_ = nullptr;
+ std::string thisNamePtr_{};
+ std::unique_ptr<SQLite3VFS> vfs_{};
+
+ explicit DiskChunkCache(PJ_CONTEXT *ctx, const std::string &path);
+
+ bool initialize();
+ void commitAndClose();
+
+ bool createDBStructure();
+ bool checkConsistency();
+ bool get_links(sqlite3_int64 chunk_id, sqlite3_int64 &link_id,
+ sqlite3_int64 &prev, sqlite3_int64 &next,
+ sqlite3_int64 &head, sqlite3_int64 &tail);
+ bool update_links_of_prev_and_next_links(sqlite3_int64 prev,
+ sqlite3_int64 next);
+ bool update_linked_chunks(sqlite3_int64 link_id, sqlite3_int64 prev,
+ sqlite3_int64 next);
+ bool update_linked_chunks_head_tail(sqlite3_int64 head, sqlite3_int64 tail);
+
+ DiskChunkCache(const DiskChunkCache &) = delete;
+ DiskChunkCache &operator=(const DiskChunkCache &) = delete;
+
+ public:
+ static std::unique_ptr<DiskChunkCache> open(PJ_CONTEXT *ctx);
+ ~DiskChunkCache();
+
+ sqlite3 *handle() { return hDB_; }
+ std::unique_ptr<SQLiteStatement> prepare(const char *sql);
+ bool move_to_head(sqlite3_int64 chunk_id);
+ bool move_to_tail(sqlite3_int64 chunk_id);
+ void closeAndUnlink();
+};
+
+// ---------------------------------------------------------------------------
+
+static bool pj_context_get_grid_cache_is_enabled(PJ_CONTEXT *ctx) {
+ pj_load_ini(ctx);
+ return ctx->gridChunkCache.enabled;
+}
+
+// ---------------------------------------------------------------------------
+
+static long long pj_context_get_grid_cache_max_size(PJ_CONTEXT *ctx) {
+ pj_load_ini(ctx);
+ return ctx->gridChunkCache.max_size;
+}
+
+// ---------------------------------------------------------------------------
+
+static int pj_context_get_grid_cache_ttl(PJ_CONTEXT *ctx) {
+ pj_load_ini(ctx);
+ return ctx->gridChunkCache.ttl;
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<DiskChunkCache> DiskChunkCache::open(PJ_CONTEXT *ctx) {
+ if (!pj_context_get_grid_cache_is_enabled(ctx)) {
+ return nullptr;
+ }
+ const auto cachePath = pj_context_get_grid_cache_filename(ctx);
+ if (cachePath.empty()) {
+ return nullptr;
+ }
+
+ auto diskCache =
+ std::unique_ptr<DiskChunkCache>(new DiskChunkCache(ctx, cachePath));
+ if (!diskCache->initialize())
+ diskCache.reset();
+ return diskCache;
+}
+
+// ---------------------------------------------------------------------------
+
+DiskChunkCache::DiskChunkCache(PJ_CONTEXT *ctx, const std::string &path)
+ : ctx_(ctx), path_(path) {}
+
+// ---------------------------------------------------------------------------
+
+bool DiskChunkCache::initialize() {
+ std::string vfsName;
+ if (ctx_->custom_sqlite3_vfs_name.empty()) {
+ vfs_ = SQLite3VFS::create(true, false, false);
+ if (vfs_ == nullptr) {
+ return false;
+ }
+ vfsName = vfs_->name();
+ } else {
+ vfsName = ctx_->custom_sqlite3_vfs_name;
+ }
+ sqlite3_open_v2(path_.c_str(), &hDB_,
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
+ vfsName.c_str());
+ if (!hDB_) {
+ pj_log(ctx_, PJ_LOG_ERROR, "Cannot open %s", path_.c_str());
+ return false;
+ }
+
+ // Cannot run more than 30 times / a bit more than one second.
+ for (int i = 0;; i++) {
+ int ret =
+ sqlite3_exec(hDB_, "BEGIN EXCLUSIVE", nullptr, nullptr, nullptr);
+ if (ret == SQLITE_OK) {
+ break;
+ }
+ if (ret != SQLITE_BUSY) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ sqlite3_close(hDB_);
+ hDB_ = nullptr;
+ return false;
+ }
+ const char *max_iters = getenv("PROJ_LOCK_MAX_ITERS");
+ if (i >= (max_iters && max_iters[0] ? atoi(max_iters)
+ : 30)) { // A bit more than 1 second
+ pj_log(ctx_, PJ_LOG_ERROR, "Cannot take exclusive lock on %s",
+ path_.c_str());
+ sqlite3_close(hDB_);
+ hDB_ = nullptr;
+ return false;
+ }
+ pj_log(ctx_, PJ_LOG_TRACE, "Lock taken on cache. Waiting a bit...");
+ // Retry every 5 ms for 50 ms, then every 10 ms for 100 ms, then
+ // every 100 ms
+ sleep_ms(i < 10 ? 5 : i < 20 ? 10 : 100);
+ }
+ char **pasResult = nullptr;
+ int nRows = 0;
+ int nCols = 0;
+ sqlite3_get_table(hDB_,
+ "SELECT 1 FROM sqlite_master WHERE name = 'properties'",
+ &pasResult, &nRows, &nCols, nullptr);
+ sqlite3_free_table(pasResult);
+ if (nRows == 0) {
+ if (!createDBStructure()) {
+ sqlite3_close(hDB_);
+ hDB_ = nullptr;
+ return false;
+ }
+ }
+
+ if (getenv("PROJ_CHECK_CACHE_CONSISTENCY")) {
+ checkConsistency();
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+static const char *cache_db_structure_sql =
+ "CREATE TABLE properties("
+ " url TEXT PRIMARY KEY NOT NULL,"
+ " lastChecked TIMESTAMP NOT NULL,"
+ " fileSize INTEGER NOT NULL,"
+ " lastModified TEXT,"
+ " etag TEXT"
+ ");"
+ "CREATE TABLE downloaded_file_properties("
+ " url TEXT PRIMARY KEY NOT NULL,"
+ " lastChecked TIMESTAMP NOT NULL,"
+ " fileSize INTEGER NOT NULL,"
+ " lastModified TEXT,"
+ " etag TEXT"
+ ");"
+ "CREATE TABLE chunk_data("
+ " id INTEGER PRIMARY KEY AUTOINCREMENT CHECK (id > 0),"
+ " data BLOB NOT NULL"
+ ");"
+ "CREATE TABLE chunks("
+ " id INTEGER PRIMARY KEY AUTOINCREMENT CHECK (id > 0),"
+ " url TEXT NOT NULL,"
+ " offset INTEGER NOT NULL,"
+ " data_id INTEGER NOT NULL,"
+ " data_size INTEGER NOT NULL,"
+ " CONSTRAINT fk_chunks_url FOREIGN KEY (url) REFERENCES properties(url),"
+ " CONSTRAINT fk_chunks_data FOREIGN KEY (data_id) REFERENCES chunk_data(id)"
+ ");"
+ "CREATE INDEX idx_chunks ON chunks(url, offset);"
+ "CREATE TABLE linked_chunks("
+ " id INTEGER PRIMARY KEY AUTOINCREMENT CHECK (id > 0),"
+ " chunk_id INTEGER NOT NULL,"
+ " prev INTEGER,"
+ " next INTEGER,"
+ " CONSTRAINT fk_links_chunkid FOREIGN KEY (chunk_id) REFERENCES chunks(id),"
+ " CONSTRAINT fk_links_prev FOREIGN KEY (prev) REFERENCES linked_chunks(id),"
+ " CONSTRAINT fk_links_next FOREIGN KEY (next) REFERENCES linked_chunks(id)"
+ ");"
+ "CREATE INDEX idx_linked_chunks_chunk_id ON linked_chunks(chunk_id);"
+ "CREATE TABLE linked_chunks_head_tail("
+ " head INTEGER,"
+ " tail INTEGER,"
+ " CONSTRAINT lht_head FOREIGN KEY (head) REFERENCES linked_chunks(id),"
+ " CONSTRAINT lht_tail FOREIGN KEY (tail) REFERENCES linked_chunks(id)"
+ ");"
+ "INSERT INTO linked_chunks_head_tail VALUES (NULL, NULL);";
+
+bool DiskChunkCache::createDBStructure() {
+
+ pj_log(ctx_, PJ_LOG_TRACE, "Creating cache DB structure");
+ if (sqlite3_exec(hDB_, cache_db_structure_sql, nullptr, nullptr, nullptr) !=
+ SQLITE_OK) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return false;
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+// Used by checkConsistency() and insert()
+#define INVALIDATED_SQL_LITERAL "'invalidated'"
+
+bool DiskChunkCache::checkConsistency() {
+
+ auto stmt = prepare("SELECT * FROM chunk_data WHERE id NOT IN (SELECT "
+ "data_id FROM chunks)");
+ if (!stmt) {
+ return false;
+ }
+ if (stmt->execute() != SQLITE_DONE) {
+ fprintf(stderr, "Rows in chunk_data not referenced by chunks.\n");
+ return false;
+ }
+
+ stmt = prepare("SELECT * FROM chunks WHERE id NOT IN (SELECT chunk_id FROM "
+ "linked_chunks)");
+ if (!stmt) {
+ return false;
+ }
+ if (stmt->execute() != SQLITE_DONE) {
+ fprintf(stderr, "Rows in chunks not referenced by linked_chunks.\n");
+ return false;
+ }
+
+ stmt = prepare("SELECT * FROM chunks WHERE url <> " INVALIDATED_SQL_LITERAL
+ " AND url "
+ "NOT IN (SELECT url FROM properties)");
+ if (!stmt) {
+ return false;
+ }
+ if (stmt->execute() != SQLITE_DONE) {
+ fprintf(stderr, "url values in chunks not referenced by properties.\n");
+ return false;
+ }
+
+ stmt = prepare("SELECT head, tail FROM linked_chunks_head_tail");
+ if (!stmt) {
+ return false;
+ }
+ if (stmt->execute() != SQLITE_ROW) {
+ fprintf(stderr, "linked_chunks_head_tail empty.\n");
+ return false;
+ }
+ const auto head = stmt->getInt64();
+ const auto tail = stmt->getInt64();
+ if (stmt->execute() != SQLITE_DONE) {
+ fprintf(stderr, "linked_chunks_head_tail has more than one row.\n");
+ return false;
+ }
+
+ stmt = prepare("SELECT COUNT(*) FROM linked_chunks");
+ if (!stmt) {
+ return false;
+ }
+ if (stmt->execute() != SQLITE_ROW) {
+ fprintf(stderr, "linked_chunks_head_tail empty.\n");
+ return false;
+ }
+ const auto count_linked_chunks = stmt->getInt64();
+
+ if (head) {
+ auto id = head;
+ std::set<sqlite3_int64> visitedIds;
+ stmt = prepare("SELECT next FROM linked_chunks WHERE id = ?");
+ if (!stmt) {
+ return false;
+ }
+ while (true) {
+ visitedIds.insert(id);
+ stmt->reset();
+ stmt->bindInt64(id);
+ if (stmt->execute() != SQLITE_ROW) {
+ fprintf(stderr, "cannot find linked_chunks.id = %d.\n",
+ static_cast<int>(id));
+ return false;
+ }
+ auto next = stmt->getInt64();
+ if (next == 0) {
+ if (id != tail) {
+ fprintf(stderr,
+ "last item when following next is not tail.\n");
+ return false;
+ }
+ break;
+ }
+ if (visitedIds.find(next) != visitedIds.end()) {
+ fprintf(stderr, "found cycle on linked_chunks.next = %d.\n",
+ static_cast<int>(next));
+ return false;
+ }
+ id = next;
+ }
+ if (visitedIds.size() != static_cast<size_t>(count_linked_chunks)) {
+ fprintf(stderr,
+ "ghost items in linked_chunks when following next.\n");
+ return false;
+ }
+ } else if (count_linked_chunks) {
+ fprintf(stderr, "linked_chunks_head_tail.head = NULL but linked_chunks "
+ "not empty.\n");
+ return false;
+ }
+
+ if (tail) {
+ auto id = tail;
+ std::set<sqlite3_int64> visitedIds;
+ stmt = prepare("SELECT prev FROM linked_chunks WHERE id = ?");
+ if (!stmt) {
+ return false;
+ }
+ while (true) {
+ visitedIds.insert(id);
+ stmt->reset();
+ stmt->bindInt64(id);
+ if (stmt->execute() != SQLITE_ROW) {
+ fprintf(stderr, "cannot find linked_chunks.id = %d.\n",
+ static_cast<int>(id));
+ return false;
+ }
+ auto prev = stmt->getInt64();
+ if (prev == 0) {
+ if (id != head) {
+ fprintf(stderr,
+ "last item when following prev is not head.\n");
+ return false;
+ }
+ break;
+ }
+ if (visitedIds.find(prev) != visitedIds.end()) {
+ fprintf(stderr, "found cycle on linked_chunks.prev = %d.\n",
+ static_cast<int>(prev));
+ return false;
+ }
+ id = prev;
+ }
+ if (visitedIds.size() != static_cast<size_t>(count_linked_chunks)) {
+ fprintf(stderr,
+ "ghost items in linked_chunks when following prev.\n");
+ return false;
+ }
+ } else if (count_linked_chunks) {
+ fprintf(stderr, "linked_chunks_head_tail.tail = NULL but linked_chunks "
+ "not empty.\n");
+ return false;
+ }
+
+ fprintf(stderr, "check ok\n");
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+void DiskChunkCache::commitAndClose() {
+ if (hDB_) {
+ if( sqlite3_exec(hDB_, "COMMIT", nullptr, nullptr, nullptr) != SQLITE_OK ) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ }
+ sqlite3_close(hDB_);
+ hDB_ = nullptr;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+DiskChunkCache::~DiskChunkCache() {
+ commitAndClose();
+}
+
+// ---------------------------------------------------------------------------
+
+void DiskChunkCache::closeAndUnlink() {
+ commitAndClose();
+ if (vfs_) {
+ vfs_->raw()->xDelete(vfs_->raw(), path_.c_str(), 0);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<SQLiteStatement> DiskChunkCache::prepare(const char *sql) {
+ sqlite3_stmt *hStmt = nullptr;
+ sqlite3_prepare_v2(hDB_, sql, -1, &hStmt, nullptr);
+ if (!hStmt) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return nullptr;
+ }
+ return std::unique_ptr<SQLiteStatement>(new SQLiteStatement(hStmt));
+}
+
+// ---------------------------------------------------------------------------
+
+bool DiskChunkCache::get_links(sqlite3_int64 chunk_id, sqlite3_int64 &link_id,
+ sqlite3_int64 &prev, sqlite3_int64 &next,
+ sqlite3_int64 &head, sqlite3_int64 &tail) {
+ auto stmt =
+ prepare("SELECT id, prev, next FROM linked_chunks WHERE chunk_id = ?");
+ if (!stmt)
+ return false;
+ stmt->bindInt64(chunk_id);
+ {
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_ROW) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return false;
+ }
+ }
+ link_id = stmt->getInt64();
+ prev = stmt->getInt64();
+ next = stmt->getInt64();
+
+ stmt = prepare("SELECT head, tail FROM linked_chunks_head_tail");
+ {
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_ROW) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return false;
+ }
+ }
+ head = stmt->getInt64();
+ tail = stmt->getInt64();
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+bool DiskChunkCache::update_links_of_prev_and_next_links(sqlite3_int64 prev,
+ sqlite3_int64 next) {
+ if (prev) {
+ auto stmt = prepare("UPDATE linked_chunks SET next = ? WHERE id = ?");
+ if (!stmt)
+ return false;
+ if (next)
+ stmt->bindInt64(next);
+ else
+ stmt->bindNull();
+ stmt->bindInt64(prev);
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_DONE) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return false;
+ }
+ }
+
+ if (next) {
+ auto stmt = prepare("UPDATE linked_chunks SET prev = ? WHERE id = ?");
+ if (!stmt)
+ return false;
+ if (prev)
+ stmt->bindInt64(prev);
+ else
+ stmt->bindNull();
+ stmt->bindInt64(next);
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_DONE) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return false;
+ }
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+bool DiskChunkCache::update_linked_chunks(sqlite3_int64 link_id,
+ sqlite3_int64 prev,
+ sqlite3_int64 next) {
+ auto stmt =
+ prepare("UPDATE linked_chunks SET prev = ?, next = ? WHERE id = ?");
+ if (!stmt)
+ return false;
+ if (prev)
+ stmt->bindInt64(prev);
+ else
+ stmt->bindNull();
+ if (next)
+ stmt->bindInt64(next);
+ else
+ stmt->bindNull();
+ stmt->bindInt64(link_id);
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_DONE) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return false;
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+bool DiskChunkCache::update_linked_chunks_head_tail(sqlite3_int64 head,
+ sqlite3_int64 tail) {
+ auto stmt =
+ prepare("UPDATE linked_chunks_head_tail SET head = ?, tail = ?");
+ if (!stmt)
+ return false;
+ if (head)
+ stmt->bindInt64(head);
+ else
+ stmt->bindNull(); // shouldn't happen normally
+ if (tail)
+ stmt->bindInt64(tail);
+ else
+ stmt->bindNull(); // shouldn't happen normally
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_DONE) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return false;
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+bool DiskChunkCache::move_to_head(sqlite3_int64 chunk_id) {
+
+ sqlite3_int64 link_id = 0;
+ sqlite3_int64 prev = 0;
+ sqlite3_int64 next = 0;
+ sqlite3_int64 head = 0;
+ sqlite3_int64 tail = 0;
+ if (!get_links(chunk_id, link_id, prev, next, head, tail)) {
+ return false;
+ }
+
+ if (link_id == head) {
+ return true;
+ }
+
+ if (!update_links_of_prev_and_next_links(prev, next)) {
+ return false;
+ }
+
+ if (head) {
+ auto stmt = prepare("UPDATE linked_chunks SET prev = ? WHERE id = ?");
+ if (!stmt)
+ return false;
+ stmt->bindInt64(link_id);
+ stmt->bindInt64(head);
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_DONE) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return false;
+ }
+ }
+
+ return update_linked_chunks(link_id, 0, head) &&
+ update_linked_chunks_head_tail(link_id,
+ (link_id == tail) ? prev : tail);
+}
+
+// ---------------------------------------------------------------------------
+
+bool DiskChunkCache::move_to_tail(sqlite3_int64 chunk_id) {
+ sqlite3_int64 link_id = 0;
+ sqlite3_int64 prev = 0;
+ sqlite3_int64 next = 0;
+ sqlite3_int64 head = 0;
+ sqlite3_int64 tail = 0;
+ if (!get_links(chunk_id, link_id, prev, next, head, tail)) {
+ return false;
+ }
+
+ if (link_id == tail) {
+ return true;
+ }
+
+ if (!update_links_of_prev_and_next_links(prev, next)) {
+ return false;
+ }
+
+ if (tail) {
+ auto stmt = prepare("UPDATE linked_chunks SET next = ? WHERE id = ?");
+ if (!stmt)
+ return false;
+ stmt->bindInt64(link_id);
+ stmt->bindInt64(tail);
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_DONE) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return false;
+ }
+ }
+
+ return update_linked_chunks(link_id, tail, 0) &&
+ update_linked_chunks_head_tail((link_id == head) ? next : head,
+ link_id);
+}
+
+// ---------------------------------------------------------------------------
+
+void NetworkChunkCache::insert(PJ_CONTEXT *ctx, const std::string &url,
+ unsigned long long chunkIdx,
+ std::vector<unsigned char> &&data) {
+ auto dataPtr(std::make_shared<std::vector<unsigned char>>(std::move(data)));
+ cache_.insert(Key(url, chunkIdx), dataPtr);
+
+ auto diskCache = DiskChunkCache::open(ctx);
+ if (!diskCache)
+ return;
+ auto hDB = diskCache->handle();
+
+ // Always insert DOWNLOAD_CHUNK_SIZE bytes to avoid fragmentation
+ std::vector<unsigned char> blob(*dataPtr);
+ assert(blob.size() <= DOWNLOAD_CHUNK_SIZE);
+ blob.resize(DOWNLOAD_CHUNK_SIZE);
+
+ // Check if there is an existing entry for that URL and offset
+ auto stmt = diskCache->prepare(
+ "SELECT id, data_id FROM chunks WHERE url = ? AND offset = ?");
+ if (!stmt)
+ return;
+ stmt->bindText(url.c_str());
+ stmt->bindInt64(chunkIdx * DOWNLOAD_CHUNK_SIZE);
+
+ const auto mainRet = stmt->execute();
+ if (mainRet == SQLITE_ROW) {
+ const auto chunk_id = stmt->getInt64();
+ const auto data_id = stmt->getInt64();
+ stmt =
+ diskCache->prepare("UPDATE chunk_data SET data = ? WHERE id = ?");
+ if (!stmt)
+ return;
+ stmt->bindBlob(blob.data(), blob.size());
+ stmt->bindInt64(data_id);
+ {
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+
+ diskCache->move_to_head(chunk_id);
+
+ return;
+ } else if (mainRet != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+
+ // Lambda to recycle an existing entry that was either invalidated, or
+ // least recently used.
+ const auto reuseExistingEntry = [ctx, &blob, &diskCache, hDB, &url,
+ chunkIdx, &dataPtr](
+ std::unique_ptr<SQLiteStatement> &stmtIn) {
+ const auto chunk_id = stmtIn->getInt64();
+ const auto data_id = stmtIn->getInt64();
+ if (data_id <= 0) {
+ pj_log(ctx, PJ_LOG_ERROR, "data_id <= 0");
+ return;
+ }
+
+ auto l_stmt =
+ diskCache->prepare("UPDATE chunk_data SET data = ? WHERE id = ?");
+ if (!l_stmt)
+ return;
+ l_stmt->bindBlob(blob.data(), blob.size());
+ l_stmt->bindInt64(data_id);
+ {
+ const auto ret2 = l_stmt->execute();
+ if (ret2 != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+
+ l_stmt = diskCache->prepare("UPDATE chunks SET url = ?, "
+ "offset = ?, data_size = ?, data_id = ? "
+ "WHERE id = ?");
+ if (!l_stmt)
+ return;
+ l_stmt->bindText(url.c_str());
+ l_stmt->bindInt64(chunkIdx * DOWNLOAD_CHUNK_SIZE);
+ l_stmt->bindInt64(dataPtr->size());
+ l_stmt->bindInt64(data_id);
+ l_stmt->bindInt64(chunk_id);
+ {
+ const auto ret2 = l_stmt->execute();
+ if (ret2 != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+
+ diskCache->move_to_head(chunk_id);
+ };
+
+ // Find if there is an invalidated chunk we can reuse
+ stmt = diskCache->prepare(
+ "SELECT id, data_id FROM chunks "
+ "WHERE id = (SELECT tail FROM linked_chunks_head_tail) AND "
+ "url = " INVALIDATED_SQL_LITERAL);
+ if (!stmt)
+ return;
+ {
+ const auto ret = stmt->execute();
+ if (ret == SQLITE_ROW) {
+ reuseExistingEntry(stmt);
+ return;
+ } else if (ret != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+
+ // Check if we have not reached the max size of the cache
+ stmt = diskCache->prepare("SELECT COUNT(*) FROM chunks");
+ if (!stmt)
+ return;
+ {
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_ROW) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+
+ const auto max_size = pj_context_get_grid_cache_max_size(ctx);
+ if (max_size > 0 &&
+ static_cast<long long>(stmt->getInt64() * DOWNLOAD_CHUNK_SIZE) >=
+ max_size) {
+ stmt = diskCache->prepare(
+ "SELECT id, data_id FROM chunks "
+ "WHERE id = (SELECT tail FROM linked_chunks_head_tail)");
+ if (!stmt)
+ return;
+
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_ROW) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ reuseExistingEntry(stmt);
+ return;
+ }
+
+ // Otherwise just append a new entry
+ stmt = diskCache->prepare("INSERT INTO chunk_data(data) VALUES (?)");
+ if (!stmt)
+ return;
+ stmt->bindBlob(blob.data(), blob.size());
+ {
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+
+ const auto chunk_data_id = sqlite3_last_insert_rowid(hDB);
+
+ stmt = diskCache->prepare("INSERT INTO chunks(url, offset, data_id, "
+ "data_size) VALUES (?,?,?,?)");
+ if (!stmt)
+ return;
+ stmt->bindText(url.c_str());
+ stmt->bindInt64(chunkIdx * DOWNLOAD_CHUNK_SIZE);
+ stmt->bindInt64(chunk_data_id);
+ stmt->bindInt64(dataPtr->size());
+ {
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+
+ const auto chunk_id = sqlite3_last_insert_rowid(hDB);
+
+ stmt = diskCache->prepare(
+ "INSERT INTO linked_chunks(chunk_id, prev, next) VALUES (?,NULL,NULL)");
+ if (!stmt)
+ return;
+ stmt->bindInt64(chunk_id);
+ if (stmt->execute() != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+
+ stmt = diskCache->prepare("SELECT head FROM linked_chunks_head_tail");
+ if (!stmt)
+ return;
+ if (stmt->execute() != SQLITE_ROW) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ if (stmt->getInt64() == 0) {
+ stmt = diskCache->prepare(
+ "UPDATE linked_chunks_head_tail SET head = ?, tail = ?");
+ if (!stmt)
+ return;
+ stmt->bindInt64(chunk_id);
+ stmt->bindInt64(chunk_id);
+ if (stmt->execute() != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+
+ diskCache->move_to_head(chunk_id);
+}
+
+// ---------------------------------------------------------------------------
+
+std::shared_ptr<std::vector<unsigned char>>
+NetworkChunkCache::get(PJ_CONTEXT *ctx, const std::string &url,
+ unsigned long long chunkIdx) {
+ std::shared_ptr<std::vector<unsigned char>> ret;
+ if (cache_.tryGet(Key(url, chunkIdx), ret)) {
+ return ret;
+ }
+
+ auto diskCache = DiskChunkCache::open(ctx);
+ if (!diskCache)
+ return ret;
+ auto hDB = diskCache->handle();
+
+ auto stmt = diskCache->prepare(
+ "SELECT chunks.id, chunks.data_size, chunk_data.data FROM chunks "
+ "JOIN chunk_data ON chunks.id = chunk_data.id "
+ "WHERE chunks.url = ? AND chunks.offset = ?");
+ if (!stmt)
+ return ret;
+
+ stmt->bindText(url.c_str());
+ stmt->bindInt64(chunkIdx * DOWNLOAD_CHUNK_SIZE);
+
+ const auto mainRet = stmt->execute();
+ if (mainRet == SQLITE_ROW) {
+ const auto chunk_id = stmt->getInt64();
+ const auto data_size = stmt->getInt64();
+ int blob_size = 0;
+ const void *blob = stmt->getBlob(blob_size);
+ if (blob_size < data_size) {
+ pj_log(ctx, PJ_LOG_ERROR,
+ "blob_size=%d < data_size for chunk_id=%d", blob_size,
+ static_cast<int>(chunk_id));
+ return ret;
+ }
+ if (data_size > static_cast<sqlite3_int64>(DOWNLOAD_CHUNK_SIZE)) {
+ pj_log(ctx, PJ_LOG_ERROR, "data_size > DOWNLOAD_CHUNK_SIZE");
+ return ret;
+ }
+ ret.reset(new std::vector<unsigned char>());
+ ret->assign(reinterpret_cast<const unsigned char *>(blob),
+ reinterpret_cast<const unsigned char *>(blob) +
+ static_cast<size_t>(data_size));
+ cache_.insert(Key(url, chunkIdx), ret);
+
+ if (!diskCache->move_to_head(chunk_id))
+ return ret;
+ } else if (mainRet != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ }
+
+ return ret;
+}
+
+// ---------------------------------------------------------------------------
+
+std::shared_ptr<std::vector<unsigned char>>
+NetworkChunkCache::get(PJ_CONTEXT *ctx, const std::string &url,
+ unsigned long long chunkIdx, FileProperties &props) {
+ if (!gNetworkFileProperties.tryGet(ctx, url, props)) {
+ return nullptr;
+ }
+
+ return get(ctx, url, chunkIdx);
+}
+
+// ---------------------------------------------------------------------------
+
+void NetworkChunkCache::clearMemoryCache() { cache_.clear(); }
+
+// ---------------------------------------------------------------------------
+
+void NetworkChunkCache::clearDiskChunkCache(PJ_CONTEXT *ctx) {
+ auto diskCache = DiskChunkCache::open(ctx);
+ if (!diskCache)
+ return;
+ diskCache->closeAndUnlink();
+}
+
+// ---------------------------------------------------------------------------
+
+void NetworkFilePropertiesCache::insert(PJ_CONTEXT *ctx, const std::string &url,
+ FileProperties &props) {
+ time(&props.lastChecked);
+ cache_.insert(url, props);
+
+ auto diskCache = DiskChunkCache::open(ctx);
+ if (!diskCache)
+ return;
+ auto hDB = diskCache->handle();
+ auto stmt = diskCache->prepare("SELECT fileSize, lastModified, etag "
+ "FROM properties WHERE url = ?");
+ if (!stmt)
+ return;
+ stmt->bindText(url.c_str());
+ if (stmt->execute() == SQLITE_ROW) {
+ FileProperties cachedProps;
+ cachedProps.size = stmt->getInt64();
+ const char *lastModified = stmt->getText();
+ cachedProps.lastModified = lastModified ? lastModified : std::string();
+ const char *etag = stmt->getText();
+ cachedProps.etag = etag ? etag : std::string();
+ if (props.size != cachedProps.size ||
+ props.lastModified != cachedProps.lastModified ||
+ props.etag != cachedProps.etag) {
+
+ // If cached properties don't match recent fresh ones, invalidate
+ // cached chunks
+ stmt = diskCache->prepare("SELECT id FROM chunks WHERE url = ?");
+ if (!stmt)
+ return;
+ stmt->bindText(url.c_str());
+ std::vector<sqlite3_int64> ids;
+ while (stmt->execute() == SQLITE_ROW) {
+ ids.emplace_back(stmt->getInt64());
+ stmt->resetResIndex();
+ }
+
+ for (const auto id : ids) {
+ diskCache->move_to_tail(id);
+ }
+
+ stmt = diskCache->prepare(
+ "UPDATE chunks SET url = " INVALIDATED_SQL_LITERAL ", "
+ "offset = -1, data_size = 0 WHERE url = ?");
+ if (!stmt)
+ return;
+ stmt->bindText(url.c_str());
+ if (stmt->execute() != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+
+ stmt = diskCache->prepare("UPDATE properties SET lastChecked = ?, "
+ "fileSize = ?, lastModified = ?, etag = ? "
+ "WHERE url = ?");
+ if (!stmt)
+ return;
+ stmt->bindInt64(props.lastChecked);
+ stmt->bindInt64(props.size);
+ if (props.lastModified.empty())
+ stmt->bindNull();
+ else
+ stmt->bindText(props.lastModified.c_str());
+ if (props.etag.empty())
+ stmt->bindNull();
+ else
+ stmt->bindText(props.etag.c_str());
+ stmt->bindText(url.c_str());
+ if (stmt->execute() != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ } else {
+ stmt = diskCache->prepare("INSERT INTO properties (url, lastChecked, "
+ "fileSize, lastModified, etag) VALUES "
+ "(?,?,?,?,?)");
+ if (!stmt)
+ return;
+ stmt->bindText(url.c_str());
+ stmt->bindInt64(props.lastChecked);
+ stmt->bindInt64(props.size);
+ if (props.lastModified.empty())
+ stmt->bindNull();
+ else
+ stmt->bindText(props.lastModified.c_str());
+ if (props.etag.empty())
+ stmt->bindNull();
+ else
+ stmt->bindText(props.etag.c_str());
+ if (stmt->execute() != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+bool NetworkFilePropertiesCache::tryGet(PJ_CONTEXT *ctx, const std::string &url,
+ FileProperties &props) {
+ if (cache_.tryGet(url, props)) {
+ return true;
+ }
+
+ auto diskCache = DiskChunkCache::open(ctx);
+ if (!diskCache)
+ return false;
+ auto stmt =
+ diskCache->prepare("SELECT lastChecked, fileSize, lastModified, etag "
+ "FROM properties WHERE url = ?");
+ if (!stmt)
+ return false;
+ stmt->bindText(url.c_str());
+ if (stmt->execute() != SQLITE_ROW) {
+ return false;
+ }
+ props.lastChecked = stmt->getInt64();
+ props.size = stmt->getInt64();
+ const char *lastModified = stmt->getText();
+ props.lastModified = lastModified ? lastModified : std::string();
+ const char *etag = stmt->getText();
+ props.etag = etag ? etag : std::string();
+
+ const auto ttl = pj_context_get_grid_cache_ttl(ctx);
+ if (ttl > 0) {
+ time_t curTime;
+ time(&curTime);
+ if (curTime > props.lastChecked + ttl) {
+ props = FileProperties();
+ return false;
+ }
+ }
+ cache_.insert(url, props);
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+void NetworkFilePropertiesCache::clearMemoryCache() { cache_.clear(); }
+
+// ---------------------------------------------------------------------------
+
+class NetworkFile : public File {
+ PJ_CONTEXT *m_ctx;
+ std::string m_url;
+ PROJ_NETWORK_HANDLE *m_handle;
+ unsigned long long m_pos = 0;
+ size_t m_nBlocksToDownload = 1;
+ unsigned long long m_lastDownloadedOffset;
+ FileProperties m_props;
+ proj_network_close_cbk_type m_closeCbk;
+ bool m_hasChanged = false;
+
+ NetworkFile(const NetworkFile &) = delete;
+ NetworkFile &operator=(const NetworkFile &) = delete;
+
+ protected:
+ NetworkFile(PJ_CONTEXT *ctx, const std::string &url,
+ PROJ_NETWORK_HANDLE *handle,
+ unsigned long long lastDownloadOffset,
+ const FileProperties &props)
+ : File(url), m_ctx(ctx), m_url(url), m_handle(handle),
+ m_lastDownloadedOffset(lastDownloadOffset), m_props(props),
+ m_closeCbk(ctx->networking.close) {}
+
+ public:
+ ~NetworkFile() override;
+
+ size_t read(void *buffer, size_t sizeBytes) override;
+ size_t write(const void *, size_t) override { return 0; }
+ bool seek(unsigned long long offset, int whence) override;
+ unsigned long long tell() override;
+ void reassign_context(PJ_CONTEXT *ctx) override;
+ bool hasChanged() const override { return m_hasChanged; }
+
+ static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename);
+
+ static bool get_props_from_headers(PJ_CONTEXT *ctx,
+ PROJ_NETWORK_HANDLE *handle,
+ FileProperties &props);
+};
+
+// ---------------------------------------------------------------------------
+
+bool NetworkFile::get_props_from_headers(PJ_CONTEXT *ctx,
+ PROJ_NETWORK_HANDLE *handle,
+ FileProperties &props) {
+ const char *contentRange = ctx->networking.get_header_value(
+ ctx, handle, "Content-Range", ctx->networking.user_data);
+ if (contentRange) {
+ const char *slash = strchr(contentRange, '/');
+ if (slash) {
+ props.size = std::stoull(slash + 1);
+
+ const char *lastModified = ctx->networking.get_header_value(
+ ctx, handle, "Last-Modified", ctx->networking.user_data);
+ if (lastModified)
+ props.lastModified = lastModified;
+
+ const char *etag = ctx->networking.get_header_value(
+ ctx, handle, "ETag", ctx->networking.user_data);
+ if (etag)
+ props.etag = etag;
+
+ return true;
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<File> NetworkFile::open(PJ_CONTEXT *ctx, const char *filename) {
+ FileProperties props;
+ if (gNetworkChunkCache.get(ctx, filename, 0, props)) {
+ return std::unique_ptr<File>(new NetworkFile(
+ ctx, filename, nullptr,
+ std::numeric_limits<unsigned long long>::max(), props));
+ } else {
+ std::vector<unsigned char> buffer(DOWNLOAD_CHUNK_SIZE);
+ size_t size_read = 0;
+ std::string errorBuffer;
+ errorBuffer.resize(1024);
+
+ auto handle = ctx->networking.open(
+ ctx, filename, 0, buffer.size(), &buffer[0], &size_read,
+ errorBuffer.size(), &errorBuffer[0], ctx->networking.user_data);
+ buffer.resize(size_read);
+ if (!handle) {
+ errorBuffer.resize(strlen(errorBuffer.data()));
+ pj_log(ctx, PJ_LOG_ERROR, "Cannot open %s: %s", filename,
+ errorBuffer.c_str());
+ }
+
+ bool ok = false;
+ if (handle) {
+ if (get_props_from_headers(ctx, handle, props)) {
+ ok = true;
+ gNetworkFileProperties.insert(ctx, filename, props);
+ gNetworkChunkCache.insert(ctx, filename, 0, std::move(buffer));
+ }
+ }
+
+ return std::unique_ptr<File>(
+ ok ? new NetworkFile(ctx, filename, handle, size_read, props)
+ : nullptr);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<File> pj_network_file_open(PJ_CONTEXT* ctx, const char* filename) {
+ return NetworkFile::open(ctx, filename);
+}
+
+// ---------------------------------------------------------------------------
+
+size_t NetworkFile::read(void *buffer, size_t sizeBytes) {
+
+ if (sizeBytes == 0)
+ return 0;
+
+ auto iterOffset = m_pos;
+ while (sizeBytes) {
+ const auto chunkIdxToDownload = iterOffset / DOWNLOAD_CHUNK_SIZE;
+ const auto offsetToDownload = chunkIdxToDownload * DOWNLOAD_CHUNK_SIZE;
+ std::vector<unsigned char> region;
+ auto pChunk = gNetworkChunkCache.get(m_ctx, m_url, chunkIdxToDownload);
+ if (pChunk != nullptr) {
+ region = *pChunk;
+ } else {
+ if (offsetToDownload == m_lastDownloadedOffset) {
+ // In case of consecutive reads (of small size), we use a
+ // heuristic that we will read the file sequentially, so
+ // we double the requested size to decrease the number of
+ // client/server roundtrips.
+ if (m_nBlocksToDownload < 100)
+ m_nBlocksToDownload *= 2;
+ } else {
+ // Random reads. Cancel the above heuristics.
+ m_nBlocksToDownload = 1;
+ }
+
+ // Ensure that we will request at least the number of blocks
+ // to satisfy the remaining buffer size to read.
+ const auto endOffsetToDownload =
+ ((iterOffset + sizeBytes + DOWNLOAD_CHUNK_SIZE - 1) /
+ DOWNLOAD_CHUNK_SIZE) *
+ DOWNLOAD_CHUNK_SIZE;
+ const auto nMinBlocksToDownload = static_cast<size_t>(
+ (endOffsetToDownload - offsetToDownload) / DOWNLOAD_CHUNK_SIZE);
+ if (m_nBlocksToDownload < nMinBlocksToDownload)
+ m_nBlocksToDownload = nMinBlocksToDownload;
+
+ // Avoid reading already cached data.
+ // Note: this might get evicted if concurrent reads are done, but
+ // this should not cause bugs. Just missed optimization.
+ for (size_t i = 1; i < m_nBlocksToDownload; i++) {
+ if (gNetworkChunkCache.get(m_ctx, m_url,
+ chunkIdxToDownload + i) != nullptr) {
+ m_nBlocksToDownload = i;
+ break;
+ }
+ }
+
+ if (m_nBlocksToDownload > MAX_CHUNKS)
+ m_nBlocksToDownload = MAX_CHUNKS;
+
+ region.resize(m_nBlocksToDownload * DOWNLOAD_CHUNK_SIZE);
+ size_t nRead = 0;
+ std::string errorBuffer;
+ errorBuffer.resize(1024);
+ if (!m_handle) {
+ m_handle = m_ctx->networking.open(
+ m_ctx, m_url.c_str(), offsetToDownload,
+ m_nBlocksToDownload * DOWNLOAD_CHUNK_SIZE, &region[0],
+ &nRead, errorBuffer.size(), &errorBuffer[0],
+ m_ctx->networking.user_data);
+ if (!m_handle) {
+ return 0;
+ }
+ } else {
+ nRead = m_ctx->networking.read_range(
+ m_ctx, m_handle, offsetToDownload,
+ m_nBlocksToDownload * DOWNLOAD_CHUNK_SIZE, &region[0],
+ errorBuffer.size(), &errorBuffer[0],
+ m_ctx->networking.user_data);
+ }
+ if (nRead == 0) {
+ errorBuffer.resize(strlen(errorBuffer.data()));
+ if (!errorBuffer.empty()) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Cannot read in %s: %s",
+ m_url.c_str(), errorBuffer.c_str());
+ }
+ return 0;
+ }
+
+ if (!m_hasChanged) {
+ FileProperties props;
+ if (get_props_from_headers(m_ctx, m_handle, props)) {
+ if (props.size != m_props.size ||
+ props.lastModified != m_props.lastModified ||
+ props.etag != m_props.etag) {
+ gNetworkFileProperties.insert(m_ctx, m_url, props);
+ gNetworkChunkCache.clearMemoryCache();
+ m_hasChanged = true;
+ }
+ }
+ }
+
+ region.resize(nRead);
+ m_lastDownloadedOffset = offsetToDownload + nRead;
+
+ const auto nChunks =
+ (region.size() + DOWNLOAD_CHUNK_SIZE - 1) / DOWNLOAD_CHUNK_SIZE;
+ for (size_t i = 0; i < nChunks; i++) {
+ std::vector<unsigned char> chunk(
+ region.data() + i * DOWNLOAD_CHUNK_SIZE,
+ region.data() +
+ std::min((i + 1) * DOWNLOAD_CHUNK_SIZE, region.size()));
+ gNetworkChunkCache.insert(m_ctx, m_url, chunkIdxToDownload + i,
+ std::move(chunk));
+ }
+ }
+ const size_t nToCopy = static_cast<size_t>(
+ std::min(static_cast<unsigned long long>(sizeBytes),
+ region.size() - (iterOffset - offsetToDownload)));
+ memcpy(buffer, region.data() + iterOffset - offsetToDownload, nToCopy);
+ buffer = static_cast<char *>(buffer) + nToCopy;
+ iterOffset += nToCopy;
+ sizeBytes -= nToCopy;
+ if (region.size() < static_cast<size_t>(DOWNLOAD_CHUNK_SIZE) &&
+ sizeBytes != 0) {
+ break;
+ }
+ }
+
+ size_t nRead = static_cast<size_t>(iterOffset - m_pos);
+ m_pos = iterOffset;
+ return nRead;
+}
+
+// ---------------------------------------------------------------------------
+
+bool NetworkFile::seek(unsigned long long offset, int whence) {
+ if (whence == SEEK_SET) {
+ m_pos = offset;
+ } else if (whence == SEEK_CUR) {
+ m_pos += offset;
+ } else {
+ if (offset != 0)
+ return false;
+ m_pos = m_props.size;
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+unsigned long long NetworkFile::tell() { return m_pos; }
+
+// ---------------------------------------------------------------------------
+
+NetworkFile::~NetworkFile() {
+ if (m_handle) {
+ m_ctx->networking.close(m_ctx, m_handle, m_ctx->networking.user_data);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void NetworkFile::reassign_context(PJ_CONTEXT *ctx) {
+ m_ctx = ctx;
+ if (m_closeCbk != m_ctx->networking.close) {
+ pj_log(m_ctx, PJ_LOG_ERROR,
+ "Networking close callback has changed following context "
+ "reassignment ! This is highly suspicious");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+#ifdef CURL_ENABLED
+
+struct CurlFileHandle {
+ std::string m_url;
+ CURL *m_handle;
+ std::string m_headers{};
+ std::string m_lastval{};
+ std::string m_useragent{};
+ char m_szCurlErrBuf[CURL_ERROR_SIZE + 1] = {};
+
+ CurlFileHandle(const CurlFileHandle &) = delete;
+ CurlFileHandle &operator=(const CurlFileHandle &) = delete;
+
+ explicit CurlFileHandle(const char *url, CURL *handle);
+ ~CurlFileHandle();
+
+ static PROJ_NETWORK_HANDLE *
+ open(PJ_CONTEXT *, const char *url, unsigned long long offset,
+ size_t size_to_read, void *buffer, size_t *out_size_read,
+ size_t error_string_max_size, char *out_error_string, void *);
+};
+
+// ---------------------------------------------------------------------------
+
+static std::string GetExecutableName() {
+#if defined(__linux)
+ std::string path;
+ path.resize(1024);
+ const auto ret = readlink("/proc/self/exe", &path[0], path.size());
+ if (ret > 0) {
+ path.resize(ret);
+ const auto pos = path.rfind('/');
+ if (pos != std::string::npos) {
+ path = path.substr(pos + 1);
+ }
+ return path;
+ }
+#elif defined(_WIN32)
+ std::string path;
+ path.resize(1024);
+ if (GetModuleFileNameA(nullptr, &path[0],
+ static_cast<DWORD>(path.size()))) {
+ path.resize(strlen(path.c_str()));
+ const auto pos = path.rfind('\\');
+ if (pos != std::string::npos) {
+ path = path.substr(pos + 1);
+ }
+ return path;
+ }
+#elif defined(__MACH__) && defined(__APPLE__)
+ std::string path;
+ path.resize(1024);
+ uint32_t size = static_cast<uint32_t>(path.size());
+ if (_NSGetExecutablePath(&path[0], &size) == 0) {
+ path.resize(strlen(path.c_str()));
+ const auto pos = path.rfind('/');
+ if (pos != std::string::npos) {
+ path = path.substr(pos + 1);
+ }
+ return path;
+ }
+#elif defined(__FreeBSD__)
+ int mib[4];
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PATHNAME;
+ mib[3] = -1;
+ std::string path;
+ path.resize(1024);
+ size_t size = path.size();
+ if (sysctl(mib, 4, &path[0], &size, nullptr, 0) == 0) {
+ path.resize(strlen(path.c_str()));
+ const auto pos = path.rfind('/');
+ if (pos != std::string::npos) {
+ path = path.substr(pos + 1);
+ }
+ return path;
+ }
+#endif
+
+ return std::string();
+}
+
+// ---------------------------------------------------------------------------
+
+CurlFileHandle::CurlFileHandle(const char *url, CURL *handle)
+ : m_url(url), m_handle(handle) {
+ curl_easy_setopt(handle, CURLOPT_URL, m_url.c_str());
+
+ if (getenv("PROJ_CURL_VERBOSE"))
+ curl_easy_setopt(handle, CURLOPT_VERBOSE, 1);
+
+// CURLOPT_SUPPRESS_CONNECT_HEADERS is defined in curl 7.54.0 or newer.
+#if LIBCURL_VERSION_NUM >= 0x073600
+ curl_easy_setopt(handle, CURLOPT_SUPPRESS_CONNECT_HEADERS, 1L);
+#endif
+
+ // Enable following redirections. Requires libcurl 7.10.1 at least.
+ curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1);
+ curl_easy_setopt(handle, CURLOPT_MAXREDIRS, 10);
+
+ if (getenv("PROJ_UNSAFE_SSL")) {
+ curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L);
+ curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L);
+ }
+
+ curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, m_szCurlErrBuf);
+
+ if (getenv("PROJ_NO_USERAGENT") == nullptr) {
+ m_useragent = "PROJ " STR(PROJ_VERSION_MAJOR) "." STR(
+ PROJ_VERSION_MINOR) "." STR(PROJ_VERSION_PATCH);
+ const auto exeName = GetExecutableName();
+ if (!exeName.empty()) {
+ m_useragent = exeName + " using " + m_useragent;
+ }
+ curl_easy_setopt(handle, CURLOPT_USERAGENT, m_useragent.data());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+CurlFileHandle::~CurlFileHandle() { curl_easy_cleanup(m_handle); }
+
+// ---------------------------------------------------------------------------
+
+static size_t pj_curl_write_func(void *buffer, size_t count, size_t nmemb,
+ void *req) {
+ const size_t nSize = count * nmemb;
+ auto pStr = static_cast<std::string *>(req);
+ if (pStr->size() + nSize > pStr->capacity()) {
+ // to avoid servers not honouring Range to cause excessive memory
+ // allocation
+ return 0;
+ }
+ pStr->append(static_cast<const char *>(buffer), nSize);
+ return nmemb;
+}
+
+// ---------------------------------------------------------------------------
+
+static double GetNewRetryDelay(int response_code, double dfOldDelay,
+ const char *pszErrBuf,
+ const char *pszCurlError) {
+ if (response_code == 429 || response_code == 500 ||
+ (response_code >= 502 && response_code <= 504) ||
+ // S3 sends some client timeout errors as 400 Client Error
+ (response_code == 400 && pszErrBuf &&
+ strstr(pszErrBuf, "RequestTimeout")) ||
+ (pszCurlError && strstr(pszCurlError, "Connection timed out"))) {
+ // Use an exponential backoff factor of 2 plus some random jitter
+ // We don't care about cryptographic quality randomness, hence:
+ // coverity[dont_call]
+ return dfOldDelay * (2 + rand() * 0.5 / RAND_MAX);
+ } else {
+ return 0;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+constexpr double MIN_RETRY_DELAY_MS = 500;
+constexpr double MAX_RETRY_DELAY_MS = 60000;
+
+PROJ_NETWORK_HANDLE *CurlFileHandle::open(PJ_CONTEXT *ctx, const char *url,
+ unsigned long long offset,
+ size_t size_to_read, void *buffer,
+ size_t *out_size_read,
+ size_t error_string_max_size,
+ char *out_error_string, void *) {
+ CURL *hCurlHandle = curl_easy_init();
+ if (!hCurlHandle)
+ return nullptr;
+
+ auto file =
+ std::unique_ptr<CurlFileHandle>(new CurlFileHandle(url, hCurlHandle));
+
+ double oldDelay = MIN_RETRY_DELAY_MS;
+ std::string headers;
+ std::string body;
+
+ char szBuffer[128];
+ sqlite3_snprintf(sizeof(szBuffer), szBuffer, "%llu-%llu", offset,
+ offset + size_to_read - 1);
+
+ while (true) {
+ curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, szBuffer);
+
+ headers.clear();
+ headers.reserve(16 * 1024);
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, &headers);
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
+ pj_curl_write_func);
+
+ body.clear();
+ body.reserve(size_to_read);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &body);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
+ pj_curl_write_func);
+
+ file->m_szCurlErrBuf[0] = '\0';
+
+ curl_easy_perform(hCurlHandle);
+
+ long response_code = 0;
+ curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
+
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, nullptr);
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION, nullptr);
+
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, nullptr);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, nullptr);
+
+ if (response_code == 0 || response_code >= 300) {
+ const double delay =
+ GetNewRetryDelay(static_cast<int>(response_code), oldDelay,
+ body.c_str(), file->m_szCurlErrBuf);
+ if (delay != 0 && delay < MAX_RETRY_DELAY_MS) {
+ pj_log(ctx, PJ_LOG_TRACE,
+ "Got a HTTP %ld error. Retrying in %d ms", response_code,
+ static_cast<int>(delay));
+ sleep_ms(static_cast<int>(delay));
+ oldDelay = delay;
+ } else {
+ if (out_error_string) {
+ if (file->m_szCurlErrBuf[0]) {
+ snprintf(out_error_string, error_string_max_size, "%s",
+ file->m_szCurlErrBuf);
+ } else {
+ snprintf(out_error_string, error_string_max_size,
+ "HTTP error %ld: %s", response_code,
+ body.c_str());
+ }
+ }
+ return nullptr;
+ }
+ } else {
+ break;
+ }
+ }
+
+ if (out_error_string && error_string_max_size) {
+ out_error_string[0] = '\0';
+ }
+
+ if (!body.empty()) {
+ memcpy(buffer, body.data(), std::min(size_to_read, body.size()));
+ }
+ *out_size_read = std::min(size_to_read, body.size());
+
+ file->m_headers = std::move(headers);
+ return reinterpret_cast<PROJ_NETWORK_HANDLE *>(file.release());
+}
+
+// ---------------------------------------------------------------------------
+
+static void pj_curl_close(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *handle,
+ void * /*user_data*/) {
+ delete reinterpret_cast<CurlFileHandle *>(handle);
+}
+
+// ---------------------------------------------------------------------------
+
+static size_t pj_curl_read_range(PJ_CONTEXT *ctx,
+ PROJ_NETWORK_HANDLE *raw_handle,
+ unsigned long long offset, size_t size_to_read,
+ void *buffer, size_t error_string_max_size,
+ char *out_error_string, void *) {
+ auto handle = reinterpret_cast<CurlFileHandle *>(raw_handle);
+ auto hCurlHandle = handle->m_handle;
+
+ double oldDelay = MIN_RETRY_DELAY_MS;
+ std::string headers;
+ std::string body;
+
+ char szBuffer[128];
+ sqlite3_snprintf(sizeof(szBuffer), szBuffer, "%llu-%llu", offset,
+ offset + size_to_read - 1);
+
+ while (true) {
+ curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, szBuffer);
+
+ headers.clear();
+ headers.reserve(16 * 1024);
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, &headers);
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
+ pj_curl_write_func);
+
+ body.clear();
+ body.reserve(size_to_read);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &body);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
+ pj_curl_write_func);
+
+ handle->m_szCurlErrBuf[0] = '\0';
+
+ curl_easy_perform(hCurlHandle);
+
+ long response_code = 0;
+ curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
+
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, nullptr);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, nullptr);
+
+ if (response_code == 0 || response_code >= 300) {
+ const double delay =
+ GetNewRetryDelay(static_cast<int>(response_code), oldDelay,
+ body.c_str(), handle->m_szCurlErrBuf);
+ if (delay != 0 && delay < MAX_RETRY_DELAY_MS) {
+ pj_log(ctx, PJ_LOG_TRACE,
+ "Got a HTTP %ld error. Retrying in %d ms", response_code,
+ static_cast<int>(delay));
+ sleep_ms(static_cast<int>(delay));
+ oldDelay = delay;
+ } else {
+ if (out_error_string) {
+ if (handle->m_szCurlErrBuf[0]) {
+ snprintf(out_error_string, error_string_max_size, "%s",
+ handle->m_szCurlErrBuf);
+ } else {
+ snprintf(out_error_string, error_string_max_size,
+ "HTTP error %ld: %s", response_code,
+ body.c_str());
+ }
+ }
+ return 0;
+ }
+ } else {
+ break;
+ }
+ }
+ if (out_error_string && error_string_max_size) {
+ out_error_string[0] = '\0';
+ }
+
+ if (!body.empty()) {
+ memcpy(buffer, body.data(), std::min(size_to_read, body.size()));
+ }
+ handle->m_headers = std::move(headers);
+
+ return std::min(size_to_read, body.size());
+}
+
+// ---------------------------------------------------------------------------
+
+static const char *pj_curl_get_header_value(PJ_CONTEXT *,
+ PROJ_NETWORK_HANDLE *raw_handle,
+ const char *header_name, void *) {
+ auto handle = reinterpret_cast<CurlFileHandle *>(raw_handle);
+ auto pos = ci_find(handle->m_headers, header_name);
+ if (pos == std::string::npos)
+ return nullptr;
+ pos += strlen(header_name);
+ const char *c_str = handle->m_headers.c_str();
+ if (c_str[pos] == ':')
+ pos++;
+ while (c_str[pos] == ' ')
+ pos++;
+ auto posEnd = pos;
+ while (c_str[posEnd] != '\r' && c_str[posEnd] != '\n' &&
+ c_str[posEnd] != '\0')
+ posEnd++;
+ handle->m_lastval = handle->m_headers.substr(pos, posEnd - pos);
+ return handle->m_lastval.c_str();
+}
+
+#else
+
+// ---------------------------------------------------------------------------
+
+static PROJ_NETWORK_HANDLE *
+no_op_network_open(PJ_CONTEXT *, const char * /* url */,
+ unsigned long long, /* offset */
+ size_t, /* size to read */
+ void *, /* buffer to update with bytes read*/
+ size_t *, /* output: size actually read */
+ size_t error_string_max_size, char *out_error_string,
+ void * /*user_data*/) {
+ if (out_error_string) {
+ snprintf(out_error_string, error_string_max_size, "%s",
+ "Network functionality not available");
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+static void no_op_network_close(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *,
+ void * /*user_data*/) {}
+
+#endif
+
+// ---------------------------------------------------------------------------
+
+void FileManager::fillDefaultNetworkInterface(PJ_CONTEXT *ctx) {
+#ifdef CURL_ENABLED
+ ctx->networking.open = CurlFileHandle::open;
+ ctx->networking.close = pj_curl_close;
+ ctx->networking.read_range = pj_curl_read_range;
+ ctx->networking.get_header_value = pj_curl_get_header_value;
+#else
+ ctx->networking.open = no_op_network_open;
+ ctx->networking.close = no_op_network_close;
+#endif
+}
+
+// ---------------------------------------------------------------------------
+
+void FileManager::clearMemoryCache() {
+ gNetworkChunkCache.clearMemoryCache();
+ gNetworkFileProperties.clearMemoryCache();
+}
+
+NS_PROJ_END
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+#ifdef WIN32
+static const char dir_chars[] = "/\\";
+#else
+static const char dir_chars[] = "/";
+#endif
+
+static bool is_tilde_slash(const char *name) {
+ return *name == '~' && strchr(dir_chars, name[1]);
+}
+
+static bool is_rel_or_absolute_filename(const char *name) {
+ return strchr(dir_chars, *name) ||
+ (*name == '.' && strchr(dir_chars, name[1])) ||
+ (!strncmp(name, "..", 2) && strchr(dir_chars, name[2])) ||
+ (name[0] != '\0' && name[1] == ':' && strchr(dir_chars, name[2]));
+}
+
+static std::string build_url(PJ_CONTEXT *ctx, const char *name) {
+ if (!is_tilde_slash(name) && !is_rel_or_absolute_filename(name) &&
+ !starts_with(name, "http://") && !starts_with(name, "https://")) {
+ std::string remote_file(pj_context_get_url_endpoint(ctx));
+ if (!remote_file.empty()) {
+ if (remote_file.back() != '/') {
+ remote_file += '/';
+ }
+ remote_file += name;
+ auto pos = remote_file.rfind('.');
+ if (pos + 4 == remote_file.size()) {
+ remote_file = remote_file.substr(0, pos) + ".tif";
+ } else {
+ // For example for resource files like 'alaska'
+ remote_file += ".tif";
+ }
+ }
+ return remote_file;
+ }
+ return name;
+}
+
+// ---------------------------------------------------------------------------
+
+/** Define a custom set of callbacks for network access.
+ *
+ * All callbacks should be provided (non NULL pointers).
+ *
+ * @param ctx PROJ context, or NULL
+ * @param open_cbk Callback to open a remote file given its URL
+ * @param close_cbk Callback to close a remote file.
+ * @param get_header_value_cbk Callback to get HTTP headers
+ * @param read_range_cbk Callback to read a range of bytes inside a remote file.
+ * @param user_data Arbitrary pointer provided by the user, and passed to the
+ * above callbacks. May be NULL.
+ * @return TRUE in case of success.
+ * @since 7.0
+ */
+int proj_context_set_network_callbacks(
+ PJ_CONTEXT *ctx, proj_network_open_cbk_type open_cbk,
+ proj_network_close_cbk_type close_cbk,
+ proj_network_get_header_value_cbk_type get_header_value_cbk,
+ proj_network_read_range_type read_range_cbk, void *user_data) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ if (!open_cbk || !close_cbk || !get_header_value_cbk || !read_range_cbk) {
+ return false;
+ }
+ ctx->networking.open = open_cbk;
+ ctx->networking.close = close_cbk;
+ ctx->networking.get_header_value = get_header_value_cbk;
+ ctx->networking.read_range = read_range_cbk;
+ ctx->networking.user_data = user_data;
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+/** Enable or disable network access.
+*
+* This overrides the default endpoint in the PROJ configuration file or with
+* the PROJ_NETWORK environment variable.
+*
+* @param ctx PROJ context, or NULL
+* @param enable TRUE if network access is allowed.
+* @return TRUE if network access is possible. That is either libcurl is
+* available, or an alternate interface has been set.
+* @since 7.0
+*/
+int proj_context_set_enable_network(PJ_CONTEXT *ctx, int enable) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ // Load ini file, now so as to override its network settings
+ pj_load_ini(ctx);
+ ctx->networking.enabled_env_variable_checked = true;
+ ctx->networking.enabled = enable != FALSE;
+#ifdef CURL_ENABLED
+ return ctx->networking.enabled;
+#else
+ return ctx->networking.enabled &&
+ ctx->networking.open != NS_PROJ::no_op_network_open;
+#endif
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** Define the URL endpoint to query for remote grids.
+*
+* This overrides the default endpoint in the PROJ configuration file or with
+* the PROJ_NETWORK_ENDPOINT environment variable.
+*
+* @param ctx PROJ context, or NULL
+* @param url Endpoint URL. Must NOT be NULL.
+* @since 7.0
+*/
+void proj_context_set_url_endpoint(PJ_CONTEXT *ctx, const char *url) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ // Load ini file, now so as to override its network settings
+ pj_load_ini(ctx);
+ ctx->endpoint = url;
+}
+
+// ---------------------------------------------------------------------------
+
+/** Enable or disable the local cache of grid chunks
+*
+* This overrides the setting in the PROJ configuration file.
+*
+* @param ctx PROJ context, or NULL
+* @param enabled TRUE if the cache is enabled.
+* @since 7.0
+*/
+void proj_grid_cache_set_enable(PJ_CONTEXT *ctx, int enabled) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ // Load ini file, now so as to override its settings
+ pj_load_ini(ctx);
+ ctx->gridChunkCache.enabled = enabled != FALSE;
+}
+
+// ---------------------------------------------------------------------------
+
+/** Override, for the considered context, the path and file of the local
+* cache of grid chunks.
+*
+* @param ctx PROJ context, or NULL
+* @param fullname Full name to the cache (encoded in UTF-8). If set to NULL,
+* caching will be disabled.
+* @since 7.0
+*/
+void proj_grid_cache_set_filename(PJ_CONTEXT *ctx, const char *fullname) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ // Load ini file, now so as to override its settings
+ pj_load_ini(ctx);
+ ctx->gridChunkCache.filename = fullname ? fullname : std::string();
+}
+
+// ---------------------------------------------------------------------------
+
+/** Override, for the considered context, the maximum size of the local
+* cache of grid chunks.
+*
+* @param ctx PROJ context, or NULL
+* @param max_size_MB Maximum size, in mega-bytes (1024*1024 bytes), or
+* negative value to set unlimited size.
+* @since 7.0
+*/
+void proj_grid_cache_set_max_size(PJ_CONTEXT *ctx, int max_size_MB) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ // Load ini file, now so as to override its settings
+ pj_load_ini(ctx);
+ ctx->gridChunkCache.max_size =
+ max_size_MB < 0 ? -1
+ : static_cast<long long>(max_size_MB) * 1024 * 1024;
+ if (max_size_MB == 0) {
+ // For debug purposes only
+ const char *env_var = getenv("PROJ_GRID_CACHE_MAX_SIZE_BYTES");
+ if (env_var && env_var[0] != '\0') {
+ ctx->gridChunkCache.max_size = atoi(env_var);
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** Override, for the considered context, the time-to-live delay for
+* re-checking if the cached properties of files are still up-to-date.
+*
+* @param ctx PROJ context, or NULL
+* @param ttl_seconds Delay in seconds. Use negative value for no expiration.
+* @since 7.0
+*/
+void proj_grid_cache_set_ttl(PJ_CONTEXT *ctx, int ttl_seconds) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ // Load ini file, now so as to override its settings
+ pj_load_ini(ctx);
+ ctx->gridChunkCache.ttl = ttl_seconds;
+}
+
+// ---------------------------------------------------------------------------
+
+/** Clear the local cache of grid chunks.
+*
+* @param ctx PROJ context, or NULL
+* @since 7.0
+*/
+void proj_grid_cache_clear(PJ_CONTEXT *ctx) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ NS_PROJ::gNetworkChunkCache.clearDiskChunkCache(ctx);
+}
+
+// ---------------------------------------------------------------------------
+
+/** Return if a file must be downloaded or is already available in the
+ * PROJ user-writable directory.
+ *
+ * The file will be determinted to have to be downloaded if it does not exist
+ * yet in the user-writable directory, or if it is determined that a more recent
+ * version exists. To determine if a more recent version exists, PROJ will
+ * use the "downloaded_file_properties" table of its grid cache database.
+ * Consequently files manually placed in the user-writable
+ * directory without using this function would be considered as
+ * non-existing/obsolete and would be unconditionnaly downloaded again.
+ *
+ * This function can only be used if networking is enabled, and either
+ * the default curl network API or a custom one have been installed.
+ *
+ * @param ctx PROJ context, or NULL
+ * @param url_or_filename URL or filename (without directory component)
+ * @param ignore_ttl_setting If set to FALSE, PROJ will only check the
+ * recentness of an already downloaded file, if
+ * the delay between the last time it has been
+ * verified and the current time exceeds the TTL
+ * setting. This can save network accesses.
+ * If set to TRUE, PROJ will unconditionnally
+ * check from the server the recentness of the file.
+ * @return TRUE if the file must be downloaded with proj_download_file()
+ * @since 7.0
+ */
+
+int proj_is_download_needed(PJ_CONTEXT *ctx, const char *url_or_filename,
+ int ignore_ttl_setting) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ if (!pj_context_is_network_enabled(ctx)) {
+ pj_log(ctx, PJ_LOG_ERROR, "Networking capabilities are not enabled");
+ return false;
+ }
+
+ const auto url(build_url(ctx, url_or_filename));
+ const char *filename = strrchr(url.c_str(), '/');
+ if (filename == nullptr)
+ return false;
+ const auto localFilename(
+ pj_context_get_user_writable_directory(ctx, false) + filename);
+
+ auto f = NS_PROJ::FileManager::open(ctx, localFilename.c_str(),
+ NS_PROJ::FileAccess::READ_ONLY);
+ if (!f) {
+ return true;
+ }
+ f.reset();
+
+ auto diskCache = NS_PROJ::DiskChunkCache::open(ctx);
+ if (!diskCache)
+ return false;
+ auto stmt =
+ diskCache->prepare("SELECT lastChecked, fileSize, lastModified, etag "
+ "FROM downloaded_file_properties WHERE url = ?");
+ if (!stmt)
+ return true;
+ stmt->bindText(url.c_str());
+ if (stmt->execute() != SQLITE_ROW) {
+ return true;
+ }
+
+ NS_PROJ::FileProperties cachedProps;
+ cachedProps.lastChecked = stmt->getInt64();
+ cachedProps.size = stmt->getInt64();
+ const char *lastModified = stmt->getText();
+ cachedProps.lastModified = lastModified ? lastModified : std::string();
+ const char *etag = stmt->getText();
+ cachedProps.etag = etag ? etag : std::string();
+
+ if (!ignore_ttl_setting) {
+ const auto ttl = NS_PROJ::pj_context_get_grid_cache_ttl(ctx);
+ if (ttl > 0) {
+ time_t curTime;
+ time(&curTime);
+ if (curTime > cachedProps.lastChecked + ttl) {
+
+ unsigned char dummy;
+ size_t size_read = 0;
+ std::string errorBuffer;
+ errorBuffer.resize(1024);
+ auto handle = ctx->networking.open(
+ ctx, url.c_str(), 0, 1, &dummy, &size_read,
+ errorBuffer.size(), &errorBuffer[0],
+ ctx->networking.user_data);
+ if (!handle) {
+ errorBuffer.resize(strlen(errorBuffer.data()));
+ pj_log(ctx, PJ_LOG_ERROR, "Cannot open %s: %s", url.c_str(),
+ errorBuffer.c_str());
+ return false;
+ }
+ NS_PROJ::FileProperties props;
+ if (!NS_PROJ::NetworkFile::get_props_from_headers(ctx, handle,
+ props)) {
+ ctx->networking.close(ctx, handle,
+ ctx->networking.user_data);
+ return false;
+ }
+ ctx->networking.close(ctx, handle, ctx->networking.user_data);
+
+ if (props.size != cachedProps.size ||
+ props.lastModified != cachedProps.lastModified ||
+ props.etag != cachedProps.etag) {
+ return true;
+ }
+
+ stmt = diskCache->prepare(
+ "UPDATE downloaded_file_properties SET lastChecked = ? "
+ "WHERE url = ?");
+ if (!stmt)
+ return false;
+ stmt->bindInt64(curTime);
+ stmt->bindText(url.c_str());
+ if (stmt->execute() != SQLITE_DONE) {
+ auto hDB = diskCache->handle();
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return false;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+/** Download a file in the PROJ user-writable directory.
+ *
+ * The file will only be downloaded if it does not exist yet in the
+ * user-writable directory, or if it is determined that a more recent
+ * version exists. To determine if a more recent version exists, PROJ will
+ * use the "downloaded_file_properties" table of its grid cache database.
+ * Consequently files manually placed in the user-writable
+ * directory without using this function would be considered as
+ * non-existing/obsolete and would be unconditionnaly downloaded again.
+ *
+ * This function can only be used if networking is enabled, and either
+ * the default curl network API or a custom one have been installed.
+ *
+ * @param ctx PROJ context, or NULL
+ * @param url_or_filename URL or filename (without directory component)
+ * @param ignore_ttl_setting If set to FALSE, PROJ will only check the
+ * recentness of an already downloaded file, if
+ * the delay between the last time it has been
+ * verified and the current time exceeds the TTL
+ * setting. This can save network accesses.
+ * If set to TRUE, PROJ will unconditionnally
+ * check from the server the recentness of the file.
+ * @param progress_cbk Progress callback, or NULL.
+ * The passed percentage is in the [0, 1] range.
+ * The progress callback must return TRUE
+ * if download must be continued.
+ * @param user_data User data to provide to the progress callback, or NULL
+ * @return TRUE if the download was successful (or not needed)
+ * @since 7.0
+ */
+
+int proj_download_file(PJ_CONTEXT *ctx, const char *url_or_filename,
+ int ignore_ttl_setting,
+ int (*progress_cbk)(PJ_CONTEXT *, double pct,
+ void *user_data),
+ void *user_data) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ if (!pj_context_is_network_enabled(ctx)) {
+ pj_log(ctx, PJ_LOG_ERROR, "Networking capabilities are not enabled");
+ return false;
+ }
+ if (!proj_is_download_needed(ctx, url_or_filename, ignore_ttl_setting)) {
+ return true;
+ }
+
+ const auto url(build_url(ctx, url_or_filename));
+ const char *filename = strrchr(url.c_str(), '/');
+ if (filename == nullptr)
+ return false;
+ const auto localFilename(pj_context_get_user_writable_directory(ctx, true) +
+ filename);
+
+#ifdef _WIN32
+ const int nPID = GetCurrentProcessId();
+#else
+ const int nPID = getpid();
+#endif
+ char szUniqueSuffix[128];
+ snprintf(szUniqueSuffix, sizeof(szUniqueSuffix), "%d_%p", nPID, &url);
+ const auto localFilenameTmp(localFilename + szUniqueSuffix);
+ auto f = NS_PROJ::FileManager::open(ctx, localFilenameTmp.c_str(),
+ NS_PROJ::FileAccess::CREATE);
+ if (!f) {
+ pj_log(ctx, PJ_LOG_ERROR, "Cannot create %s", localFilenameTmp.c_str());
+ return false;
+ }
+
+ constexpr size_t FULL_FILE_CHUNK_SIZE = 1024 * 1024;
+ std::vector<unsigned char> buffer(FULL_FILE_CHUNK_SIZE);
+ // For testing purposes only
+ const char *env_var_PROJ_FULL_FILE_CHUNK_SIZE =
+ getenv("PROJ_FULL_FILE_CHUNK_SIZE");
+ if (env_var_PROJ_FULL_FILE_CHUNK_SIZE &&
+ env_var_PROJ_FULL_FILE_CHUNK_SIZE[0] != '\0') {
+ buffer.resize(atoi(env_var_PROJ_FULL_FILE_CHUNK_SIZE));
+ }
+ size_t size_read = 0;
+ std::string errorBuffer;
+ errorBuffer.resize(1024);
+ auto handle = ctx->networking.open(
+ ctx, url.c_str(), 0, buffer.size(), &buffer[0], &size_read,
+ errorBuffer.size(), &errorBuffer[0], ctx->networking.user_data);
+ if (!handle) {
+ errorBuffer.resize(strlen(errorBuffer.data()));
+ pj_log(ctx, PJ_LOG_ERROR, "Cannot open %s: %s", url.c_str(),
+ errorBuffer.c_str());
+ f.reset();
+ NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str());
+ return false;
+ }
+
+ time_t curTime;
+ time(&curTime);
+ NS_PROJ::FileProperties props;
+ if (!NS_PROJ::NetworkFile::get_props_from_headers(ctx, handle, props)) {
+ ctx->networking.close(ctx, handle, ctx->networking.user_data);
+ f.reset();
+ NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str());
+ return false;
+ }
+
+ if (size_read <
+ std::min(static_cast<unsigned long long>(buffer.size()), props.size)) {
+ pj_log(ctx, PJ_LOG_ERROR, "Did not get as many bytes as expected");
+ ctx->networking.close(ctx, handle, ctx->networking.user_data);
+ f.reset();
+ NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str());
+ return false;
+ }
+ if (f->write(buffer.data(), size_read) != size_read) {
+ pj_log(ctx, PJ_LOG_ERROR, "Write error");
+ ctx->networking.close(ctx, handle, ctx->networking.user_data);
+ f.reset();
+ NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str());
+ return false;
+ }
+
+ unsigned long long totalDownloaded = size_read;
+ while (totalDownloaded < props.size) {
+ if (totalDownloaded + buffer.size() > props.size) {
+ buffer.resize(static_cast<size_t>(props.size - totalDownloaded));
+ }
+ errorBuffer.resize(1024);
+ size_read = ctx->networking.read_range(
+ ctx, handle, totalDownloaded, buffer.size(), &buffer[0],
+ errorBuffer.size(), &errorBuffer[0], ctx->networking.user_data);
+
+ if (size_read < buffer.size()) {
+ pj_log(ctx, PJ_LOG_ERROR, "Did not get as many bytes as expected");
+ ctx->networking.close(ctx, handle, ctx->networking.user_data);
+ f.reset();
+ NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str());
+ return false;
+ }
+ if (f->write(buffer.data(), size_read) != size_read) {
+ pj_log(ctx, PJ_LOG_ERROR, "Write error");
+ ctx->networking.close(ctx, handle, ctx->networking.user_data);
+ f.reset();
+ NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str());
+ return false;
+ }
+
+ totalDownloaded += size_read;
+ if (progress_cbk &&
+ !progress_cbk(ctx, double(totalDownloaded) / props.size,
+ user_data)) {
+ ctx->networking.close(ctx, handle, ctx->networking.user_data);
+ f.reset();
+ NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str());
+ return false;
+ }
+ }
+
+ ctx->networking.close(ctx, handle, ctx->networking.user_data);
+ f.reset();
+ NS_PROJ::FileManager::unlink(ctx, localFilename.c_str());
+ if (!NS_PROJ::FileManager::rename(ctx, localFilenameTmp.c_str(),
+ localFilename.c_str())) {
+ pj_log(ctx, PJ_LOG_ERROR, "Cannot rename %s to %s",
+ localFilenameTmp.c_str(), localFilename.c_str());
+ return false;
+ }
+
+ auto diskCache = NS_PROJ::DiskChunkCache::open(ctx);
+ if (!diskCache)
+ return false;
+ auto stmt =
+ diskCache->prepare("SELECT lastChecked, fileSize, lastModified, etag "
+ "FROM downloaded_file_properties WHERE url = ?");
+ if (!stmt)
+ return false;
+ stmt->bindText(url.c_str());
+
+ props.lastChecked = curTime;
+ auto hDB = diskCache->handle();
+
+ if (stmt->execute() == SQLITE_ROW) {
+ stmt = diskCache->prepare(
+ "UPDATE downloaded_file_properties SET lastChecked = ?, "
+ "fileSize = ?, lastModified = ?, etag = ? "
+ "WHERE url = ?");
+ if (!stmt)
+ return false;
+ stmt->bindInt64(props.lastChecked);
+ stmt->bindInt64(props.size);
+ if (props.lastModified.empty())
+ stmt->bindNull();
+ else
+ stmt->bindText(props.lastModified.c_str());
+ if (props.etag.empty())
+ stmt->bindNull();
+ else
+ stmt->bindText(props.etag.c_str());
+ stmt->bindText(url.c_str());
+ if (stmt->execute() != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return false;
+ }
+ } else {
+ stmt = diskCache->prepare(
+ "INSERT INTO downloaded_file_properties (url, lastChecked, "
+ "fileSize, lastModified, etag) VALUES "
+ "(?,?,?,?,?)");
+ if (!stmt)
+ return false;
+ stmt->bindText(url.c_str());
+ stmt->bindInt64(props.lastChecked);
+ stmt->bindInt64(props.size);
+ if (props.lastModified.empty())
+ stmt->bindNull();
+ else
+ stmt->bindText(props.lastModified.c_str());
+ if (props.etag.empty())
+ stmt->bindNull();
+ else
+ stmt->bindText(props.etag.c_str());
+ if (stmt->execute() != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return false;
+ }
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+bool pj_context_is_network_enabled(PJ_CONTEXT *ctx) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ if (ctx->networking.enabled_env_variable_checked) {
+ return ctx->networking.enabled;
+ }
+ const char *enabled = getenv("PROJ_NETWORK");
+ if (enabled && enabled[0] != '\0') {
+ ctx->networking.enabled = ci_equal(enabled, "ON") ||
+ ci_equal(enabled, "YES") ||
+ ci_equal(enabled, "TRUE");
+ }
+ pj_load_ini(ctx);
+ ctx->networking.enabled_env_variable_checked = true;
+ return ctx->networking.enabled;
+}
+
+// ---------------------------------------------------------------------------
+
+std::string pj_context_get_grid_cache_filename(PJ_CONTEXT *ctx) {
+ pj_load_ini(ctx);
+ if (!ctx->gridChunkCache.filename.empty()) {
+ return ctx->gridChunkCache.filename;
+ }
+ const std::string path(pj_context_get_user_writable_directory(ctx, true));
+ ctx->gridChunkCache.filename = path + "/cache.db";
+ return ctx->gridChunkCache.filename;
+}
+
+//! @endcond
diff --git a/src/open_lib.cpp b/src/open_lib.cpp
deleted file mode 100644
index ae387281..00000000
--- a/src/open_lib.cpp
+++ /dev/null
@@ -1,576 +0,0 @@
-/******************************************************************************
- * Project: PROJ.4
- * Purpose: Implementation of pj_open_lib(), and pj_set_finder(). These
- * provide a standard interface for opening projections support
- * data files.
- * Author: Gerald Evenden, Frank Warmerdam <warmerdam@pobox.com>
- *
- ******************************************************************************
- * Copyright (c) 1995, Gerald Evenden
- * Copyright (c) 2002, Frank Warmerdam <warmerdam@pobox.com>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included
- * in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- *****************************************************************************/
-
-#define PJ_LIB__
-
-#ifndef FROM_PROJ_CPP
-#define FROM_PROJ_CPP
-#endif
-
-#include <assert.h>
-#include <errno.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "proj/internal/internal.hpp"
-
-#include "proj_internal.h"
-#include "filemanager.hpp"
-
-static const char * proj_lib_name =
-#ifdef PROJ_LIB
-PROJ_LIB;
-#else
-nullptr;
-#endif
-
-using namespace NS_PROJ::internal;
-
-/************************************************************************/
-/* pj_set_finder() */
-/************************************************************************/
-
-void pj_set_finder( const char *(*new_finder)(const char *) )
-
-{
- auto ctx = pj_get_default_ctx();
- if( ctx ) {
- ctx->file_finder_legacy = new_finder;
- }
-}
-
-/************************************************************************/
-/* proj_context_set_file_finder() */
-/************************************************************************/
-
-/** \brief Assign a file finder callback to a context.
- *
- * This callback will be used whenever PROJ must open one of its resource files
- * (proj.db database, grids, etc...)
- *
- * The callback will be called with the context currently in use at the moment
- * where it is used (not necessarily the one provided during this call), and
- * with the provided user_data (which may be NULL).
- * The user_data must remain valid during the whole lifetime of the context.
- *
- * A finder set on the default context will be inherited by contexts created
- * later.
- *
- * @param ctx PROJ context, or NULL for the default context.
- * @param finder Finder callback. May be NULL
- * @param user_data User data provided to the finder callback. May be NULL.
- *
- * @since PROJ 6.0
- */
-void proj_context_set_file_finder(PJ_CONTEXT *ctx, proj_file_finder finder,
- void* user_data)
-{
- if( !ctx )
- ctx = pj_get_default_ctx();
- if( !ctx )
- return;
- ctx->file_finder = finder;
- ctx->file_finder_user_data = user_data;
-}
-
-/************************************************************************/
-/* proj_context_set_search_paths() */
-/************************************************************************/
-
-
-/** \brief Sets search paths.
- *
- * Those search paths will be used whenever PROJ must open one of its resource files
- * (proj.db database, grids, etc...)
- *
- * If set on the default context, they will be inherited by contexts created
- * later.
- *
- * @param ctx PROJ context, or NULL for the default context.
- * @param count_paths Number of paths. 0 if paths == NULL.
- * @param paths Paths. May be NULL.
- *
- * @since PROJ 6.0
- */
-void proj_context_set_search_paths(PJ_CONTEXT *ctx,
- int count_paths,
- const char* const* paths)
-{
- if( !ctx )
- ctx = pj_get_default_ctx();
- if( !ctx )
- return;
- try {
- std::vector<std::string> vector_of_paths;
- for (int i = 0; i < count_paths; i++)
- {
- vector_of_paths.emplace_back(paths[i]);
- }
- ctx->set_search_paths(vector_of_paths);
- } catch( const std::exception& )
- {
- }
-}
-
-/************************************************************************/
-/* pj_set_searchpath() */
-/* */
-/* Path control for callers that can't practically provide */
-/* pj_set_finder() style callbacks. Call with (0,NULL) as args */
-/* to clear the searchpath set. */
-/************************************************************************/
-
-void pj_set_searchpath ( int count, const char **path )
-{
- proj_context_set_search_paths( nullptr, count, const_cast<const char* const*>(path) );
-}
-
-#ifdef _WIN32
-#include <windows.h>
-#include <sys/stat.h>
-static const char *get_path_from_win32_projlib(const char *name, std::string& out) {
- /* Check if proj.db lieves in a share/proj dir parallel to bin/proj.dll */
- /* Based in https://stackoverflow.com/questions/9112893/how-to-get-path-to-executable-in-c-running-on-windows */
-
- DWORD path_size = 1024;
-
- for (;;) {
- out.resize(path_size);
- memset(&out[0], 0, path_size);
- DWORD result = GetModuleFileNameA(nullptr, &out[0], path_size - 1);
- DWORD last_error = GetLastError();
-
- if (result == 0) {
- return nullptr;
- }
- else if (result == path_size - 1) {
- if (ERROR_INSUFFICIENT_BUFFER != last_error) {
- return nullptr;
- }
- path_size = path_size * 2;
- }
- else {
- break;
- }
- }
- // Now remove the program's name. It was (example) "C:\programs\gmt6\bin\gdal_translate.exe"
- size_t k = strlen(out.c_str());
- while (k > 0 && out[--k] != '\\') {}
- out.resize(k);
-
- out += "/../share/proj/";
- out += name;
-
- struct stat fileInfo;
- if (stat(out.c_str(), &fileInfo) == 0) // Check if file exists (probably there are simpler ways)
- return out.c_str();
- else {
- return nullptr;
- }
-}
-#endif
-
-/************************************************************************/
-/* pj_open_lib_internal() */
-/************************************************************************/
-
-#ifdef WIN32
-static const char dir_chars[] = "/\\";
-static const char dirSeparator = ';';
-#else
-static const char dir_chars[] = "/";
-static const char dirSeparator = ':';
-#endif
-
-static bool is_tilde_slash(const char* name)
-{
- return *name == '~' && strchr(dir_chars,name[1]);
-}
-
-static bool is_rel_or_absolute_filename(const char *name)
-{
- return strchr(dir_chars,*name)
- || (*name == '.' && strchr(dir_chars,name[1]))
- || (!strncmp(name, "..", 2) && strchr(dir_chars,name[2]))
- || (name[0] != '\0' && name[1] == ':' && strchr(dir_chars,name[2]));
-}
-
-static bool ignoreUserWritableDirectory()
-{
- // Env var mostly for testing purposes and being independent from
- // an existing installation
- const char* envVarIgnoreUserWritableDirectory =
- getenv("PROJ_IGNORE_USER_WRITABLE_DIRECTORY");
- return envVarIgnoreUserWritableDirectory != nullptr &&
- envVarIgnoreUserWritableDirectory[0] != '\0';
-}
-
-static void*
-pj_open_lib_internal(projCtx ctx, const char *name, const char *mode,
- void* (*open_file)(projCtx, const char*, const char*),
- char* out_full_filename, size_t out_full_filename_size) {
- try {
- std::string fname;
- const char *sysname = nullptr;
- void* fid = nullptr;
-
- if( ctx == nullptr ) {
- ctx = pj_get_default_ctx();
- }
-
- if( out_full_filename != nullptr && out_full_filename_size > 0 )
- out_full_filename[0] = '\0';
-
- /* check if ~/name */
- if (is_tilde_slash(name))
- if ((sysname = getenv("HOME")) != nullptr) {
- fname = sysname;
- fname += DIR_CHAR;
- fname += name;
- sysname = fname.c_str();
- } else
- return nullptr;
-
- /* or fixed path: /name, ./name or ../name or http[s]:// */
- else if (is_rel_or_absolute_filename(name)
- || starts_with(name, "http://")
- || starts_with(name, "https://"))
- sysname = name;
-
- /* or try to use application provided file finder */
- else if( ctx->file_finder != nullptr && (sysname = ctx->file_finder( ctx, name, ctx->file_finder_user_data )) != nullptr )
- ;
-
- else if( ctx->file_finder_legacy != nullptr && (sysname = ctx->file_finder_legacy( name )) != nullptr )
- ;
-
- /* The user has search paths set */
- else if( !ctx->search_paths.empty() ) {
- for( const auto& path: ctx->search_paths ) {
- try {
- fname = path;
- fname += DIR_CHAR;
- fname += name;
- sysname = fname.c_str();
- fid = open_file(ctx, sysname, mode);
- } catch( const std::exception& )
- {
- }
- if( fid )
- break;
- }
- }
-
- else if( !ignoreUserWritableDirectory() &&
- (fid = open_file(ctx,
- (pj_context_get_user_writable_directory(ctx, false) +
- DIR_CHAR + name).c_str(), mode)) != nullptr ) {
- fname = pj_context_get_user_writable_directory(ctx, false);
- fname += DIR_CHAR;
- fname += name;
- sysname = fname.c_str();
- }
-
- /* if is environment PROJ_LIB defined */
- else if ((sysname = getenv("PROJ_LIB")) != nullptr) {
- auto paths = NS_PROJ::internal::split(std::string(sysname), dirSeparator);
- for( const auto& path: paths ) {
- fname = path;
- fname += DIR_CHAR;
- fname += name;
- sysname = fname.c_str();
- fid = open_file(ctx, sysname, mode);
- if( fid )
- break;
- }
-#ifdef _WIN32
- /* check if it lives in a ../share/proj dir of the proj dll */
- } else if ((sysname = get_path_from_win32_projlib(name, fname)) != nullptr) {
-#endif
- /* or hardcoded path */
- } else if ((sysname = proj_lib_name) != nullptr) {
- fname = sysname;
- fname += DIR_CHAR;
- fname += name;
- sysname = fname.c_str();
- /* just try it bare bones */
- } else {
- sysname = name;
- }
-
- assert(sysname); // to make Coverity Scan happy
- if ( fid != nullptr || (fid = open_file(ctx, sysname, mode)) != nullptr)
- {
- if( out_full_filename != nullptr && out_full_filename_size > 0 )
- {
- // cppcheck-suppress nullPointer
- strncpy(out_full_filename, sysname, out_full_filename_size);
- out_full_filename[out_full_filename_size-1] = '\0';
- }
- errno = 0;
- }
-
- if( ctx->last_errno == 0 && errno != 0 )
- pj_ctx_set_errno( ctx, errno );
-
- pj_log( ctx, PJ_LOG_DEBUG_MAJOR,
- "pj_open_lib(%s): call fopen(%s) - %s",
- name, sysname,
- fid == nullptr ? "failed" : "succeeded" );
-
- return(fid);
- }
- catch( const std::exception& ) {
-
- pj_log( ctx, PJ_LOG_DEBUG_MAJOR,
- "pj_open_lib(%s): out of memory",
- name );
-
- return nullptr;
- }
-}
-
-/************************************************************************/
-/* pj_open_file_with_manager() */
-/************************************************************************/
-
-static void* pj_open_file_with_manager(projCtx ctx, const char *name,
- const char * /* mode */)
-{
- return NS_PROJ::FileManager::open(ctx, name).release();
-}
-
-/************************************************************************/
-/* FileManager::open_resource_file() */
-/************************************************************************/
-
-std::unique_ptr<NS_PROJ::File> NS_PROJ::FileManager::open_resource_file(
- projCtx ctx, const char *name)
-{
- auto file = std::unique_ptr<NS_PROJ::File>(
- reinterpret_cast<NS_PROJ::File*>(
- pj_open_lib_internal(ctx, name, "rb",
- pj_open_file_with_manager,
- nullptr, 0)));
- if( file == nullptr &&
- !is_tilde_slash(name) &&
- !is_rel_or_absolute_filename(name) &&
- !starts_with(name, "http://") &&
- !starts_with(name, "https://") &&
- pj_context_is_network_enabled(ctx) ) {
- std::string remote_file(pj_context_get_url_endpoint(ctx));
- if( !remote_file.empty() ) {
- if( remote_file.back() != '/' ) {
- remote_file += '/';
- }
- remote_file += name;
- auto pos = remote_file.rfind('.');
- if( pos + 4 == remote_file.size() ) {
- remote_file = remote_file.substr(0, pos) + ".tif";
- file = open(ctx, remote_file.c_str());
- if( file ) {
- pj_log( ctx, PJ_LOG_DEBUG_MAJOR,
- "Using %s", remote_file.c_str() );
- pj_ctx_set_errno( ctx, 0 );
- }
- } else {
- // For example for resource files like 'alaska'
- auto remote_file_tif = remote_file + ".tif";
- file = open(ctx, remote_file_tif.c_str());
- if( file ) {
- pj_log( ctx, PJ_LOG_DEBUG_MAJOR,
- "Using %s", remote_file_tif.c_str() );
- pj_ctx_set_errno( ctx, 0 );
- } else {
- // Init files
- file = open(ctx, remote_file.c_str());
- if( file ) {
- pj_log( ctx, PJ_LOG_DEBUG_MAJOR,
- "Using %s", remote_file.c_str() );
- pj_ctx_set_errno( ctx, 0 );
- }
- }
- }
- }
- }
- return file;
-}
-
-/************************************************************************/
-/* pj_open_lib() */
-/************************************************************************/
-
-#ifndef REMOVE_LEGACY_SUPPORT
-
-// Used by following legacy function
-static void* pj_ctx_fopen_adapter(projCtx ctx, const char *name, const char *mode)
-{
- return pj_ctx_fopen(ctx, name, mode);
-}
-
-// Legacy function
-PAFile
-pj_open_lib(projCtx ctx, const char *name, const char *mode) {
- return (PAFile)pj_open_lib_internal(ctx, name, mode, pj_ctx_fopen_adapter, nullptr, 0);
-}
-
-#endif // REMOVE_LEGACY_SUPPORT
-
-/************************************************************************/
-/* pj_find_file() */
-/************************************************************************/
-
-
-/** Returns the full filename corresponding to a proj resource file specified
- * as a short filename.
- *
- * @param ctx context.
- * @param short_filename short filename (e.g. egm96_15.gtx). Must not be NULL.
- * @param out_full_filename output buffer, of size out_full_filename_size, that
- * will receive the full filename on success.
- * Will be zero-terminated.
- * @param out_full_filename_size size of out_full_filename.
- * @return 1 if the file was found, 0 otherwise.
- */
-int pj_find_file(projCtx ctx, const char *short_filename,
- char* out_full_filename, size_t out_full_filename_size)
-{
- auto f = reinterpret_cast<NS_PROJ::File*>(
- pj_open_lib_internal(ctx, short_filename, "rb",
- pj_open_file_with_manager,
- out_full_filename,
- out_full_filename_size));
- if( f != nullptr )
- {
- delete f;
- return 1;
- }
- return 0;
-}
-
-/************************************************************************/
-/* pj_context_get_url_endpoint() */
-/************************************************************************/
-
-std::string pj_context_get_url_endpoint(PJ_CONTEXT* ctx)
-{
- if( !ctx->endpoint.empty() ) {
- return ctx->endpoint;
- }
- pj_load_ini(ctx);
- return ctx->endpoint;
-}
-
-/************************************************************************/
-/* trim() */
-/************************************************************************/
-
-static std::string trim(const std::string& s) {
- const auto first = s.find_first_not_of(' ');
- const auto last = s.find_last_not_of(' ');
- if( first == std::string::npos || last == std::string::npos ) {
- return std::string();
- }
- return s.substr(first, last - first + 1);
-}
-
-/************************************************************************/
-/* pj_load_ini() */
-/************************************************************************/
-
-void pj_load_ini(projCtx ctx)
-{
- if( ctx->iniFileLoaded )
- return;
-
- const char* endpoint_from_env = getenv("PROJ_NETWORK_ENDPOINT");
- if( endpoint_from_env && endpoint_from_env[0] != '\0' ) {
- ctx->endpoint = endpoint_from_env;
- }
-
- ctx->iniFileLoaded = true;
- auto file = std::unique_ptr<NS_PROJ::File>(
- reinterpret_cast<NS_PROJ::File*>(
- pj_open_lib_internal(ctx, "proj.ini", "rb",
- pj_open_file_with_manager,
- nullptr, 0)));
- if( !file )
- return;
- file->seek(0, SEEK_END);
- const auto filesize = file->tell();
- if( filesize == 0 || filesize > 100 * 1024U )
- return;
- file->seek(0, SEEK_SET);
- std::string content;
- content.resize(static_cast<size_t>(filesize));
- const auto nread = file->read(&content[0], content.size());
- if( nread != content.size() )
- return;
- content += '\n';
- size_t pos = 0;
- while( pos != std::string::npos ) {
- const auto eol = content.find_first_of("\r\n", pos);
- if( eol == std::string::npos ) {
- break;
- }
-
- const auto equal = content.find('=', pos);
- if( equal < eol )
- {
- const auto key = trim(content.substr(pos, equal-pos));
- const auto value = trim(content.substr(equal + 1,
- eol - (equal+1)));
- if( ctx->endpoint.empty() && key == "cdn_endpoint" ) {
- ctx->endpoint = value;
- } else if ( key == "network" ) {
- const char *enabled = getenv("PROJ_NETWORK");
- if (enabled == nullptr || enabled[0] == '\0') {
- ctx->networking.enabled = ci_equal(value, "ON") ||
- ci_equal(value, "YES") ||
- ci_equal(value, "TRUE");
- }
- } else if ( key == "cache_enabled" ) {
- ctx->gridChunkCache.enabled = ci_equal(value, "ON") ||
- ci_equal(value, "YES") ||
- ci_equal(value, "TRUE");
- } else if ( key == "cache_size_MB" ) {
- const int val = atoi(value.c_str());
- ctx->gridChunkCache.max_size = val > 0 ?
- static_cast<long long>(val) * 1024 * 1024 : -1;
- } else if ( key == "cache_ttl_sec" ) {
- ctx->gridChunkCache.ttl = atoi(value.c_str());
- }
- }
-
- pos = content.find_first_not_of("\r\n", eol);
- }
-}
diff --git a/src/proj.h b/src/proj.h
index 8add29ac..2a05fbc9 100644
--- a/src/proj.h
+++ b/src/proj.h
@@ -355,7 +355,66 @@ int PROJ_DLL proj_context_get_use_proj4_init_rules(PJ_CONTEXT *ctx, int from_leg
/*! @endcond */
-/** Opaque structure for PROJ. Implementations might cast it to their
+/** Opaque structure for PROJ for a file handle. Implementations might cast it to their
+ * structure/class of choice. */
+typedef struct PROJ_FILE_HANDLE PROJ_FILE_HANDLE;
+
+/** Open access / mode */
+typedef enum PROJ_OPEN_ACCESS
+{
+ /** Read-only access. Equivalent to "rb" */
+ PROJ_OPEN_ACCESS_READ_ONLY,
+
+ /** Read-update access. File should be created if not existing. Equivalent to "r+b" */
+ PROJ_OPEN_ACCESS_READ_UPDATE,
+
+ /** Create access. File should be truncated to 0-byte if already existing. Equivalent to "w+b" */
+ PROJ_OPEN_ACCESS_CREATE
+} PROJ_OPEN_ACCESS;
+
+/** File API callbacks */
+typedef struct PROJ_FILE_API
+{
+ /** Version of this structure. Should be set to 1 currently. */
+ int version;
+
+ /** Open file. Return NULL if error */
+ PROJ_FILE_HANDLE* (*open_cbk)(PJ_CONTEXT *ctx, const char *filename, PROJ_OPEN_ACCESS access, void* user_data);
+
+ /** Read sizeBytes into buffer from current position and return number of bytes read */
+ size_t (*read_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, void* buffer, size_t sizeBytes, void* user_data);
+
+ /** Write sizeBytes into buffer from current position and return number of bytes written */
+ size_t (*write_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, const void* buffer, size_t sizeBytes, void* user_data);
+
+ /** Seek to offset using whence=SEEK_SET/SEEK_CUR/SEEK_END. Return TRUE in case of success */
+ int (*seek_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, long long offset, int whence, void* user_data);
+
+ /** Return current file position */
+ unsigned long long (*tell_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, void* user_data);
+
+ /** Close file */
+ void (*close_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, void* user_data);
+
+ /** Return TRUE if a file exists */
+ int (*exists_cbk)(PJ_CONTEXT *ctx, const char *filename, void* user_data);
+
+ /** Return TRUE if directory exists or could be created */
+ int (*mkdir_cbk)(PJ_CONTEXT *ctx, const char *filename, void* user_data);
+
+ /** Return TRUE if file could be removed */
+ int (*unlink_cbk)(PJ_CONTEXT *ctx, const char *filename, void* user_data);
+
+ /** Return TRUE if file could be renamed */
+ int (*rename_cbk)(PJ_CONTEXT *ctx, const char *oldPath, const char *newPath, void* user_data);
+} PROJ_FILE_API;
+
+int PROJ_DLL proj_context_set_fileapi(
+ PJ_CONTEXT* ctx, const PROJ_FILE_API* fileapi, void* user_data);
+
+void PROJ_DLL proj_context_set_sqlite3_vfs_name(PJ_CONTEXT* ctx, const char* name);
+
+/** Opaque structure for PROJ for a network handle. Implementations might cast it to their
* structure/class of choice. */
typedef struct PROJ_NETWORK_HANDLE PROJ_NETWORK_HANDLE;
diff --git a/src/proj_internal.h b/src/proj_internal.h
index ce7b9d74..15b98859 100644
--- a/src/proj_internal.h
+++ b/src/proj_internal.h
@@ -685,6 +685,23 @@ struct projGridChunkCache
int ttl = 86400; // 1 day
};
+struct projFileApiCallbackAndData
+{
+ PROJ_FILE_HANDLE* (*open_cbk)(PJ_CONTEXT *ctx, const char *filename, PROJ_OPEN_ACCESS access, void* user_data) = nullptr;
+ size_t (*read_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, void* buffer, size_t size, void* user_data) = nullptr;
+ size_t (*write_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, const void* buffer, size_t size, void* user_data) = nullptr;
+ int (*seek_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, long long offset, int whence, void* user_data) = nullptr;
+ unsigned long long (*tell_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, void* user_data) = nullptr;
+ void (*close_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, void* user_data) = nullptr;
+
+ int (*exists_cbk)(PJ_CONTEXT *ctx, const char *filename, void* user_data) = nullptr;
+ int (*mkdir_cbk)(PJ_CONTEXT *ctx, const char *filename, void* user_data) = nullptr;
+ int (*unlink_cbk)(PJ_CONTEXT *ctx, const char *filename, void* user_data) = nullptr;
+ int (*rename_cbk)(PJ_CONTEXT *ctx, const char *oldPath, const char *newPath, void* user_data) = nullptr;
+
+ void* user_data = nullptr;
+};
+
/* proj thread context */
struct projCtx_t {
int last_errno = 0;
@@ -696,6 +713,7 @@ struct projCtx_t {
int use_proj4_init_rules = -1; /* -1 = unknown, 0 = no, 1 = yes */
int epsg_file_exists = -1; /* -1 = unknown, 0 = no, 1 = yes */
+ std::string env_var_proj_lib{}; // content of PROJ_LIB environment variable. Use Filemanager::getProjLibEnvVar() to access
std::vector<std::string> search_paths{};
const char **c_compat_paths = nullptr; // same, but for projinfo usage
@@ -706,6 +724,9 @@ struct projCtx_t {
projNetworkCallbacksAndData networking{};
bool defer_grid_opening = false; // set by pj_obj_create()
+ projFileApiCallbackAndData fileApi{};
+ std::string custom_sqlite3_vfs_name{};
+
bool iniFileLoaded = false;
std::string endpoint{};
diff --git a/src/sqlite3.cpp b/src/sqlite3_utils.cpp
index 90e73c2a..673eb89c 100644
--- a/src/sqlite3.cpp
+++ b/src/sqlite3_utils.cpp
@@ -30,7 +30,7 @@
#pragma GCC diagnostic ignored "-Weffc++"
#endif
-#include "sqlite3.hpp"
+#include "sqlite3_utils.hpp"
#ifdef __GNUC__
#pragma GCC diagnostic pop
diff --git a/src/sqlite3.hpp b/src/sqlite3_utils.hpp
index ef141d1f..ef141d1f 100644
--- a/src/sqlite3.hpp
+++ b/src/sqlite3_utils.hpp
diff --git a/test/cli/ntv2_out.dist b/test/cli/ntv2_out.dist
index 650a69d8..ce866f37 100644
--- a/test/cli/ntv2_out.dist
+++ b/test/cli/ntv2_out.dist
@@ -12,3 +12,11 @@ Try with NTv2 and NTv1 together ... falls back to NTv1
##############################################################
Switching between NTv2 subgrids
-112.5839956 49.4914451 0 -112.58307487 49.49145197 0.00000000
+##############################################################
+Interpolating very close (and sometimes a bit outside) to the edges a NTv2 subgrid (#209)
+-115.58333333 51.25000000 0 -115.58228512 51.24997866 0.00000000
+-115.58333333 51.25000010 0 -115.58228512 51.24997876 0.00000000
+-115.58333334 51.25000000 0 -115.58228513 51.24997866 0.00000000
+-115.49166667 51.07500000 0 -115.49062909 51.07497666 0.00000000
+-115.49166668 51.07500000 0 -115.49062910 51.07497666 0.00000000
+-115.49166667 51.07499990 0 -115.49062909 51.07497656 0.00000000
diff --git a/test/cli/td_out.dist b/test/cli/td_out.dist
index 82b64321..76c6e6ec 100644
--- a/test/cli/td_out.dist
+++ b/test/cli/td_out.dist
@@ -21,7 +21,7 @@ edge or even a wee bit outside (#141).
-5.5001 52.0 -5.500100000000 52.000000000000 0.000000000000
-5.5 52.0 -5.498893534472 52.000109529716 0.000000000000
-5.5000000000001 52.0000000000001 -5.498893534472 52.000109529717 0.000000000000
--5.4999 51.9999 -5.498793541695 52.000009529743 0.000000000000
+-5.4999 51.9999 -5.498793593803 52.000009531513 0.000000000000
-5.5001 52.0 -5.500100000000 52.000000000000 0.000000000000
##############################################################
NAD27 -> NAD83: 1st through ntv1, 2nd through conus
diff --git a/test/cli/testntv2 b/test/cli/testntv2
index 2a31304e..d15ad7aa 100755
--- a/test/cli/testntv2
+++ b/test/cli/testntv2
@@ -60,6 +60,17 @@ $EXE +proj=latlong +datum=NAD83 +to +proj=latlong +ellps=clrk66 +nadgrids=ntv2_0
-112.5839956 49.4914451 0
EOF
+echo "##############################################################" >> ${OUT}
+echo "Interpolating very close (and sometimes a bit outside) to the edges a NTv2 subgrid (#209)" >> ${OUT}
+$EXE +proj=latlong +datum=NAD83 +to +proj=latlong +ellps=clrk66 +nadgrids=ntv2_0.gsb -E -d 8 >>${OUT} <<EOF
+-115.58333333 51.25000000 0
+-115.58333333 51.25000010 0
+-115.58333334 51.25000000 0
+-115.49166667 51.07500000 0
+-115.49166668 51.07500000 0
+-115.49166667 51.07499990 0
+EOF
+
#
##############################################################################
# Done!
diff --git a/test/gie/geotiff_grids.gie b/test/gie/geotiff_grids.gie
index 85c0cbe9..62a5b16d 100644
--- a/test/gie/geotiff_grids.gie
+++ b/test/gie/geotiff_grids.gie
@@ -277,6 +277,20 @@ accept -80.5041667 44.5458333 0
expect -80.50401615833 44.5458827236 0
-------------------------------------------------------------------------------
+# Check a nested grid of a nested grid only based on spatial extent analysis
+-------------------------------------------------------------------------------
+operation +proj=hgridshift +grids=tests/test_hgrid_with_two_level_of_subgrids_no_grid_name.tif
+-------------------------------------------------------------------------------
+accept -45.0 22.5
+accept -44.9983333334 22.5013888889
+
+# Check a nested grid of a nested grid only based on spatial extent analysis
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_hgrid_with_two_level_of_subgrids_no_grid_name.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept -45.0 22.5 0
+accept -45.0 22.5 5
+
-------------------------------------------------------------------------------
operation +proj=hgridshift +grids=tests/test_vgrid.tif
-------------------------------------------------------------------------------
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index e1eefcf0..e14d4c70 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -128,14 +128,20 @@ add_executable(proj_test_cpp_api
test_operation.cpp
test_datum.cpp
test_factory.cpp
- test_c_api.cpp)
+ test_c_api.cpp
+ test_grids.cpp)
target_link_libraries(proj_test_cpp_api
GTest::gtest
${PROJ_LIBRARIES}
${SQLITE3_LIBRARY})
add_test(NAME proj_test_cpp_api COMMAND proj_test_cpp_api)
-set_property(TEST proj_test_cpp_api
- PROPERTY ENVIRONMENT "PROJ_IGNORE_USER_WRITABLE_DIRECTORY=YES;PROJ_LIB=${PROJECT_BINARY_DIR}/data")
+if(MSVC)
+ set_property(TEST proj_test_cpp_api
+ PROPERTY ENVIRONMENT "PROJ_IGNORE_USER_WRITABLE_DIRECTORY=YES;PROJ_LIB=${PROJECT_BINARY_DIR}/data\\;${PROJECT_SOURCE_DIR}/data")
+else()
+ set_property(TEST proj_test_cpp_api
+ PROPERTY ENVIRONMENT "PROJ_IGNORE_USER_WRITABLE_DIRECTORY=YES;PROJ_LIB=${PROJECT_BINARY_DIR}/data:${PROJECT_SOURCE_DIR}/data")
+endif()
add_executable(gie_self_tests
diff --git a/test/unit/Makefile.am b/test/unit/Makefile.am
index 7ffb06ae..b7a26d4e 100644
--- a/test/unit/Makefile.am
+++ b/test/unit/Makefile.am
@@ -49,7 +49,7 @@ proj_context_test_LDADD = ../../src/libproj.la @GTEST_LIBS@
proj_context_test-check: proj_context_test
PROJ_IGNORE_USER_WRITABLE_DIRECTORY=YES ./proj_context_test
-test_cpp_api_SOURCES = test_util.cpp test_common.cpp test_crs.cpp test_metadata.cpp test_io.cpp test_operation.cpp test_datum.cpp test_factory.cpp test_c_api.cpp main.cpp
+test_cpp_api_SOURCES = test_util.cpp test_common.cpp test_crs.cpp test_metadata.cpp test_io.cpp test_operation.cpp test_datum.cpp test_factory.cpp test_c_api.cpp test_grids.cpp main.cpp
test_cpp_api_LDADD = ../../src/libproj.la @GTEST_LIBS@ @SQLITE3_LIBS@
test_cpp_api-check: test_cpp_api
diff --git a/test/unit/test_c_api.cpp b/test/unit/test_c_api.cpp
index f87f6589..8871f679 100644
--- a/test/unit/test_c_api.cpp
+++ b/test/unit/test_c_api.cpp
@@ -4434,4 +4434,26 @@ TEST_F(CApi, proj_create_derived_geographic_crs) {
"+o_lat_p=-2 +lon_0=3 +datum=WGS84 +no_defs "
"+type=crs"));
}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_context_set_sqlite3_vfs_name) {
+
+ PJ_CONTEXT *ctx = proj_context_create();
+ proj_log_func(ctx, nullptr, [](void *, int, const char *) -> void {});
+
+ // Set a dummy VFS and check it is taken into account
+ // (failure to open proj.db)
+ proj_context_set_sqlite3_vfs_name(ctx, "dummy_vfs_name");
+ ASSERT_EQ(proj_create(ctx, "EPSG:4326"), nullptr);
+
+ // Restore default VFS
+ proj_context_set_sqlite3_vfs_name(ctx, nullptr);
+ PJ *crs_4326 = proj_create(ctx, "EPSG:4326");
+ ASSERT_NE(crs_4326, nullptr);
+ proj_destroy(crs_4326);
+
+ proj_context_destroy(ctx);
+}
+
} // namespace
diff --git a/test/unit/test_grids.cpp b/test/unit/test_grids.cpp
new file mode 100644
index 00000000..5240949e
--- /dev/null
+++ b/test/unit/test_grids.cpp
@@ -0,0 +1,227 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: Test grids.hpp
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2020, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#include "gtest_include.h"
+
+#include "grids.hpp"
+
+#include "proj_internal.h" // M_PI
+
+namespace {
+
+// ---------------------------------------------------------------------------
+
+class GridTest : public ::testing::Test {
+
+ static void DummyLogFunction(void *, int, const char *) {}
+
+ protected:
+ void SetUp() override {
+ m_ctxt = proj_context_create();
+ proj_log_func(m_ctxt, nullptr, DummyLogFunction);
+ m_ctxt2 = proj_context_create();
+ proj_log_func(m_ctxt2, nullptr, DummyLogFunction);
+ }
+
+ void TearDown() override {
+ proj_context_destroy(m_ctxt);
+ proj_context_destroy(m_ctxt2);
+ }
+
+ PJ_CONTEXT *m_ctxt = nullptr;
+ PJ_CONTEXT *m_ctxt2 = nullptr;
+};
+
+// ---------------------------------------------------------------------------
+
+TEST_F(GridTest, VerticalShiftGridSet_null) {
+ auto gridSet = NS_PROJ::VerticalShiftGridSet::open(m_ctxt, "null");
+ ASSERT_NE(gridSet, nullptr);
+ auto grid = gridSet->gridAt(0.0, 0.0);
+ ASSERT_NE(grid, nullptr);
+ EXPECT_EQ(grid->width(), 3);
+ EXPECT_EQ(grid->height(), 3);
+ EXPECT_EQ(grid->extentAndRes().westLon, -M_PI);
+ EXPECT_TRUE(grid->isNullGrid());
+ EXPECT_FALSE(grid->hasChanged());
+ float out = -1.0f;
+ EXPECT_TRUE(grid->valueAt(0, 0, out));
+ EXPECT_EQ(out, 0.0f);
+ EXPECT_FALSE(grid->isNodata(0.0f, 0.0));
+ gridSet->reassign_context(m_ctxt2);
+ gridSet->reopen(m_ctxt2);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(GridTest, VerticalShiftGridSet_gtx) {
+ ASSERT_EQ(NS_PROJ::VerticalShiftGridSet::open(m_ctxt, "foobar"), nullptr);
+ auto gridSet =
+ NS_PROJ::VerticalShiftGridSet::open(m_ctxt, "tests/test_nodata.gtx");
+ ASSERT_NE(gridSet, nullptr);
+ ASSERT_EQ(gridSet->gridAt(-100, -100), nullptr);
+ auto grid = gridSet->gridAt(4.15 / 180 * M_PI, 52.15 / 180 * M_PI);
+ ASSERT_NE(grid, nullptr);
+ EXPECT_TRUE(grid->isNodata(-88.8888f, 1.0));
+ gridSet->reassign_context(m_ctxt2);
+ gridSet->reopen(m_ctxt2);
+ grid = gridSet->gridAt(4.15 / 180 * M_PI, 52.15 / 180 * M_PI);
+ EXPECT_NE(grid, nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(GridTest, HorizontalShiftGridSet_null) {
+ auto gridSet = NS_PROJ::HorizontalShiftGridSet::open(m_ctxt, "null");
+ ASSERT_NE(gridSet, nullptr);
+ auto grid = gridSet->gridAt(0.0, 0.0);
+ ASSERT_NE(grid, nullptr);
+ EXPECT_EQ(grid->width(), 3);
+ EXPECT_EQ(grid->height(), 3);
+ EXPECT_EQ(grid->extentAndRes().westLon, -M_PI);
+ EXPECT_TRUE(grid->isNullGrid());
+ EXPECT_FALSE(grid->hasChanged());
+ float out1 = -1.0f;
+ float out2 = -1.0f;
+ EXPECT_TRUE(grid->valueAt(0, 0, false, out1, out2));
+ EXPECT_EQ(out1, 0.0f);
+ EXPECT_EQ(out2, 0.0f);
+ gridSet->reassign_context(m_ctxt2);
+ gridSet->reopen(m_ctxt2);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(GridTest, HorizontalShiftGridSet_gtiff) {
+ auto gridSet =
+ NS_PROJ::HorizontalShiftGridSet::open(m_ctxt, "tests/test_hgrid.tif");
+ ASSERT_NE(gridSet, nullptr);
+ EXPECT_EQ(gridSet->format(), "gtiff");
+ EXPECT_TRUE(gridSet->name().find("tests/test_hgrid.tif") !=
+ std::string::npos)
+ << gridSet->name();
+ EXPECT_EQ(gridSet->grids().size(), 1U);
+ ASSERT_EQ(gridSet->gridAt(-100, -100), nullptr);
+ auto grid = gridSet->gridAt(5.5 / 180 * M_PI, 53.5 / 180 * M_PI);
+ ASSERT_NE(grid, nullptr);
+ EXPECT_EQ(grid->width(), 4);
+ EXPECT_EQ(grid->height(), 4);
+ EXPECT_EQ(grid->extentAndRes().westLon, 4.0 / 180 * M_PI);
+ EXPECT_FALSE(grid->isNullGrid());
+ EXPECT_FALSE(grid->hasChanged());
+ float out1 = -1.0f;
+ float out2 = -1.0f;
+ EXPECT_TRUE(grid->valueAt(0, 3, false, out1, out2));
+ EXPECT_EQ(out1, static_cast<float>(14400.0 / 3600. / 180 * M_PI));
+ EXPECT_EQ(out2, static_cast<float>(900.0 / 3600. / 180 * M_PI));
+ gridSet->reassign_context(m_ctxt2);
+ gridSet->reopen(m_ctxt2);
+ grid = gridSet->gridAt(5.5 / 180 * M_PI, 53.5 / 180 * M_PI);
+ EXPECT_NE(grid, nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(GridTest, GenericShiftGridSet_null) {
+ auto gridSet = NS_PROJ::GenericShiftGridSet::open(m_ctxt, "null");
+ ASSERT_NE(gridSet, nullptr);
+ auto grid = gridSet->gridAt(0.0, 0.0);
+ ASSERT_NE(grid, nullptr);
+ EXPECT_EQ(grid->width(), 3);
+ EXPECT_EQ(grid->height(), 3);
+ EXPECT_EQ(grid->extentAndRes().westLon, -M_PI);
+ EXPECT_TRUE(grid->isNullGrid());
+ EXPECT_FALSE(grid->hasChanged());
+ float out = -1.0f;
+ EXPECT_TRUE(grid->valueAt(0, 0, 0, out));
+ EXPECT_EQ(out, 0.0f);
+ EXPECT_EQ(grid->unit(0), "");
+ EXPECT_EQ(grid->description(0), "");
+ EXPECT_EQ(grid->metadataItem("foo"), "");
+ EXPECT_EQ(grid->samplesPerPixel(), 0);
+ gridSet->reassign_context(m_ctxt2);
+ gridSet->reopen(m_ctxt2);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(GridTest, GenericShiftGridSet_gtiff) {
+ ASSERT_EQ(NS_PROJ::GenericShiftGridSet::open(m_ctxt, "foobar"), nullptr);
+ auto gridSet = NS_PROJ::GenericShiftGridSet::open(
+ m_ctxt, "tests/nkgrf03vel_realigned_extract.tif");
+ ASSERT_NE(gridSet, nullptr);
+ ASSERT_EQ(gridSet->gridAt(-100, -100), nullptr);
+ auto grid = gridSet->gridAt(21.3333333 / 180 * M_PI, 63.0 / 180 * M_PI);
+ ASSERT_NE(grid, nullptr);
+ EXPECT_EQ(grid->width(), 5);
+ EXPECT_EQ(grid->height(), 5);
+ EXPECT_EQ(grid->extentAndRes().westLon, 21.0 / 180 * M_PI);
+ EXPECT_FALSE(grid->isNullGrid());
+ EXPECT_FALSE(grid->hasChanged());
+ float out = -1.0f;
+ EXPECT_FALSE(grid->valueAt(0, 0, grid->samplesPerPixel(), out));
+ EXPECT_EQ(grid->metadataItem("area_of_use"), "Nordic and Baltic countries");
+ EXPECT_EQ(grid->metadataItem("non_existing"), std::string());
+ EXPECT_EQ(grid->metadataItem("non_existing", 1), std::string());
+ EXPECT_EQ(grid->metadataItem("non_existing", 10), std::string());
+ gridSet->reassign_context(m_ctxt2);
+ gridSet->reopen(m_ctxt2);
+ grid = gridSet->gridAt(21.3333333 / 180 * M_PI, 63.0 / 180 * M_PI);
+ EXPECT_NE(grid, nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(GridTest, GenericShiftGridSet_gtiff_with_subgrid) {
+ auto gridSet = NS_PROJ::GenericShiftGridSet::open(
+ m_ctxt, "tests/test_hgrid_with_subgrid.tif");
+ ASSERT_NE(gridSet, nullptr);
+ ASSERT_EQ(gridSet->gridAt(-100, -100), nullptr);
+ auto grid =
+ gridSet->gridAt(-115.5416667 / 180 * M_PI, 51.1666667 / 180 * M_PI);
+ ASSERT_NE(grid, nullptr);
+ EXPECT_EQ(grid->width(), 11);
+ EXPECT_EQ(grid->height(), 21);
+ EXPECT_EQ(grid->metadataItem("grid_name"), "ALbanff");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(GridTest,
+ GenericShiftGridSet_gtiff_with_two_level_of_subgrids_no_grid_name) {
+ auto gridSet = NS_PROJ::GenericShiftGridSet::open(
+ m_ctxt, "tests/test_hgrid_with_two_level_of_subgrids_no_grid_name.tif");
+ ASSERT_NE(gridSet, nullptr);
+ ASSERT_EQ(gridSet->gridAt(-100, -100), nullptr);
+ auto grid = gridSet->gridAt(-45.5 / 180 * M_PI, 22.5 / 180 * M_PI);
+ ASSERT_NE(grid, nullptr);
+ EXPECT_EQ(grid->width(), 8);
+ EXPECT_EQ(grid->height(), 8);
+}
+
+} // namespace
diff --git a/test/unit/test_network.cpp b/test/unit/test_network.cpp
index 4e66d8c5..688e61e5 100644
--- a/test/unit/test_network.cpp
+++ b/test/unit/test_network.cpp
@@ -1574,27 +1574,45 @@ TEST(networking, download_whole_files) {
proj_cleanup();
unlink("proj_test_tmp/cache.db");
- unlink("proj_test_tmp/ntf_r93.tif");
+ unlink("proj_test_tmp/dvr90.tif");
rmdir("proj_test_tmp");
putenv(const_cast<char *>("PROJ_IGNORE_USER_WRITABLE_DIRECTORY="));
putenv(const_cast<char *>("PROJ_USER_WRITABLE_DIRECTORY=./proj_test_tmp"));
- putenv(const_cast<char *>("PROJ_FULL_FILE_CHUNK_SIZE=30000"));
+ putenv(const_cast<char *>("PROJ_FULL_FILE_CHUNK_SIZE=100000"));
auto ctx = proj_context_create();
proj_context_set_enable_network(ctx, true);
- ASSERT_TRUE(proj_is_download_needed(ctx, "ntf_r93.gsb", false));
+ ASSERT_TRUE(proj_is_download_needed(ctx, "dvr90.gtx", false));
- ASSERT_TRUE(
- proj_download_file(ctx, "ntf_r93.gsb", false, nullptr, nullptr));
+ ASSERT_TRUE(proj_download_file(ctx, "dvr90.gtx", false, nullptr, nullptr));
- FILE *f = fopen("proj_test_tmp/ntf_r93.tif", "rb");
+ FILE *f = fopen("proj_test_tmp/dvr90.tif", "rb");
ASSERT_NE(f, nullptr);
- fseek(f, 0, SEEK_END);
- ASSERT_EQ(ftell(f), 93581);
fclose(f);
- ASSERT_FALSE(proj_is_download_needed(ctx, "ntf_r93.gsb", false));
+ proj_context_set_enable_network(ctx, false);
+
+ const char *pipeline =
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=dvr90.gtx +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg";
+
+ auto P = proj_create(ctx, pipeline);
+ ASSERT_NE(P, nullptr);
+
+ double lon = 12;
+ double lat = 56;
+ double z = 0;
+ proj_trans_generic(P, PJ_FWD, &lon, sizeof(double), 1, &lat, sizeof(double),
+ 1, &z, sizeof(double), 1, nullptr, 0, 0);
+ EXPECT_NEAR(z, 36.5909996032715, 1e-10);
+ proj_destroy(P);
+
+ proj_context_set_enable_network(ctx, true);
+
+ ASSERT_FALSE(proj_is_download_needed(ctx, "dvr90.gtx", false));
{
sqlite3 *hDB = nullptr;
@@ -1613,7 +1631,7 @@ TEST(networking, download_whole_files) {
}
// If we ignore TTL settings, then no network access will be done
- ASSERT_FALSE(proj_is_download_needed(ctx, "ntf_r93.gsb", true));
+ ASSERT_FALSE(proj_is_download_needed(ctx, "dvr90.gtx", true));
{
sqlite3 *hDB = nullptr;
@@ -1633,7 +1651,7 @@ TEST(networking, download_whole_files) {
}
// Should recheck from the CDN, update last_checked and do nothing
- ASSERT_FALSE(proj_is_download_needed(ctx, "ntf_r93.gsb", false));
+ ASSERT_FALSE(proj_is_download_needed(ctx, "dvr90.gtx", false));
{
sqlite3 *hDB = nullptr;
@@ -1662,10 +1680,10 @@ TEST(networking, download_whole_files) {
sqlite3_close(hDB);
}
- ASSERT_TRUE(proj_is_download_needed(ctx, "ntf_r93.gsb", false));
+ ASSERT_TRUE(proj_is_download_needed(ctx, "dvr90.gtx", false));
// Redo download with a progress callback this time.
- unlink("proj_test_tmp/ntf_r93.tif");
+ unlink("proj_test_tmp/dvr90.tif");
const auto cbk = [](PJ_CONTEXT *l_ctx, double pct, void *user_data) -> int {
auto vect = static_cast<std::vector<std::pair<PJ_CONTEXT *, double>> *>(
@@ -1675,7 +1693,7 @@ TEST(networking, download_whole_files) {
};
std::vector<std::pair<PJ_CONTEXT *, double>> vectPct;
- ASSERT_TRUE(proj_download_file(ctx, "ntf_r93.gsb", false, cbk, &vectPct));
+ ASSERT_TRUE(proj_download_file(ctx, "dvr90.gtx", false, cbk, &vectPct));
ASSERT_EQ(vectPct.size(), 3U);
ASSERT_EQ(vectPct.back().first, ctx);
ASSERT_EQ(vectPct.back().second, 1.0);
@@ -1685,7 +1703,151 @@ TEST(networking, download_whole_files) {
putenv(const_cast<char *>("PROJ_USER_WRITABLE_DIRECTORY="));
putenv(const_cast<char *>("PROJ_FULL_FILE_CHUNK_SIZE="));
unlink("proj_test_tmp/cache.db");
- unlink("proj_test_tmp/ntf_r93.tif");
+ unlink("proj_test_tmp/dvr90.tif");
+ rmdir("proj_test_tmp");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(networking, file_api) {
+ if (!networkAccessOK) {
+ return;
+ }
+
+ proj_cleanup();
+ unlink("proj_test_tmp/cache.db");
+ unlink("proj_test_tmp/dvr90.tif");
+ rmdir("proj_test_tmp");
+
+ putenv(const_cast<char *>("PROJ_IGNORE_USER_WRITABLE_DIRECTORY="));
+ putenv(const_cast<char *>("PROJ_USER_WRITABLE_DIRECTORY=./proj_test_tmp"));
+ putenv(const_cast<char *>("PROJ_FULL_FILE_CHUNK_SIZE=30000"));
+ auto ctx = proj_context_create();
+ proj_context_set_enable_network(ctx, true);
+
+ struct UserData {
+ bool in_open = false;
+ bool in_read = false;
+ bool in_write = false;
+ bool in_seek = false;
+ bool in_tell = false;
+ bool in_close = false;
+ bool in_exists = false;
+ bool in_mkdir = false;
+ bool in_unlink = false;
+ bool in_rename = false;
+ };
+
+ struct PROJ_FILE_API api;
+ api.version = 1;
+ api.open_cbk = [](PJ_CONTEXT *, const char *filename,
+ PROJ_OPEN_ACCESS access,
+ void *user_data) -> PROJ_FILE_HANDLE * {
+ static_cast<UserData *>(user_data)->in_open = true;
+ return reinterpret_cast<PROJ_FILE_HANDLE *>(fopen(
+ filename,
+ access == PROJ_OPEN_ACCESS_READ_ONLY
+ ? "rb"
+ : access == PROJ_OPEN_ACCESS_READ_UPDATE ? "r+b" : "w+b"));
+ };
+ api.read_cbk = [](PJ_CONTEXT *, PROJ_FILE_HANDLE *handle, void *buffer,
+ size_t sizeBytes, void *user_data) -> size_t {
+ static_cast<UserData *>(user_data)->in_read = true;
+ return fread(buffer, 1, sizeBytes, reinterpret_cast<FILE *>(handle));
+ };
+ api.write_cbk = [](PJ_CONTEXT *, PROJ_FILE_HANDLE *handle,
+ const void *buffer, size_t sizeBytes,
+ void *user_data) -> size_t {
+ static_cast<UserData *>(user_data)->in_write = true;
+ return fwrite(buffer, 1, sizeBytes, reinterpret_cast<FILE *>(handle));
+ };
+ api.seek_cbk = [](PJ_CONTEXT *, PROJ_FILE_HANDLE *handle, long long offset,
+ int whence, void *user_data) -> int {
+ static_cast<UserData *>(user_data)->in_seek = true;
+ return fseek(reinterpret_cast<FILE *>(handle),
+ static_cast<long>(offset), whence) == 0;
+ };
+ api.tell_cbk = [](PJ_CONTEXT *, PROJ_FILE_HANDLE *handle,
+ void *user_data) -> unsigned long long {
+ static_cast<UserData *>(user_data)->in_tell = true;
+ return ftell(reinterpret_cast<FILE *>(handle));
+ };
+ api.close_cbk = [](PJ_CONTEXT *, PROJ_FILE_HANDLE *handle,
+ void *user_data) -> void {
+ static_cast<UserData *>(user_data)->in_close = true;
+ fclose(reinterpret_cast<FILE *>(handle));
+ };
+ api.exists_cbk = [](PJ_CONTEXT *, const char *filename,
+ void *user_data) -> int {
+ static_cast<UserData *>(user_data)->in_exists = true;
+ struct stat buf;
+ return stat(filename, &buf) == 0;
+ };
+ api.mkdir_cbk = [](PJ_CONTEXT *, const char *filename,
+ void *user_data) -> int {
+ static_cast<UserData *>(user_data)->in_mkdir = true;
+#ifdef _WIN32
+ return mkdir(filename) == 0;
+#else
+ return mkdir(filename, 0755) == 0;
+#endif
+ };
+ api.unlink_cbk = [](PJ_CONTEXT *, const char *filename,
+ void *user_data) -> int {
+ static_cast<UserData *>(user_data)->in_unlink = true;
+ return unlink(filename) == 0;
+ };
+ api.rename_cbk = [](PJ_CONTEXT *, const char *oldPath, const char *newPath,
+ void *user_data) -> int {
+ static_cast<UserData *>(user_data)->in_rename = true;
+ return rename(oldPath, newPath) == 0;
+ };
+
+ UserData userData;
+ ASSERT_TRUE(proj_context_set_fileapi(ctx, &api, &userData));
+
+ ASSERT_TRUE(proj_is_download_needed(ctx, "dvr90.gtx", false));
+
+ ASSERT_TRUE(proj_download_file(ctx, "dvr90.gtx", false, nullptr, nullptr));
+
+ ASSERT_TRUE(userData.in_open);
+ ASSERT_FALSE(userData.in_read);
+ ASSERT_TRUE(userData.in_write);
+ ASSERT_TRUE(userData.in_close);
+ ASSERT_TRUE(userData.in_exists);
+ ASSERT_TRUE(userData.in_mkdir);
+ ASSERT_TRUE(userData.in_unlink);
+ ASSERT_TRUE(userData.in_rename);
+
+ proj_context_set_enable_network(ctx, false);
+
+ const char *pipeline =
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=dvr90.gtx +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg";
+
+ auto P = proj_create(ctx, pipeline);
+ ASSERT_NE(P, nullptr);
+
+ double lon = 12;
+ double lat = 56;
+ double z = 0;
+ proj_trans_generic(P, PJ_FWD, &lon, sizeof(double), 1, &lat, sizeof(double),
+ 1, &z, sizeof(double), 1, nullptr, 0, 0);
+ EXPECT_NEAR(z, 36.5909996032715, 1e-10);
+
+ proj_destroy(P);
+
+ ASSERT_TRUE(userData.in_read);
+ ASSERT_TRUE(userData.in_seek);
+
+ proj_context_destroy(ctx);
+ putenv(const_cast<char *>("PROJ_IGNORE_USER_WRITABLE_DIRECTORY=YES"));
+ putenv(const_cast<char *>("PROJ_USER_WRITABLE_DIRECTORY="));
+ putenv(const_cast<char *>("PROJ_FULL_FILE_CHUNK_SIZE="));
+ unlink("proj_test_tmp/cache.db");
+ unlink("proj_test_tmp/dvr90.tif");
rmdir("proj_test_tmp");
}
diff --git a/travis/mingw32/install.sh b/travis/mingw32/install.sh
index abe79c7b..8c756ced 100755
--- a/travis/mingw32/install.sh
+++ b/travis/mingw32/install.sh
@@ -53,6 +53,7 @@ make install
make dist-all
find /tmp/proj_autoconf_install
(cd test; make -j2)
+cp -r ../data/tests /tmp/proj_autoconf_install/share/proj
test/unit/test_cpp_api.exe
cd ..
# Now with grids