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