ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
Download.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
10 // BoringSSL is currently compatible with OpenSSL 1.1.0
11 #define OPENSSL_API_COMPAT 10100
12 // clang-format off
13 // Must include openssl before curl to build on Windows.
14 #include <openssl/md5.h>
15 
16 // https://stackoverflow.com/a/41873190/1255535
17 #ifdef _WIN32
18 #pragma comment(lib, "wldap32.lib")
19 #pragma comment(lib, "crypt32.lib")
20 #pragma comment(lib, "Ws2_32.lib")
21 #define USE_SSLEAY
22 #define USE_OPENSSL
23 #endif
24 
25 #define CURL_STATICLIB
26 
27 #include <curl/curl.h>
28 #include <curl/easy.h>
29 // clang-format on
30 
31 #include <FileSystem.h>
32 #include <Logging.h>
33 
34 #include <fstream>
35 #include <iomanip>
36 #include <iostream>
37 #include <sstream>
38 #include <string>
39 #include <unordered_set>
40 
42 
43 namespace cloudViewer {
44 namespace utility {
45 
46 std::string GetMD5(const std::string& file_path) {
47  if (!utility::filesystem::FileExists(file_path)) {
48  utility::LogError("{} does not exist.", file_path);
49  }
50 
51  std::ifstream fp(file_path.c_str(), std::ios::in | std::ios::binary);
52 
53  if (!fp.good()) {
54  std::ostringstream os;
55  utility::LogError("Cannot open {}", file_path);
56  }
57 
58  constexpr const std::size_t buffer_size{1 << 12}; // 4 KiB
59  char buffer[buffer_size];
60  unsigned char hash[MD5_DIGEST_LENGTH] = {0};
61 
62  MD5_CTX ctx;
63  MD5_Init(&ctx);
64 
65  while (fp.good()) {
66  fp.read(buffer, buffer_size);
67  MD5_Update(&ctx, buffer, fp.gcount());
68  }
69 
70  MD5_Final(hash, &ctx);
71  fp.close();
72 
73  std::ostringstream os;
74  os << std::hex << std::setfill('0');
75 
76  for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
77  os << std::setw(2) << static_cast<unsigned int>(hash[i]);
78  }
79 
80  return os.str();
81 }
82 
83 static size_t WriteDataCb(void* ptr, size_t size, size_t nmemb, FILE* stream) {
84  size_t written;
85  written = fwrite(ptr, size, nmemb, stream);
86  return written;
87 }
88 
89 std::string DownloadFromURL(const std::string& url,
90  const std::string& md5,
91  const std::string& download_dir) {
92  // Sanity checks.
93  if (md5.size() != MD5_DIGEST_LENGTH * 2) {
94  utility::LogError("Invalid md5 length {}, expected to be {}.",
95  md5.size(), MD5_DIGEST_LENGTH * 2);
96  }
97  if (download_dir.empty()) {
98  utility::LogError("download_dir cannot be empty.");
99  }
100 
101  // Resolve path.
102  const std::string file_name =
104  const std::string file_path = download_dir + "/" + file_name;
105  if (!utility::filesystem::DirectoryExists(download_dir)) {
107  }
108 
109  // Check if the file exists.
110  if (utility::filesystem::FileExists(file_path) &&
111  GetMD5(file_path) == md5) {
112  utility::LogInfo("{} exists and MD5 matches. Download skipped.",
113  file_path);
114  return file_path;
115  }
116 
117  // Always print URL to inform the user. If the download fails, the user
118  // knows the URL.
119  utility::LogInfo("Downloading {}", url);
120 
121  // Download.
122  CURL* curl;
123  FILE* fp;
124  CURLcode res;
125  curl = curl_easy_init();
126  if (!curl) {
127  utility::LogError("Failed to initialize CURL.");
128  }
129  fp = fopen(file_path.c_str(), "wb");
130  if (!fp) {
131  utility::LogError("Failed to open file {}.", file_path);
132  }
133  curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
134  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // -L redirection.
135  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
136  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false);
137  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteDataCb);
138  curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
139  res = curl_easy_perform(curl);
140  curl_easy_cleanup(curl);
141  fclose(fp);
142 
143  if (res == CURLE_OK) {
144  const std::string actual_md5 = GetMD5(file_path);
145  if (actual_md5 == md5) {
146  utility::LogInfo("Downloaded to {}", file_path);
147  } else {
149  "MD5 mismatch for {}.\n- Expected: {}\n- Actual : {}",
150  file_path, md5, actual_md5);
151  }
152  } else {
153  utility::LogError("Download failed with error code: {}.",
154  curl_easy_strerror(res));
155  }
156 
157  return file_path;
158 }
159 
160 std::string DownloadFromMirrors(const std::vector<std::string>& mirrors,
161  const std::string& md5,
162  const std::string& download_dir) {
163  // All file names must be the same in mirrors.
164  if (mirrors.empty()) {
165  utility::LogError("No mirror URLs provided.");
166  }
167  const std::string file_name =
169  for (const std::string& url : mirrors) {
171  file_name) {
172  utility::LogError("File name mismatch in mirrors {}.", mirrors);
173  }
174  }
175 
176  for (const std::string& url : mirrors) {
177  try {
178  return DownloadFromURL(url, md5, download_dir);
179  } catch (const std::exception& ex) {
180  utility::LogWarning("Failed to download from {}. Exception {}.",
181  url, ex.what());
182  }
183  }
184 
185  utility::LogError("Downloading failed from available mirrors.");
186 }
187 
188 } // namespace utility
189 } // namespace cloudViewer
int size
#define LogWarning(...)
Definition: Logging.h:72
#define LogInfo(...)
Definition: Logging.h:81
#define LogError(...)
Definition: Logging.h:60
bool MakeDirectoryHierarchy(const std::string &directory)
Definition: FileSystem.cpp:499
bool DirectoryExists(const std::string &directory)
Definition: FileSystem.cpp:473
bool FileExists(const std::string &filename)
Definition: FileSystem.cpp:524
std::string GetFileNameWithoutDirectory(const std::string &filename)
Definition: FileSystem.cpp:301
std::string DownloadFromURL(const std::string &url, const std::string &md5, const std::string &download_dir)
Download a file from URL. If a file already exists and the MD5 hash matches, the download will be ski...
Definition: Download.cpp:89
std::string GetMD5(const std::string &file_path)
Computes MD5 Hash for the given file.
Definition: Download.cpp:46
std::string DownloadFromMirrors(const std::vector< std::string > &mirrors, const std::string &md5, const std::string &download_dir)
Download a file from list of mirror URLs. If a file already exists and the MD5 hash matches,...
Definition: Download.cpp:160
static size_t WriteDataCb(void *ptr, size_t size, size_t nmemb, FILE *stream)
Definition: Download.cpp:83
Generic file read and write utility for python interface.