Skip to content
This repository was archived by the owner on Jul 4, 2025. It is now read-only.

Commit d3ba576

Browse files
authored
Merge pull request #1279 from janhq/j/add-download-resume
feat: add download resume
2 parents 7cf2797 + 1bbb4c7 commit d3ba576

File tree

2 files changed

+59
-12
lines changed

2 files changed

+59
-12
lines changed

engine/services/download_service.cc

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1+
#include "download_service.h"
12
#include <curl/curl.h>
23
#include <httplib.h>
34
#include <stdio.h>
45
#include <trantor/utils/Logger.h>
56
#include <filesystem>
7+
#include <ostream>
68
#include <thread>
7-
8-
#include "download_service.h"
99
#include "exceptions/failed_curl_exception.h"
1010
#include "exceptions/failed_init_curl_exception.h"
1111
#include "exceptions/failed_open_file_exception.h"
12+
#include "utils/format_utils.h"
1213
#include "utils/logging_utils.h"
1314

1415
namespace {
@@ -19,14 +20,15 @@ size_t WriteCallback(void* ptr, size_t size, size_t nmemb, FILE* stream) {
1920
} // namespace
2021

2122
void DownloadService::AddDownloadTask(
22-
const DownloadTask& task,
23-
std::optional<OnDownloadTaskSuccessfully> callback) {
23+
DownloadTask& task, std::optional<OnDownloadTaskSuccessfully> callback) {
2424
CLI_LOG("Validating download items, please wait..");
2525
// preprocess to check if all the item are valid
2626
auto total_download_size{0};
27-
for (const auto& item : task.items) {
27+
for (auto& item : task.items) {
2828
try {
29-
total_download_size += GetFileSize(item.downloadUrl);
29+
auto size = GetFileSize(item.downloadUrl);
30+
item.bytes = size;
31+
total_download_size += size;
3032
} catch (const FailedCurlException& e) {
3133
CTL_ERR("Found invalid download item: " << item.downloadUrl << " - "
3234
<< e.what());
@@ -37,7 +39,7 @@ void DownloadService::AddDownloadTask(
3739
// all items are valid, start downloading
3840
for (const auto& item : task.items) {
3941
CLI_LOG("Start downloading: " + item.localPath.filename().string());
40-
Download(task.id, item);
42+
Download(task.id, item, true);
4143
}
4244

4345
if (callback.has_value()) {
@@ -76,15 +78,16 @@ void DownloadService::AddAsyncDownloadTask(
7678

7779
for (const auto& item : task.items) {
7880
std::thread([this, task, &callback, item]() {
79-
this->Download(task.id, item);
81+
this->Download(task.id, item, false);
8082
}).detach();
8183
}
8284

8385
// TODO: how to call the callback when all the download has finished?
8486
}
8587

8688
void DownloadService::Download(const std::string& download_id,
87-
const DownloadItem& download_item) {
89+
const DownloadItem& download_item,
90+
bool allow_resume) {
8891
CTL_INF("Absolute file output: " << download_item.localPath.string());
8992

9093
CURL* curl;
@@ -96,7 +99,43 @@ void DownloadService::Download(const std::string& download_id,
9699
throw FailedInitCurlException();
97100
}
98101

99-
file = fopen(download_item.localPath.string().c_str(), "wb");
102+
std::string mode = "wb";
103+
if (allow_resume && std::filesystem::exists(download_item.localPath) &&
104+
download_item.bytes.has_value()) {
105+
FILE* existing_file = fopen(download_item.localPath.string().c_str(), "r");
106+
fseek(existing_file, 0, SEEK_END);
107+
curl_off_t existing_file_size = ftell(existing_file);
108+
fclose(existing_file);
109+
auto missing_bytes = download_item.bytes.value() - existing_file_size;
110+
if (missing_bytes > 0) {
111+
CLI_LOG("Found unfinished download! Additional "
112+
<< format_utils::BytesToHumanReadable(missing_bytes)
113+
<< " need to be downloaded.");
114+
std::cout << "Continue download [Y/n]: " << std::flush;
115+
std::string answer{""};
116+
std::cin >> answer;
117+
if (answer == "Y" || answer == "y" || answer.empty()) {
118+
mode = "ab";
119+
CLI_LOG("Resuming download..");
120+
} else {
121+
CLI_LOG("Start over..");
122+
}
123+
} else {
124+
CLI_LOG(download_item.localPath.filename().string()
125+
<< " is already downloaded!");
126+
std::cout << "Re-download? [Y/n]: " << std::flush;
127+
128+
std::string answer = "";
129+
std::cin >> answer;
130+
if (answer == "Y" || answer == "y" || answer.empty()) {
131+
CLI_LOG("Re-downloading..");
132+
} else {
133+
return;
134+
}
135+
}
136+
}
137+
138+
file = fopen(download_item.localPath.string().c_str(), mode.c_str());
100139
if (!file) {
101140
auto err_msg{"Failed to open output file " +
102141
download_item.localPath.string()};
@@ -109,6 +148,12 @@ void DownloadService::Download(const std::string& download_id,
109148
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
110149
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
111150

151+
if (mode == "ab") {
152+
fseek(file, 0, SEEK_END);
153+
curl_off_t local_file_size = ftell(file);
154+
curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, local_file_size);
155+
}
156+
112157
res = curl_easy_perform(curl);
113158

114159
if (res != CURLE_OK) {

engine/services/download_service.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ struct DownloadItem {
2020

2121
std::optional<std::string> checksum;
2222

23+
std::optional<uint64_t> bytes;
24+
2325
std::string ToString() const {
2426
std::ostringstream output;
2527
output << "DownloadItem{id: " << id << ", downloadUrl: " << downloadUrl
@@ -54,7 +56,7 @@ class DownloadService {
5456
std::function<void(const DownloadTask& task)>;
5557

5658
void AddDownloadTask(
57-
const DownloadTask& task,
59+
DownloadTask& task,
5860
std::optional<OnDownloadTaskSuccessfully> callback = std::nullopt);
5961

6062
void AddAsyncDownloadTask(
@@ -70,5 +72,5 @@ class DownloadService {
7072

7173
private:
7274
void Download(const std::string& download_id,
73-
const DownloadItem& download_item);
75+
const DownloadItem& download_item, bool allow_resume);
7476
};

0 commit comments

Comments
 (0)