]> git.sesse.net Git - vlc/blob - modules/access/archive/access.c
access/stream_filter: add libarchive
[vlc] / modules / access / archive / access.c
1 /*****************************************************************************
2  * access.c: libarchive based access
3  *****************************************************************************
4  * Copyright (C) 2014 Videolan Team
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20
21 #include "archive.h"
22
23 #include <vlc_access.h>
24 #include <vlc_url.h>
25 #include <vlc_stream.h>
26
27 #include <archive.h>
28 #include <archive_entry.h>
29
30 struct access_sys_t
31 {
32     struct archive *p_archive;
33     bool b_source_canseek;
34     uint8_t buffer[ARCHIVE_READ_SIZE];
35
36     struct archive_entry *p_entry;
37     stream_t *p_stream;
38     char *psz_uri;
39     bool b_seekable; /* Is our archive type seekable ? */
40 };
41
42 static ssize_t Read(access_t *p_access, uint8_t *p_data, size_t i_size)
43 {
44     access_sys_t *p_sys = p_access->p_sys;
45
46     size_t i_read = 0;
47
48     i_read = archive_read_data(p_sys->p_archive, p_data, i_size);
49
50     if (i_read > 0)
51         p_access->info.i_pos += i_read;
52
53     if (i_size > 0 && i_read <= 0)
54         p_access->info.b_eof = true;
55
56     return i_read;
57 }
58
59 static int Seek(access_t *p_access, uint64_t i_pos)
60 {
61     access_sys_t *p_sys = p_access->p_sys;
62
63     if (!p_sys->b_seekable)
64         return VLC_EGENERIC;
65
66     int64_t i_ret = archive_seek_data(p_sys->p_archive, i_pos, SEEK_SET);
67     if ( i_ret < ARCHIVE_OK )
68         return VLC_EGENERIC;
69     p_access->info.i_pos = i_ret;
70
71     return VLC_SUCCESS;
72 }
73
74 static ssize_t ReadCallback(struct archive *p_archive, void *p_object, const void **pp_buffer)
75 {
76     VLC_UNUSED(p_archive);
77     access_t *p_access = (access_t*)p_object;
78     access_sys_t *p_sys = p_access->p_sys;
79
80     *pp_buffer = &p_sys->buffer;
81     return stream_Read(p_sys->p_stream, &p_sys->buffer, ARCHIVE_READ_SIZE);
82 }
83
84 static ssize_t SkipCallback(struct archive *p_archive, void *p_object, ssize_t i_request)
85 {
86     VLC_UNUSED(p_archive);
87     access_t *p_access = (access_t*)p_object;
88     access_sys_t *p_sys = p_access->p_sys;
89     ssize_t i_skipped = 0;
90
91     /* be smart as small seeks converts to reads */
92     if (p_sys->b_source_canseek)
93     {
94         int64_t i_pos = stream_Tell(p_sys->p_stream);
95         if (i_pos >=0)
96             stream_Seek(p_sys->p_stream, i_pos + i_request);
97         i_skipped = stream_Tell(p_sys->p_stream) - i_pos;
98     }
99     else while(i_request)
100     {
101         int i_skip = __MIN(INT32_MAX, i_request);
102         int i_read = stream_Read(p_sys->p_stream, NULL, i_skip);
103         if (i_read > 0)
104             i_skipped += i_read;
105         else
106             break;
107         i_request -= i_read;
108     }
109
110     return i_skipped;
111 }
112
113 static ssize_t SeekCallback(struct archive *p_archive, void *p_object, ssize_t i_offset, int i_whence)
114 {
115     VLC_UNUSED(p_archive);
116     access_t *p_access = (access_t*)p_object;
117     access_sys_t *p_sys = p_access->p_sys;
118
119     ssize_t i_pos;
120
121     switch(i_whence)
122     {
123     case SEEK_CUR:
124         i_pos = stream_Tell(p_sys->p_stream);
125         break;
126     case SEEK_SET:
127         i_pos = 0;
128         break;
129     case SEEK_END:
130         i_pos = stream_Size(p_sys->p_stream) - 1;
131         break;
132     default:
133         return -1;
134     }
135
136     if (i_pos < 0)
137         return -1;
138
139     stream_Seek(p_sys->p_stream, i_pos + i_offset); /* We don't care about return val */
140     return stream_Tell(p_sys->p_stream);
141 }
142
143 static int OpenCallback(struct archive *p_archive, void *p_object)
144 {
145     VLC_UNUSED(p_archive);
146     access_t *p_access = (access_t*)p_object;
147     access_sys_t *p_sys = p_access->p_sys;
148
149     p_sys->p_stream = stream_UrlNew( p_access, p_sys->psz_uri );
150     if(!p_sys->p_stream)
151         return ARCHIVE_FATAL;
152
153     /* Seek callback must only be set if calls are guaranteed to succeed */
154     stream_Control(p_sys->p_stream, STREAM_CAN_SEEK, &p_sys->b_source_canseek);
155     if(p_sys->b_source_canseek)
156         archive_read_set_seek_callback(p_sys->p_archive, SeekCallback);
157
158     return ARCHIVE_OK;
159 }
160
161 static int CloseCallback(struct archive *p_archive, void *p_object)
162 {
163     VLC_UNUSED(p_archive);
164     access_t *p_access = (access_t*)p_object;
165
166     if (p_access->p_sys->p_stream)
167         stream_Delete(p_access->p_sys->p_stream);
168
169     return ARCHIVE_OK;
170 }
171
172 static int Control(access_t *p_access, int i_query, va_list args)
173 {
174     access_sys_t *p_sys = p_access->p_sys;
175
176     switch (i_query)
177     {
178
179     case ACCESS_CAN_SEEK:
180         *va_arg(args, bool *)= p_sys->b_seekable;
181         break;
182
183     case ACCESS_CAN_FASTSEEK:
184         if (!p_sys->b_seekable || !p_sys->p_stream)
185         {
186             *va_arg( args, bool* ) = false;
187             break;
188         }
189         else
190             return stream_vaControl( p_sys->p_stream, i_query, args );
191
192     case ACCESS_SET_PAUSE_STATE:
193         break;
194
195     case ACCESS_CAN_PAUSE:
196     case ACCESS_CAN_CONTROL_PACE:
197         *va_arg(args, bool *) = true;
198         break;
199
200     case ACCESS_GET_SIZE:
201         *va_arg(args, uint64_t *) = archive_entry_size(p_sys->p_entry);
202         break;
203
204     case ACCESS_GET_PTS_DELAY:
205         *va_arg(args, int64_t *) = DEFAULT_PTS_DELAY;
206         break;
207
208     default:
209         return VLC_EGENERIC;
210     }
211     return VLC_SUCCESS;
212 }
213
214 int AccessOpen(vlc_object_t *p_object)
215 {
216     access_t *p_access = (access_t*)p_object;
217
218     if (!strchr(p_access->psz_location, ARCHIVE_SEP_CHAR))
219         return VLC_EGENERIC;
220
221     char *psz_base = strdup(p_access->psz_location);
222     if (!psz_base)
223         return VLC_EGENERIC;
224     char *psz_name = strchr(psz_base, ARCHIVE_SEP_CHAR);
225     *psz_name++ = '\0';
226
227     if (decode_URI(psz_base) == NULL)
228     {
229         free(psz_base);
230         return VLC_EGENERIC;
231     }
232
233     access_sys_t *p_sys = p_access->p_sys = calloc(1, sizeof(access_sys_t));
234     p_sys->p_archive = archive_read_new();
235     if (!p_sys->p_archive)
236     {
237         msg_Err(p_access, "can't create libarchive instance: %s",
238                 archive_error_string(p_sys->p_archive));
239         free(psz_base);
240         goto error;
241     }
242
243     EnableArchiveFormats(p_sys->p_archive);
244
245     p_sys->psz_uri = psz_base;
246
247     if (archive_read_open2(p_sys->p_archive, p_access, OpenCallback, ReadCallback, SkipCallback, CloseCallback) != ARCHIVE_OK)
248     {
249         msg_Err(p_access, "can't open archive: %s",
250                 archive_error_string(p_sys->p_archive));
251         AccessClose(p_object);
252         return VLC_EGENERIC;
253     }
254
255     bool b_present = false;
256     while(archive_read_next_header(p_sys->p_archive, &p_sys->p_entry) == ARCHIVE_OK)
257     {
258         if (!strcmp(archive_entry_pathname(p_sys->p_entry), psz_name))
259         {
260             b_present = true;
261             break;
262         }
263         msg_Dbg(p_access, "skipping entry %s != %s", archive_entry_pathname(p_sys->p_entry), psz_name);
264     }
265
266     if (!b_present)
267     {
268         msg_Err(p_access, "entry '%s' not found in archive", psz_name);
269         /* entry not found */
270         goto error;
271     }
272
273     msg_Dbg(p_access, "reading entry %s %"PRId64, archive_entry_pathname(p_sys->p_entry),
274                                                   archive_entry_size(p_sys->p_entry));
275
276     /* try to guess if it is seekable or not (does not depend on backend) */
277     p_sys->b_seekable = (archive_seek_data(p_sys->p_archive, 0, SEEK_SET) >= 0);
278
279     p_access->pf_read    = Read;
280     p_access->pf_block   = NULL; /* libarchive's zerocopy keeps owning block :/ */
281     p_access->pf_control = Control;
282     p_access->pf_seek    = Seek;
283
284     access_InitFields(p_access);
285
286     return VLC_SUCCESS;
287
288 error:
289     AccessClose(p_object);
290     return VLC_EGENERIC;
291 }
292
293 void AccessClose(vlc_object_t *p_object)
294 {
295     access_t *p_access = (access_t*)p_object;
296     access_sys_t *p_sys = p_access->p_sys;
297
298     if (p_sys->p_archive)
299         archive_read_free(p_sys->p_archive);
300
301     free(p_sys->psz_uri);
302     free(p_sys);
303 }