Fix yet another oversight for the windows icon file update
[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 #include <fcntl.h>
23 #include <sys/stat.h>
24
25 #include <glib.h>
26 #include "pbd/gstdio_compat.h"
27 #include <glibmm.h>
28
29 #include <archive.h>
30 #include <archive_entry.h>
31 #include <curl/curl.h>
32
33 #include "pbd/failed_constructor.h"
34 #include "pbd/file_archive.h"
35 #include "pbd/file_utils.h"
36
37 using namespace PBD;
38
39 static size_t
40 write_callback (void* buffer, size_t size, size_t nmemb, void* d)
41 {
42         FileArchive::MemPipe* p = (FileArchive::MemPipe*)d;
43         size_t realsize = size * nmemb;
44
45         p->lock ();
46         p->data = (uint8_t*) realloc ((void*) p->data, p->size + realsize);
47         memcpy (&p->data[p->size], buffer, realsize);
48         p->size += realsize;
49         p->signal ();
50         p->unlock ();
51         return realsize;
52 }
53
54 static void*
55 get_url (void* arg)
56 {
57         FileArchive::Request* r = (FileArchive::Request*) arg;
58         CURL* curl;
59
60         curl = curl_easy_init ();
61         curl_easy_setopt (curl, CURLOPT_URL, r->url);
62         curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1L);
63
64         /* get size */
65         if (r->mp.progress) {
66                 curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
67                 curl_easy_setopt(curl, CURLOPT_HEADER, 0L);
68                 curl_easy_perform (curl);
69                 curl_easy_getinfo (curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &r->mp.length);
70         }
71
72         curl_easy_setopt(curl, CURLOPT_NOBODY, 0L);
73         curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, write_callback);
74         curl_easy_setopt (curl, CURLOPT_WRITEDATA, (void*) &r->mp);
75         curl_easy_perform (curl);
76         curl_easy_cleanup (curl);
77
78         r->mp.lock ();
79         r->mp.done = 1;
80         r->mp.signal ();
81         r->mp.unlock ();
82
83         return NULL;
84 }
85
86 static ssize_t
87 ar_read (struct archive* a, void* d, const void** buff)
88 {
89         FileArchive::MemPipe* p = (FileArchive::MemPipe*)d;
90         size_t rv;
91
92         p->lock ();
93         while (p->size == 0) {
94                 if (p->done) {
95                         p->unlock ();
96                         return 0;
97                 }
98                 p->wait ();
99         }
100
101         rv = p->size > 8192 ? 8192 : p->size;
102         memcpy (p->buf, p->data, rv);
103         if (p->size > rv) {
104                 memmove (p->data, &p->data[rv], p->size - rv);
105         }
106         p->size -= rv;
107         p->processed += rv;
108         *buff = p->buf;
109         if (p->progress) {
110                 p->progress->progress (p->processed, p->length);
111         }
112         p->unlock ();
113         return rv;
114 }
115
116 static int
117 ar_copy_data (struct archive *ar, struct archive *aw)
118 {
119         for (;;) {
120                 const void *buff;
121                 size_t size;
122                 int64_t offset;
123                 int r;
124                 r = archive_read_data_block (ar, &buff, &size, &offset);
125                 if (r == ARCHIVE_EOF) {
126                         return (ARCHIVE_OK);
127                 }
128                 if (r != ARCHIVE_OK) {
129                         return (r);
130                 }
131                 r = archive_write_data_block (aw, buff, size, offset);
132                 if (r != ARCHIVE_OK) {
133                         fprintf (stderr, "Extract/Write Archive: %s", archive_error_string(aw));
134                         return (r);
135                 }
136         }
137 }
138
139 static struct archive*
140 setup_archive ()
141 {
142         struct archive* a;
143         a = archive_read_new ();
144         archive_read_support_filter_all (a);
145         archive_read_support_format_all (a);
146         return a;
147 }
148
149 FileArchive::FileArchive (const std::string& url)
150         : _req (url)
151 {
152         if (!_req.url) {
153                 fprintf (stderr, "Invalid Archive URL/filename\n");
154                 throw failed_constructor ();
155         }
156
157         if (_req.is_remote ()) {
158                 _req.mp.progress = this;
159         } else {
160                 _req.mp.progress = 0;
161         }
162 }
163
164 int
165 FileArchive::inflate (const std::string& destdir)
166 {
167         int rv = -1;
168         std::string pwd (Glib::get_current_dir ());
169
170         if (g_chdir (destdir.c_str ())) {
171                 fprintf (stderr, "Archive: cannot chdir to '%s'\n", destdir.c_str ());
172                 return rv;
173         }
174
175         if (_req.is_remote ()) {
176                 rv = extract_url ();
177         } else {
178                 rv = extract_file ();
179         }
180
181         g_chdir (pwd.c_str());
182         return rv;
183 }
184
185 std::vector<std::string>
186 FileArchive::contents ()
187 {
188         if (_req.is_remote ()) {
189                 return contents_url ();
190         } else {
191                 return contents_file ();
192         }
193 }
194
195 std::vector<std::string>
196 FileArchive::contents_file ()
197 {
198         struct archive* a = setup_archive ();
199         GStatBuf statbuf;
200         if (!g_stat (_req.url, &statbuf)) {
201                 _req.mp.length = statbuf.st_size;
202         } else {
203                 _req.mp.length = -1;
204         }
205         if (ARCHIVE_OK != archive_read_open_filename (a, _req.url, 8192)) {
206                 fprintf (stderr, "Error opening archive: %s\n", archive_error_string(a));
207                 return std::vector<std::string> ();
208         }
209         return get_contents (a);
210 }
211
212 std::vector<std::string>
213 FileArchive::contents_url ()
214 {
215         _req.mp.reset ();
216         pthread_create (&_tid, NULL, get_url, (void*)&_req);
217
218         struct archive* a = setup_archive ();
219         archive_read_open (a, (void*)&_req.mp, NULL, ar_read, NULL);
220         std::vector<std::string> rv (get_contents (a));
221
222         pthread_join (_tid, NULL);
223         return rv;
224 }
225
226 int
227 FileArchive::extract_file ()
228 {
229         struct archive* a = setup_archive ();
230         GStatBuf statbuf;
231         if (!g_stat (_req.url, &statbuf)) {
232                 _req.mp.length = statbuf.st_size;
233         } else {
234                 _req.mp.length = -1;
235         }
236         if (ARCHIVE_OK != archive_read_open_filename (a, _req.url, 8192)) {
237                 fprintf (stderr, "Error opening archive: %s\n", archive_error_string(a));
238                 return -1;
239         }
240         return do_extract (a);
241 }
242
243 int
244 FileArchive::extract_url ()
245 {
246         _req.mp.reset ();
247         pthread_create (&_tid, NULL, get_url, (void*)&_req);
248
249         struct archive* a = setup_archive ();
250         archive_read_open (a, (void*)&_req.mp, NULL, ar_read, NULL);
251         int rv = do_extract (a);
252
253         pthread_join (_tid, NULL);
254         return rv;
255 }
256
257 std::vector<std::string>
258 FileArchive::get_contents (struct archive* a)
259 {
260         std::vector<std::string> rv;
261         struct archive_entry* entry;
262         for (;;) {
263                 int r = archive_read_next_header (a, &entry);
264                 if (!_req.mp.progress) {
265                         // file i/o -- not URL
266                         const uint64_t read = archive_filter_bytes (a, -1);
267                         progress (read, _req.mp.length);
268                 }
269                 if (r == ARCHIVE_EOF) {
270                         break;
271                 }
272                 if (r != ARCHIVE_OK) {
273                         fprintf (stderr, "Error reading archive: %s\n", archive_error_string(a));
274                         break;
275                 }
276                 rv.push_back (archive_entry_pathname (entry));
277         }
278
279         archive_read_close (a);
280         archive_read_free (a);
281         return rv;
282 }
283
284 int
285 FileArchive::do_extract (struct archive* a)
286 {
287         int flags = ARCHIVE_EXTRACT_TIME;
288
289         int rv = 0;
290         struct archive_entry* entry;
291         struct archive *ext;
292
293         ext = archive_write_disk_new();
294         archive_write_disk_set_options(ext, flags);
295
296         for (;;) {
297                 int r = archive_read_next_header (a, &entry);
298                 if (!_req.mp.progress) {
299                         // file i/o -- not URL
300                         const uint64_t read = archive_filter_bytes (a, -1);
301                         progress (read, _req.mp.length);
302                 }
303
304                 if (r == ARCHIVE_EOF) {
305                         break;
306                 }
307                 if (r != ARCHIVE_OK) {
308                         fprintf (stderr, "Error reading archive: %s\n", archive_error_string(a));
309                         break;
310                 }
311
312 #if 0 // hacky alternative to chdir
313                 const std::string full_path = Glib::build_filename (destdir, archive_entry_pathname (entry));
314                 archive_entry_set_pathname (entry, full_path.c_str());
315 #endif
316
317                 r = archive_write_header(ext, entry);
318                 if (r != ARCHIVE_OK) {
319                         fprintf (stderr, "Extracting archive: %s\n", archive_error_string(ext));
320                 } else {
321                         ar_copy_data (a, ext);
322                         r = archive_write_finish_entry (ext);
323                         if (r != ARCHIVE_OK) {
324                                 fprintf (stderr, "Extracting archive: %s\n", archive_error_string(ext));
325                                 rv = -1;
326                                 break;
327                         }
328                 }
329         }
330
331         archive_read_close (a);
332         archive_read_free (a);
333         archive_write_close(ext);
334         archive_write_free(ext);
335         return rv;
336 }
337
338
339 int
340 FileArchive::create (const std::string& srcdir)
341 {
342         if (_req.is_remote ()) {
343                 return -1;
344         }
345
346         std::string parent = Glib::path_get_dirname (srcdir);
347         size_t p_len = parent.size () + 1;
348
349         Searchpath sp (srcdir);
350         std::vector<std::string> files;
351         find_files_matching_pattern (files, sp, "*");
352
353         std::map<std::string, std::string> filemap;
354
355         for (std::vector<std::string>::const_iterator f = files.begin (); f != files.end (); ++f) {
356                 assert (f->size () > p_len);
357                 filemap[*f] = f->substr (p_len);
358         }
359
360         return create (filemap);
361 }
362
363 int
364 FileArchive::create (const std::map<std::string, std::string>& filemap)
365 {
366         struct archive *a;
367         struct archive_entry *entry;
368
369         size_t read_bytes = 0;
370         size_t total_bytes = 0;
371
372         for (std::map<std::string, std::string>::const_iterator f = filemap.begin (); f != filemap.end (); ++f) {
373                 GStatBuf statbuf;
374                 if (g_stat (f->first.c_str(), &statbuf)) {
375                         continue;
376                 }
377                 total_bytes += statbuf.st_size;
378         }
379
380         if (total_bytes == 0) {
381                 return -1;
382         }
383
384         progress (0, total_bytes);
385
386         a = archive_write_new ();
387         archive_write_set_format_pax_restricted (a);
388         archive_write_add_filter_lzma (a);
389         archive_write_open_filename (a, _req.url);
390         entry = archive_entry_new ();
391
392         for (std::map<std::string, std::string>::const_iterator f = filemap.begin (); f != filemap.end (); ++f) {
393                 char buf[8192];
394                 const char* filepath = f->first.c_str ();
395                 const char* filename = f->second.c_str ();
396
397                 GStatBuf statbuf;
398                 if (g_stat (filepath, &statbuf)) {
399                         continue;
400                 }
401
402                 archive_entry_clear (entry);
403
404 #ifdef PLATFORM_WINDOWS
405                 archive_entry_set_size (entry, statbuf.st_size);
406                 archive_entry_set_atime (entry, statbuf.st_atime, 0);
407                 archive_entry_set_ctime (entry, statbuf.st_ctime, 0);
408                 archive_entry_set_mtime (entry, statbuf.st_mtime, 0);
409 #else
410                 archive_entry_copy_stat (entry, &statbuf);
411 #endif
412
413                 archive_entry_set_pathname (entry, filename);
414                 archive_entry_set_filetype (entry, AE_IFREG);
415                 archive_entry_set_perm (entry, 0644);
416
417                 archive_write_header (a, entry);
418
419                 int fd = g_open (filepath, O_RDONLY, 0444);
420                 assert (fd >= 0);
421
422                 ssize_t len = read (fd, buf, sizeof (buf));
423                 while (len > 0) {
424                         read_bytes += len;
425                         archive_write_data (a, buf, len);
426                         progress (read_bytes, total_bytes);
427                         len = read (fd, buf, sizeof (buf));
428                 }
429                 close (fd);
430         }
431
432         archive_entry_free (entry);
433         archive_write_close (a);
434         archive_write_free (a);
435
436         return 0;
437 }