32 #ifdef COLMAP_DOWNLOAD_ENABLED
39 #define OPENSSL_API_COMPAT 10100
42 #include <openssl/sha.h>
46 #pragma comment(lib, "wldap32.lib")
47 #pragma comment(lib, "crypt32.lib")
48 #pragma comment(lib, "Ws2_32.lib")
55 #define CURL_STATICLIB
58 #include <curl/curl.h>
59 #include <curl/easy.h>
72 #include <string_view>
77 extern char** environ;
85 size_t WriteCurlData(
char* buf,
88 std::ostringstream* data_stream) {
89 *data_stream << std::string_view(buf,
size * nmemb);
99 int CurlProgressCallback(
void* clientp,
104 ProgressData* progress_data =
static_cast<ProgressData*
>(clientp);
107 if (progress_data && progress_data->canceled && *progress_data->canceled) {
111 if (progress_data && progress_data->callback) {
113 progress_data->callback(
static_cast<int64_t
>(dlnow),
static_cast<int64_t
>(dltotal));
116 if (progress_data->canceled && *progress_data->canceled) {
125 const double percent = 100.0 *
static_cast<double>(dlnow) /
static_cast<double>(dltotal);
126 const double dlnow_mb =
static_cast<double>(dlnow) / (1024.0 * 1024.0);
127 const double dltotal_mb =
static_cast<double>(dltotal) / (1024.0 * 1024.0);
130 const int bar_width = 40;
131 const int filled =
static_cast<int>(bar_width * percent / 100.0);
136 for (
int i = 0; i < bar_width; ++i) {
139 }
else if (i == filled) {
145 std::cout <<
"] " << std::fixed << std::setprecision(1) << percent <<
"% ("
146 << dlnow_mb <<
"/" << dltotal_mb <<
" MB)" << std::flush;
154 static std::once_flag global_curl_init;
155 std::call_once(global_curl_init, []() {
156 curl_global_init(CURL_GLOBAL_ALL);
159 ptr = curl_easy_init();
162 ~CurlHandle() { curl_easy_cleanup(ptr); }
167 std::string SHA256DigestToHex(
const unsigned char* digest,
size_t length) {
168 std::ostringstream hex;
169 for (
size_t i = 0; i < length; ++i) {
170 hex << std::hex << std::setw(2) << std::setfill(
'0')
171 <<
static_cast<unsigned int>(digest[i]);
176 std::optional<std::string> GetEnvSafe(
const char* key) {
179 getenv_s(&
size,
nullptr, 0, key);
183 std::string value(
size,
' ');
184 getenv_s(&
size, value.data(),
size, key);
187 CHECK_EQ(value.back(),
'\0');
188 return value.substr(0,
size - 1);
193 char** env = environ;
194 const std::string_view key_sv(key);
195 for (; *env; ++env) {
196 const std::string_view key_value(*env);
197 if (key_sv.size() < key_value.size() &&
198 key_value.substr(0, key_sv.size()) == key_sv &&
199 key_value[key_sv.size()] ==
'=') {
200 return std::string(key_value.substr(
201 key_sv.size() + 1, key_value.size() - key_sv.size() - 1));
210 std::optional<std::string> DownloadFile(
211 const std::string& url,
212 DownloadProgressCallback progress_callback) {
213 std::cout <<
"Downloading file from: " << url <<
std::endl;
216 CHECK_NOTNULL(handle.ptr);
218 curl_easy_setopt(handle.ptr, CURLOPT_URL, url.c_str());
219 curl_easy_setopt(handle.ptr, CURLOPT_FOLLOWLOCATION, 1L);
220 curl_easy_setopt(handle.ptr, CURLOPT_WRITEFUNCTION, &WriteCurlData);
221 std::ostringstream data_stream;
222 curl_easy_setopt(handle.ptr, CURLOPT_WRITEDATA, &data_stream);
225 bool canceled =
false;
226 ProgressData progress_data{progress_callback, &canceled};
227 curl_easy_setopt(handle.ptr, CURLOPT_XFERINFOFUNCTION, CurlProgressCallback);
228 curl_easy_setopt(handle.ptr, CURLOPT_XFERINFODATA, &progress_data);
229 curl_easy_setopt(handle.ptr, CURLOPT_NOPROGRESS, 0L);
233 const std::optional<std::string> ssl_cert_file = GetEnvSafe(
"SSL_CERT_FILE");
234 if (ssl_cert_file.has_value() && !ssl_cert_file->empty()) {
235 VLOG(2) <<
"Using SSL_CERT_FILE: " << *ssl_cert_file;
236 curl_easy_setopt(handle.ptr, CURLOPT_CAINFO, ssl_cert_file->c_str());
238 const std::optional<std::string> ssl_cert_dir = GetEnvSafe(
"SSL_CERT_DIR");
239 if (ssl_cert_dir.has_value() && !ssl_cert_dir->empty()) {
240 VLOG(2) <<
"Using SSL_CERT_DIR: " << *ssl_cert_dir;
241 curl_easy_setopt(handle.ptr, CURLOPT_CAPATH, ssl_cert_dir->c_str());
244 const CURLcode code = curl_easy_perform(handle.ptr);
248 if (!progress_callback) {
253 if (canceled || code == CURLE_ABORTED_BY_CALLBACK) {
254 std::cerr <<
"WARNING: Download was canceled by user" <<
std::endl;
258 if (code != CURLE_OK) {
259 if (code == CURLE_SSL_CACERT_BADFILE || code == CURLE_SSL_CACERT) {
260 std::cerr <<
"ERROR: Curl SSL certificate error (code " << code
261 <<
"). Try setting SSL_CERT_FILE to point to your system's "
262 "CA certificate bundle (e.g., "
263 "SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt on "
266 std::cerr <<
"ERROR: Curl failed to perform request with code: " << code
267 <<
" (" << curl_easy_strerror(code) <<
")" <<
std::endl;
272 long response_code = 0;
273 curl_easy_getinfo(handle.ptr, CURLINFO_RESPONSE_CODE, &response_code);
274 if (response_code != 0 && (response_code < 200 || response_code >= 300)) {
275 std::cerr <<
"ERROR: Request failed with status: " << response_code <<
std::endl;
279 std::string data_str = data_stream.str();
280 std::cout <<
"Downloaded " << data_str.size() <<
" bytes ("
281 << std::fixed << std::setprecision(2)
282 <<
static_cast<double>(data_str.size()) / (1024.0 * 1024.0) <<
" MB)" <<
std::endl;
287 std::string ComputeSHA256(
const std::string_view& str) {
288 unsigned char digest[SHA256_DIGEST_LENGTH];
289 SHA256(
reinterpret_cast<const unsigned char*
>(str.data()), str.size(), digest);
290 return SHA256DigestToHex(digest, SHA256_DIGEST_LENGTH);
295 std::optional<std::filesystem::path> download_cache_dir_overwrite;
297 std::optional<std::filesystem::path> HomeDir() {
299 std::optional<std::string> userprofile = GetEnvSafe(
"USERPROFILE");
300 if (userprofile.has_value()) {
303 const std::optional<std::string> homedrive = GetEnvSafe(
"HOMEDRIVE");
304 const std::optional<std::string> homepath = GetEnvSafe(
"HOMEPATH");
305 if (!homedrive.has_value() || !homepath.has_value()) {
310 std::optional<std::string> home = GetEnvSafe(
"HOME");
311 if (!home.has_value()) {
320 std::string DownloadAndCacheFile(
321 const std::string& uri,
322 DownloadProgressCallback progress_callback) {
323 const std::vector<std::string> parts =
StringSplit(uri,
";");
324 CHECK_EQ(parts.size(), 3)
325 <<
"Invalid URI format. Expected: <url>;<name>;<sha256>";
327 const std::string& url = parts[0];
329 const std::string&
name = parts[1];
330 CHECK(!
name.empty());
331 const std::string& sha256 = parts[2];
332 CHECK_EQ(sha256.size(), 64);
335 if (download_cache_dir_overwrite.has_value()) {
336 download_cache_dir = *download_cache_dir_overwrite;
338 const std::optional<std::filesystem::path> home_dir = HomeDir();
339 CHECK(home_dir.has_value());
340 download_cache_dir = *home_dir /
".cache" /
"cloudViewer";
343 if (!std::filesystem::exists(download_cache_dir)) {
344 VLOG(2) <<
"Creating download cache directory: " << download_cache_dir;
345 CHECK(std::filesystem::create_directories(download_cache_dir));
348 const auto path = download_cache_dir / (sha256 +
"-" +
name);
350 if (std::filesystem::exists(
path)) {
351 std::cout <<
"File already cached. Using cached file at: " <<
path <<
std::endl;
352 std::vector<char> blob;
354 CHECK_EQ(ComputeSHA256(std::string_view(blob.data(), blob.size())), sha256)
355 <<
"The cached file does not match the expected SHA256";
357 std::cout <<
"File not found in cache. Downloading from: " << url <<
std::endl;
358 std::cout <<
"Cache directory: " << download_cache_dir <<
std::endl;
360 const std::optional<std::string> blob = DownloadFile(url, progress_callback);
361 CHECK(blob.has_value()) <<
"Failed to download file";
362 CHECK_EQ(ComputeSHA256(std::string_view(blob->data(), blob->size())), sha256)
363 <<
"The downloaded file does not match the expected SHA256";
365 std::vector<char> blob_vec(blob->begin(), blob->end());
367 std::cout <<
"File successfully cached at: " <<
path <<
std::endl;
370 return path.string();
374 download_cache_dir_overwrite = std::move(
path);
377 bool IsURI(
const std::string& uri) {
387 const std::vector<std::string> parts =
StringSplit(uri,
";");
388 if (parts.size() != 3) {
392 const std::string& sha256 = parts[2];
393 if (sha256.size() != 64) {
397 const std::string&
name = parts[1];
403 #ifdef COLMAP_DOWNLOAD_ENABLED
404 if (download_cache_dir_overwrite.has_value()) {
405 download_cache_dir = *download_cache_dir_overwrite;
408 const std::optional<std::filesystem::path> home_dir = HomeDir();
409 if (!home_dir.has_value()) {
412 download_cache_dir = *home_dir /
".cache" /
"cloudViewer";
413 #ifdef COLMAP_DOWNLOAD_ENABLED
417 const auto path = download_cache_dir / (sha256 +
"-" +
name);
418 if (std::filesystem::exists(
path)) {
429 #ifdef COLMAP_DOWNLOAD_ENABLED
430 return DownloadAndCacheFile(uri);
432 throw std::runtime_error(
"COLMAP was compiled without download support");
std::function< void(std::shared_ptr< core::Tensor >)> callback
QTextStream & endl(QTextStream &stream)
static const std::string path
constexpr nullopt_t nullopt
void ReadBinaryBlob(const std::string &path, std::vector< T > *data)
bool IsURI(const std::string &uri)
std::filesystem::path GetCachedFilePath(const std::string &uri)
bool StringStartsWith(const std::string &str, const std::string &prefix)
void WriteBinaryBlob(const std::string &path, const std::vector< T > &data)
std::vector< std::string > StringSplit(const std::string &str, const std::string &delim)
std::filesystem::path MaybeDownloadAndCacheFile(const std::string &uri)