Add support for built-in file/url unzip/untar
[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
59         curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, write_callback);
60         curl_easy_setopt (curl, CURLOPT_WRITEDATA, (void*) &r->mp);
61         curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1L);
62
63         curl_easy_perform (curl);
64         curl_easy_cleanup (curl);
65
66         r->mp.lock ();
67         r->mp.done = 1;
68         r->mp.signal ();
69         r->mp.unlock ();
70
71         return NULL;
72 }
73
74 static ssize_t
75 ar_read (struct archive* a, void* d, const void** buff)
76 {
77         FileArchive::MemPipe* p = (FileArchive::MemPipe*)d;
78         size_t rv;
79
80         p->lock ();
81         while (p->size == 0) {
82                 if (p->done) {
83                         p->unlock ();
84                         return 0;
85                 }
86                 p->wait ();
87         }
88
89         rv = p->size > 8192 ? 8192 : p->size;
90         memcpy (p->buf, p->data, rv);
91         if (p->size > rv) {
92                 memmove (p->data, &p->data[rv], p->size - rv);
93         }
94         p->size -= rv;
95         *buff = p->buf;
96         p->unlock ();
97         return rv;
98 }
99
100 static int
101 ar_copy_data (struct archive *ar, struct archive *aw)
102 {
103         for (;;) {
104                 const void *buff;
105                 size_t size;
106                 int64_t offset;
107                 int r;
108                 r = archive_read_data_block (ar, &buff, &size, &offset);
109                 if (r == ARCHIVE_EOF) {
110                         return (ARCHIVE_OK);
111                 }
112                 if (r != ARCHIVE_OK) {
113                         return (r);
114                 }
115                 r = archive_write_data_block (aw, buff, size, offset);
116                 if (r != ARCHIVE_OK) {
117                         fprintf (stderr, "Extract/Write Archive: %s", archive_error_string(aw));
118                         return (r);
119                 }
120         }
121 }
122
123 static struct archive*
124 setup_archive ()
125 {
126         struct archive* a;
127         a = archive_read_new ();
128         archive_read_support_filter_all (a);
129         archive_read_support_format_all (a);
130         return a;
131 }
132
133
134 FileArchive::FileArchive (const std::string& url)
135         : _req (url)
136 {
137         if (!_req.url) {
138                 fprintf (stderr, "Invalid Archive URL/filename\n");
139                 throw failed_constructor ();
140         }
141 }
142
143 int
144 FileArchive::inflate (const std::string& destdir)
145 {
146         int rv = -1;
147         std::string pwd (Glib::get_current_dir ());
148
149         if (g_chdir (destdir.c_str ())) {
150                 fprintf (stderr, "Archive: cannot chdir to '%s'\n", destdir.c_str ());
151                 return rv;
152         }
153
154         if (_req.is_remote ()) {
155                 rv = extract_url ();
156         } else {
157                 rv = extract_file ();
158         }
159
160         g_chdir (pwd.c_str());
161         return rv;
162 }
163
164 std::vector<std::string>
165 FileArchive::contents ()
166 {
167         if (_req.is_remote ()) {
168                 return contents_url ();
169         } else {
170                 return contents_file ();
171         }
172 }
173
174 std::vector<std::string>
175 FileArchive::contents_file ()
176 {
177         struct archive* a = setup_archive ();
178         if (ARCHIVE_OK != archive_read_open_filename (a, _req.url, 8192)) {
179                 fprintf (stderr, "Error opening archive: %s\n", archive_error_string(a));
180                 return std::vector<std::string> ();
181         }
182         return get_contents (a);
183 }
184
185 std::vector<std::string>
186 FileArchive::contents_url ()
187 {
188         _req.mp.reset ();
189         pthread_create (&_tid, NULL, get_url, (void*)&_req);
190
191         struct archive* a = setup_archive ();
192         archive_read_open (a, (void*)&_req.mp, NULL, ar_read, NULL);
193         std::vector<std::string> rv (get_contents (a));
194
195         pthread_join (_tid, NULL);
196         return rv;
197 }
198
199 int
200 FileArchive::extract_file ()
201 {
202         struct archive* a = setup_archive ();
203         if (ARCHIVE_OK != archive_read_open_filename (a, _req.url, 8192)) {
204                 fprintf (stderr, "Error opening archive: %s\n", archive_error_string(a));
205                 return -1;
206         }
207         return do_extract (a);
208 }
209
210 int
211 FileArchive::extract_url ()
212 {
213         _req.mp.reset ();
214         pthread_create (&_tid, NULL, get_url, (void*)&_req);
215
216         struct archive* a = setup_archive ();
217         archive_read_open (a, (void*)&_req.mp, NULL, ar_read, NULL);
218         int rv = do_extract (a);
219
220         pthread_join (_tid, NULL);
221         return rv;
222 }
223
224 std::vector<std::string>
225 FileArchive::get_contents (struct archive* a)
226 {
227         std::vector<std::string> rv;
228         struct archive_entry* entry;
229         for (;;) {
230                 int r = archive_read_next_header (a, &entry);
231                 if (r == ARCHIVE_EOF) {
232                         break;
233                 }
234                 if (r != ARCHIVE_OK) {
235                         fprintf (stderr, "Error reading archive: %s\n", archive_error_string(a));
236                         break;
237                 }
238                 rv.push_back (archive_entry_pathname (entry));
239         }
240
241         archive_read_close (a);
242         archive_read_free (a);
243         return rv;
244 }
245
246 int
247 FileArchive::do_extract (struct archive* a)
248 {
249         int flags = ARCHIVE_EXTRACT_TIME;
250
251         int rv = 0;
252         struct archive_entry* entry;
253         struct archive *ext;
254
255         ext = archive_write_disk_new();
256         archive_write_disk_set_options(ext, flags);
257
258         for (;;) {
259                 int r = archive_read_next_header (a, &entry);
260                 if (r == ARCHIVE_EOF) {
261                         break;
262                 }
263                 if (r != ARCHIVE_OK) {
264                         fprintf (stderr, "Error reading archive: %s\n", archive_error_string(a));
265                         break;
266                 }
267
268 #if 0 // hacky alternative to chdir
269                 const std::string full_path = Glib::build_filename (destdir, archive_entry_pathname (entry));
270                 archive_entry_set_pathname (entry, full_path.c_str());
271 #endif
272
273                 r = archive_write_header(ext, entry);
274                 if (r != ARCHIVE_OK) {
275                         fprintf (stderr, "Extracting archive: %s\n", archive_error_string(ext));
276                 } else {
277                         ar_copy_data (a, ext);
278                         r = archive_write_finish_entry (ext);
279                         if (r != ARCHIVE_OK) {
280                                 fprintf (stderr, "Extracting archive: %s\n", archive_error_string(ext));
281                                 rv = -1;
282                                 break;
283                         }
284                 }
285         }
286
287         archive_read_close (a);
288         archive_read_free (a);
289         archive_write_close(ext);
290         archive_write_free(ext);
291         return rv;
292 }