diff options
| -rw-r--r-- | scripts/reference_exported_symbols.txt | 2 | ||||
| -rw-r--r-- | src/filemanager.cpp | 206 | ||||
| -rw-r--r-- | src/iso19111/factory.cpp | 28 | ||||
| -rw-r--r-- | src/proj.h | 61 | ||||
| -rw-r--r-- | src/proj_internal.h | 20 | ||||
| -rw-r--r-- | test/unit/test_c_api.cpp | 22 | ||||
| -rw-r--r-- | test/unit/test_network.cpp | 144 |
7 files changed, 461 insertions, 22 deletions
diff --git a/scripts/reference_exported_symbols.txt b/scripts/reference_exported_symbols.txt index d10f3bc1..dd66197a 100644 --- a/scripts/reference_exported_symbols.txt +++ b/scripts/reference_exported_symbols.txt @@ -798,10 +798,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/filemanager.cpp b/src/filemanager.cpp index d2fce822..1857e397 100644 --- a/src/filemanager.cpp +++ b/src/filemanager.cpp @@ -834,6 +834,92 @@ FileLegacyAdapter::open(PJ_CONTEXT *ctx, const char *filename, FileAccess) { // --------------------------------------------------------------------------- +class FileApiAdapter : public File { + PJ_CONTEXT *m_ctx; + PROJ_FILE_HANDLE *m_fp; + + FileApiAdapter(const FileApiAdapter &) = delete; + FileApiAdapter &operator=(const FileApiAdapter &) = delete; + + protected: + FileApiAdapter(const std::string &name, PJ_CONTEXT *ctx, + PROJ_FILE_HANDLE *fp) + : File(name), m_ctx(ctx), m_fp(fp) {} + + public: + ~FileApiAdapter() override; + + size_t read(void *buffer, size_t sizeBytes) 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 { m_ctx = ctx; } + + // We may lie, but the real use case is only for network files + bool hasChanged() const override { return false; } + + static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename, + FileAccess access); +}; + +// --------------------------------------------------------------------------- + +FileApiAdapter::~FileApiAdapter() { + m_ctx->fileApi.close_cbk(m_ctx, m_fp, m_ctx->fileApi.user_data); +} + +// --------------------------------------------------------------------------- + +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 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 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 FileApiAdapter::tell() { + return m_ctx->fileApi.tell_cbk(m_ctx, m_fp, m_ctx->fileApi.user_data); +} + +// --------------------------------------------------------------------------- + +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); +} + +// --------------------------------------------------------------------------- + constexpr size_t DOWNLOAD_CHUNK_SIZE = 16 * 1024; constexpr int MAX_CHUNKS = 64; @@ -990,12 +1076,20 @@ std::unique_ptr<DiskChunkCache> DiskChunkCache::open(PJ_CONTEXT *ctx) { // --------------------------------------------------------------------------- DiskChunkCache::DiskChunkCache(PJ_CONTEXT *ctx, const std::string &path) - : ctx_(ctx), path_(path), vfs_(SQLite3VFS::create(true, false, false)) { - if (vfs_ == nullptr) { - return; + : ctx_(ctx), path_(path) { + std::string vfsName; + if (ctx->custom_sqlite3_vfs_name.empty()) { + vfs_ = SQLite3VFS::create(true, false, false); + if (vfs_ == nullptr) { + return; + } + vfsName = vfs_->name(); + } else { + vfsName = ctx->custom_sqlite3_vfs_name; } sqlite3_open_v2(path.c_str(), &hDB_, - SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, vfs_->name()); + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + vfsName.c_str()); if (!hDB_) { return; } @@ -2206,12 +2300,6 @@ void NetworkFile::reassign_context(PJ_CONTEXT *ctx) { std::unique_ptr<File> FileManager::open(PJ_CONTEXT *ctx, const char *filename, FileAccess access) { -#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); - } -#endif if (starts_with(filename, "http://") || starts_with(filename, "https://")) { if (!pj_context_is_network_enabled(ctx)) { pj_log( @@ -2223,6 +2311,15 @@ std::unique_ptr<File> FileManager::open(PJ_CONTEXT *ctx, const char *filename, } return NetworkFile::open(ctx, 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, access); + } +#endif + if (ctx->fileApi.open_cbk != nullptr) { + return FileApiAdapter::open(ctx, filename, access); + } #ifdef _WIN32 return FileWin32::open(ctx, filename, access); #else @@ -2233,6 +2330,11 @@ std::unique_ptr<File> FileManager::open(PJ_CONTEXT *ctx, const char *filename, // --------------------------------------------------------------------------- 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; + } + #ifdef _WIN32 struct __stat64 buf; try { @@ -2251,6 +2353,11 @@ bool FileManager::exists(PJ_CONTEXT *ctx, const char *filename) { // --------------------------------------------------------------------------- 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; + } + #ifdef _WIN32 try { return _wmkdir(UTF8ToWString(filename).c_str()) == 0; @@ -2267,6 +2374,11 @@ bool FileManager::mkdir(PJ_CONTEXT *ctx, const char *filename) { // --------------------------------------------------------------------------- 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; + } + #ifdef _WIN32 try { return _wunlink(UTF8ToWString(filename).c_str()) == 0; @@ -2284,6 +2396,11 @@ bool FileManager::unlink(PJ_CONTEXT *ctx, const char *filename) { 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; + } + #ifdef _WIN32 try { return _wrename(UTF8ToWString(oldPath).c_str(), @@ -2750,6 +2867,75 @@ NS_PROJ_END // --------------------------------------------------------------------------- +/** 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. + * + * \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 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. + */ +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 (!fileapi) { + return false; + } + if (fileapi->version != 1) { + return false; + } + 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; + } + 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; +} + +// --------------------------------------------------------------------------- + +/** 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. + */ +void proj_context_set_sqlite3_vfs_name(PJ_CONTEXT *ctx, const char *name) { + if (ctx == nullptr) { + ctx = pj_get_default_ctx(); + } + ctx->custom_sqlite3_vfs_name = name ? name : std::string(); +} + +// --------------------------------------------------------------------------- + /** Define a custom set of callbacks for network access. * * All callbacks should be provided (non NULL pointers). diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp index 7fb248c6..6345ecf4 100644 --- a/src/iso19111/factory.cpp +++ b/src/iso19111/factory.cpp @@ -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(); @@ -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 +{ + /** 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 fb8f294c..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; @@ -707,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/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_network.cpp b/test/unit/test_network.cpp index e959fd54..688e61e5 100644 --- a/test/unit/test_network.cpp +++ b/test/unit/test_network.cpp @@ -1707,6 +1707,150 @@ TEST(networking, download_whole_files) { 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"); +} + #endif } // namespace |
