4afc9abad98f9fe93907a85f97c37774e777f147
[ardour.git] / libs / pbd / file_archive.cc
1 /*
2  * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */
18
19 #include <stdlib.h>
20 #include <string.h>
21 #include <cstdio>
22
23 #include "pbd/gstdio_compat.h"
24 #include <glibmm.h>
25
26 #include <archive.h>
27 #include <archive_entry.h>
28 #include <curl/curl.h>
29
30 #include "pbd/failed_constructor.h"
31 #include "pbd/file_archive.h"
32
33 using namespace PBD;
34
35 static size_t
36 write_callback (void* buffer, size_t size, size_t nmemb, void* d)
37 {
38         FileArchive::MemPipe* p = (FileArchive::MemPipe*)d;
39         size_t realsize = size * nmemb;
40
41         p->lock ();
42         p->data = (uint8_t*) realloc ((void*) p->data, p->size + realsize);
43         memcpy (&p->data[p->size], buffer, realsize);
44         p->size += realsize;
45         p->signal ();
46         p->unlock ();
47         return realsize;
48 }
49
50 static void*
51 get_url (void* arg)
52 {
53         FileArchive::Request* r = (FileArchive::Request*) arg;
54         CURL* curl;
55
56         curl = curl_easy_init ();
57         curl_easy_setopt (curl, CURLOPT_URL, r->url);
58         curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1L);
59
60         /* get size */
61         if (r->mp.progress) {
62                 curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
63                 curl_easy_setopt(curl, CURLOPT_HEADER, 0L);
64                 curl_easy_perform (curl);
65                 curl_easy_getinfo (curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &r->mp.length);
66         }
67
68         curl_easy_setopt(curl, CURLOPT_NOBODY, 0L);
69         curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, write_callback);
70         curl_easy_setopt (curl, CURLOPT_WRITEDATA, (void*) &r->mp);
71         curl_easy_perform (curl);
72         curl_easy_cleanup (curl);
73
74         r->mp.lock ();
75         r->mp.done = 1;
76         r->mp.signal ();
77         r->mp.unlock ();
78
79         return NULL;
80 }
81
82 static ssize_t
83 ar_read (struct archive* a, void* d, const void** buff)
84 {
85         FileArchive::MemPipe* p = (FileArchive::MemPipe*)d;
86         size_t rv;
87
88         p->lock ();
89         while (p->size == 0) {
90                 if (p->done) {
91                         p->unlock ();
92                         return 0;
93                 }
94                 p->wait ();
95         }
96
97         rv = p->size > 8192 ? 8192 : p->size;
98         memcpy (p->buf, p->data, rv);
99         if (p->size > rv) {
100                 memmove (p->data, &p->data[rv], p->size - rv);
101         }
102         p->size -= rv;
103         p->processed += rv;
104         *buff = p->buf;
105         if (p->progress) {
106                 p->progress->progress (p->processed, p->length);
107         }
108         p->unlock ();
109         return rv;
110 }
111
112 static int
113 ar_copy_data (struct archive *ar, struct archive *aw)
114 {
115         for (;;) {
116                 const void *buff;
117                 size_t size;
118                 int64_t offset;
119                 int r;
120                 r = archive_read_data_block (ar, &buff, &size, &offset);
121                 if (r == ARCHIVE_EOF) {
122                         return (ARCHIVE_OK);
123                 }
124                 if (r != ARCHIVE_OK) {
125                         return (r);
126                 }
127                 r = archive_write_data_block (aw, buff, size, offset);
128                 if (r != ARCHIVE_OK) {
129                         fprintf (stderr, "Extract/Write Archive: %s", archive_error_string(aw));
130                         return (r);
131                 }
132         }
133 }
134
135 static struct archive*
136 setup_archive ()
137 {
138         struct archive* a;
139         a = archive_read_new ();
140         archive_read_support_filter_all (a);
141         archive_read_support_format_all (a);
142         return a;
143 }
144
145 FileArchive::FileArchive (const std::string& url)
146         : _req (url)
147 {
148         if (!_req.url) {
149                 fprintf (stderr, "Invalid Archive URL/filename\n");
150                 throw failed_constructor ();
151         }
152
153         if (_req.is_remote ()) {
154                 _req.mp.progress = this;
155         } else {
156                 _req.mp.progress = 0;
157         }
158 }
159
160 int
161 FileArchive::inflate (const std::string& destdir)
162 {
163         int rv = -1;
164         std::string pwd (Glib::get_current_dir ());
165
166         if (g_chdir (destdir.c_str ())) {
167                 fprintf (stderr, "Archive: cannot chdir to '%s'\n", destdir.c_str ());
168                 return rv;
169         }
170
171         if (_req.is_remote ()) {
172                 rv = extract_url ();
173         } else {
174                 rv = extract_file ();
175         }
176
177         g_chdir (pwd.c_str());
178         return rv;
179 }
180
181 std::vector<std::string>
182 FileArchive::contents ()
183 {
184         if (_req.is_remote ()) {
185                 return contents_url ();
186         } else {
187                 return contents_file ();
188         }
189 }
190
191 std::vector<std::string>
192 FileArchive::contents_file ()
193 {
194         struct archive* a = setup_archive ();
195         GStatBuf statbuf;
196         if (!g_stat (_req.url, &statbuf)) {
197                 _req.mp.length = statbuf.st_size;
198         } else {
199                 _req.mp.length = -1;
200         }
201         if (ARCHIVE_OK != archive_read_open_filename (a, _req.url, 8192)) {
202                 fprintf (stderr, "Error opening archive: %s\n", archive_error_string(a));
203                 return std::vector<std::string> ();
204         }
205         return get_contents (a);
206 }
207
208 std::vector<std::string>
209 FileArchive::contents_url ()
210 {
211         _req.mp.reset ();
212         pthread_create (&_tid, NULL, get_url, (void*)&_req);
213
214         struct archive* a = setup_archive ();
215         archive_read_open (a, (void*)&_req.mp, NULL, ar_read, NULL);
216         std::vector<std::string> rv (get_contents (a));
217
218         pthread_join (_tid, NULL);
219         return rv;
220 }
221
222 int
223 FileArchive::extract_file ()
224 {
225         struct archive* a = setup_archive ();
226         GStatBuf statbuf;
227         if (!g_stat (_req.url, &statbuf)) {
228                 _req.mp.length = statbuf.st_size;
229         } else {
230                 _req.mp.length = -1;
231         }
232         if (ARCHIVE_OK != archive_read_open_filename (a, _req.url, 8192)) {
233                 fprintf (stderr, "Error opening archive: %s\n", archive_error_string(a));
234                 return -1;
235         }
236         return do_extract (a);
237 }
238
239 int
240 FileArchive::extract_url ()
241 {
242         _req.mp.reset ();
243         pthread_create (&_tid, NULL, get_url, (void*)&_req);
244
245         struct archive* a = setup_archive ();
246         archive_read_open (a, (void*)&_req.mp, NULL, ar_read, NULL);
247         int rv = do_extract (a);
248
249         pthread_join (_tid, NULL);
250         return rv;
251 }
252
253 std::vector<std::string>
254 FileArchive::get_contents (struct archive* a)
255 {
256         std::vector<std::string> rv;
257         struct archive_entry* entry;
258         for (;;) {
259                 int r = archive_read_next_header (a, &entry);
260                 if (!_req.mp.progress) {
261                         // file i/o -- not URL
262                         const uint64_t read = archive_filter_bytes (a, -1);
263                         progress (read, _req.mp.length);
264                 }
265                 if (r == ARCHIVE_EOF) {
266                         break;
267                 }
268                 if (r != ARCHIVE_OK) {
269                         fprintf (stderr, "Error reading archive: %s\n", archive_error_string(a));
270                         break;
271                 }
272                 rv.push_back (archive_entry_pathname (entry));
273         }
274
275         archive_read_close (a);
276         archive_read_free (a);
277         return rv;
278 }
279
280 int
281 FileArchive::do_extract (struct archive* a)
282 {
283         int flags = ARCHIVE_EXTRACT_TIME;
284
285         int rv = 0;
286         struct archive_entry* entry;
287         struct archive *ext;
288
289         ext = archive_write_disk_new();
290         archive_write_disk_set_options(ext, flags);
291
292         for (;;) {
293                 int r = archive_read_next_header (a, &entry);
294                 if (!_req.mp.progress) {
295                         // file i/o -- not URL
296                         const uint64_t read = archive_filter_bytes (a, -1);
297                         progress (read, _req.mp.length);
298                 }
299
300                 if (r == ARCHIVE_EOF) {
301                         break;
302                 }
303                 if (r != ARCHIVE_OK) {
304                         fprintf (stderr, "Error reading archive: %s\n", archive_error_string(a));
305                         break;
306                 }
307
308 #if 0 // hacky alternative to chdir
309                 const std::string full_path = Glib::build_filename (destdir, archive_entry_pathname (entry));
310                 archive_entry_set_pathname (entry, full_path.c_str());
311 #endif
312
313                 r = archive_write_header(ext, entry);
314                 if (r != ARCHIVE_OK) {
315                         fprintf (stderr, "Extracting archive: %s\n", archive_error_string(ext));
316                 } else {
317                         ar_copy_data (a, ext);
318                         r = archive_write_finish_entry (ext);
319                         if (r != ARCHIVE_OK) {
320                                 fprintf (stderr, "Extracting archive: %s\n", archive_error_string(ext));
321                                 rv = -1;
322                                 break;
323                         }
324                 }
325         }
326
327         archive_read_close (a);
328         archive_read_free (a);
329         archive_write_close(ext);
330         archive_write_free(ext);
331         return rv;
332 }