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