Chesto 0.9
A declarative and element-based library for creating GUIs on homebrew'd consoles
DownloadQueue.cpp
1#include "DownloadQueue.hpp"
2
3#if defined(USE_RAMFS)
4#define RAMFS "resin:/"
5#else
6#define RAMFS "resin/"
7#endif
8
9#define MAX_PARALLEL_DOWNLOADS 4
10
11
12#define SOCU_ALIGN 0x1000
13#define SOCU_BUFFERSIZE 0x100000
14
15#ifndef SO_TCPSACK
16#define SO_TCPSACK 0x00200 /* Allow TCP SACK (Selective acknowledgment) */
17#endif
18
19#ifndef SO_WINSCALE
20#define SO_WINSCALE 0x00400 /* Set scaling window option */
21#endif
22
23#ifndef SO_RCVBUF
24#define SO_RCVBUF 0x01002 /* Receive buffer size */
25#endif
26
27#ifndef NETWORK_MOCK
28// networking optimizations adapted from:
29// - https://github.com/samdejong86/Arria-V-ADC-Ethernet-software/blob/master/ADC_Socket_bsp/iniche/src/h/socket.h
30int sockopt_callback_chesto(void *clientp, curl_socket_t curlfd, curlsocktype purpose)
31{
32 int winscale = 1, rcvbuf = 0x20000, tcpsack = 1;
33#ifndef WIN32
34 setsockopt(curlfd, SOL_SOCKET, SO_WINSCALE, &winscale, sizeof(int));
35 setsockopt(curlfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(int));
36 setsockopt(curlfd, SOL_SOCKET, SO_TCPSACK, &tcpsack, sizeof(int));
37#endif
38 return 0;
39}
40#endif
41
42DownloadQueue* DownloadQueue::downloadQueue = NULL;
43
44void DownloadQueue::init()
45{
46 downloadQueue = new DownloadQueue();
47}
48
49void DownloadQueue::quit()
50{
51 delete downloadQueue;
52}
53
54static size_t WriteCallback(char *data, size_t n, size_t l, void *userp)
55{
56 DownloadOperation *download = (DownloadOperation *)userp;
57 download->buffer.append(data, n * l);
58 return n * l;
59}
60
61DownloadQueue::DownloadQueue()
62{
63#ifndef NETWORK_MOCK
64 cm = curl_multi_init();
65 curl_multi_setopt(cm, CURLMOPT_MAXCONNECTS, MAX_PARALLEL_DOWNLOADS);
66#endif
67}
68
69DownloadQueue::~DownloadQueue()
70{
71#ifndef NETWORK_MOCK
72 curl_multi_cleanup(cm);
73 cm = NULL;
74#endif
75}
76
77// add a new download operation
79{
80 download->status = DownloadStatus::QUEUED;
81 queue.push_back(download);
82}
83
84// cancel a download operation
86{
87 if (download->status == DownloadStatus::DOWNLOADING)
88 transferFinish(download);
89 else if (download->status == DownloadStatus::QUEUED && queue.size() > 0)
90 queue.remove(download);
91}
92
93#ifndef NETWORK_MOCK
94void DownloadQueue::setPlatformCurlFlags(CURL* c)
95{
96 // from https://github.com/GaryOderNichts/wiiu-examples/blob/main/curl-https/romfs/cacert.pem
97 curl_easy_setopt(c, CURLOPT_CAINFO, RAMFS "res/cacert.pem");
98
99 curl_easy_setopt(c, CURLOPT_SOCKOPTFUNCTION, sockopt_callback_chesto);
100}
101#endif
102
103// start a transfer operation
104void DownloadQueue::transferStart(DownloadOperation *download)
105{
106#ifndef NETWORK_MOCK
107 download->eh = curl_easy_init();
108
109 setPlatformCurlFlags(download->eh);
110
111 curl_easy_setopt(download->eh, CURLOPT_URL, download->url.c_str());
112 curl_easy_setopt(download->eh, CURLOPT_WRITEFUNCTION, WriteCallback);
113 curl_easy_setopt(download->eh, CURLOPT_WRITEDATA, download);
114 curl_easy_setopt(download->eh, CURLOPT_PRIVATE, download);
115
116 curl_easy_setopt(download->eh, CURLOPT_FOLLOWLOCATION, 1L);
117
118 curl_multi_add_handle(cm, download->eh);
119#endif
120 transfers++;
121}
122
123// finish a transfer operation
124void DownloadQueue::transferFinish(DownloadOperation *download)
125{
126#ifndef NETWORK_MOCK
127 if (cm != NULL) {
128 curl_multi_remove_handle(cm, download->eh);
129 }
130 curl_easy_cleanup(download->eh);
131#endif
132 transfers--;
133}
134
135// start new transfers from the queue
136void DownloadQueue::startTransfersFromQueue()
137{
138#ifndef NETWORK_MOCK
139 while ((transfers < MAX_PARALLEL_DOWNLOADS) && (queue.size() > 0))
140 {
141 // remove the first element from the download queue
142 DownloadOperation *download = queue.front();
143 queue.pop_front();
144
145 // start a new download
146 download->status = DownloadStatus::DOWNLOADING;
147 transferStart(download);
148 }
149#endif
150}
151
152// process finished and queued downloads
154{
155#ifndef NETWORK_MOCK
156 DownloadOperation *download;
157 int still_alive = 1;
158 int msgs_left = -1;
159
160 CURLMsg *msg;
161
162 curl_multi_perform(cm, &still_alive);
163
164 // handle completed or failed downloads
165 while(msg = curl_multi_info_read(cm, &msgs_left))
166 {
167 long response_code = 404;
168
169 if (msg->msg != CURLMSG_DONE)
170 continue;
171
172 curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &download);
173 curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &response_code);
174
175 transferFinish(download);
176 startTransfersFromQueue();
177
178 if (response_code == 200)
179 download->status = DownloadStatus::COMPLETE;
180 else
181 download->status = DownloadStatus::FAILED;
182
183 download->cb(download);
184 }
185
186 startTransfersFromQueue();
187
188 return ((still_alive) || (msgs_left > 0) || (queue.size() > 0));
189#else
190 return false;
191#endif
192}
int process()
process finished and queued downloads
void downloadAdd(DownloadOperation *download)
add a new download operation
void downloadCancel(DownloadOperation *download)
cancel a download operation