diff options
Diffstat (limited to 'stream/stream_libarchive.c')
-rw-r--r-- | stream/stream_libarchive.c | 170 |
1 files changed, 155 insertions, 15 deletions
diff --git a/stream/stream_libarchive.c b/stream/stream_libarchive.c index 21e1111671..7aa7feb073 100644 --- a/stream/stream_libarchive.c +++ b/stream/stream_libarchive.c @@ -18,31 +18,39 @@ #include <archive.h> #include <archive_entry.h> +#include "misc/bstr.h" #include "common/common.h" #include "stream.h" #include "stream_libarchive.h" +struct mp_archive_volume { + struct mp_archive *mpa; + struct stream *src; + char *url; +}; + static ssize_t read_cb(struct archive *arch, void *priv, const void **buffer) { - struct mp_archive *mpa = priv; - int res = stream_read_partial(mpa->src, mpa->buffer, sizeof(mpa->buffer)); - *buffer = mpa->buffer; + struct mp_archive_volume *vol = priv; + int res = stream_read_partial(vol->src, vol->mpa->buffer, + sizeof(vol->mpa->buffer)); + *buffer = vol->mpa->buffer; return MPMAX(res, 0); } static int64_t seek_cb(struct archive *arch, void *priv, int64_t offset, int whence) { - struct mp_archive *mpa = priv; + struct mp_archive_volume *vol = priv; switch (whence) { case SEEK_SET: break; case SEEK_CUR: - offset += mpa->src->pos; + offset += vol->src->pos; break; case SEEK_END: ; - int64_t size = stream_get_size(mpa->src); + int64_t size = stream_get_size(vol->src); if (size < 0) return -1; offset += size; @@ -50,15 +58,53 @@ static int64_t seek_cb(struct archive *arch, void *priv, default: return -1; } - return stream_seek(mpa->src, offset) ? offset : -1; + return stream_seek(vol->src, offset) ? offset : -1; } static int64_t skip_cb(struct archive *arch, void *priv, int64_t request) { - struct mp_archive *mpa = priv; - int64_t old = stream_tell(mpa->src); - stream_skip(mpa->src, request); - return stream_tell(mpa->src) - old; + struct mp_archive_volume *vol = priv; + int64_t old = stream_tell(vol->src); + stream_skip(vol->src, request); + return stream_tell(vol->src) - old; +} + +static int open_cb(struct archive *arch, void *priv) +{ + struct mp_archive_volume *vol = priv; + if (!vol->src) { + vol->src = stream_create(vol->url, STREAM_READ, + vol->mpa->primary_src->cancel, + vol->mpa->primary_src->global); + return vol->src ? ARCHIVE_OK : ARCHIVE_FATAL; + } + + // just rewind the primary stream + return stream_seek(vol->src, 0) ? ARCHIVE_OK : ARCHIVE_FATAL; +} + +static void volume_close(struct mp_archive_volume *vol) +{ + // don't close the primary stream + if (vol->src && vol->src != vol->mpa->primary_src) { + free_stream(vol->src); + vol->src = NULL; + } +} + +static int close_cb(struct archive *arch, void *priv) +{ + struct mp_archive_volume *vol = priv; + volume_close(vol); + talloc_free(vol); + return ARCHIVE_OK; +} + +static int switch_cb(struct archive *arch, void *oldpriv, void *newpriv) +{ + struct mp_archive_volume *oldvol = oldpriv; + volume_close(oldvol); + return open_cb(arch, newpriv); } void mp_archive_free(struct mp_archive *mpa) @@ -70,16 +116,108 @@ void mp_archive_free(struct mp_archive *mpa) talloc_free(mpa); } +static char *standard_volume_url(void *ctx, const char *format, + struct bstr base, int index) +{ + return talloc_asprintf(ctx, format, BSTR_P(base), index); +} + +static char *old_rar_volume_url(void *ctx, const char *format, + struct bstr base, int index) +{ + return talloc_asprintf(ctx, format, BSTR_P(base), + 'r' + index / 100, index % 100); +} + +struct file_pattern { + const char *match; + const char *format; + char *(*volume_url)(void *ctx, const char *format, + struct bstr base, int index); + int start; + int stop; +}; + +static const struct file_pattern patterns[] = { + { ".part1.rar", "%.*s.part%.1d.rar", standard_volume_url, 2, 9 }, + { ".part01.rar", "%.*s.part%.2d.rar", standard_volume_url, 2, 99 }, + { ".part001.rar", "%.*s.part%.3d.rar", standard_volume_url, 2, 999 }, + { ".part0001.rar", "%.*s.part%.4d.rar", standard_volume_url, 2, 9999 }, + { ".rar", "%.*s.%c%.2d", old_rar_volume_url, 0, 9999 }, + { ".001", "%.*s.%.3d", standard_volume_url, 2, 9999 }, + { NULL, NULL, NULL, 0, 0 }, +}; + +static char **find_volumes(struct stream *primary_stream) +{ + char **res = talloc_new(NULL); + int num = 0; + struct bstr primary_url = bstr0(primary_stream->url); + + const struct file_pattern *pattern = patterns; + while (pattern->match) { + if (bstr_endswith0(primary_url, pattern->match)) + break; + pattern++; + } + + if (!pattern->match) + goto done; + + struct bstr base = bstr_splice(primary_url, 0, -strlen(pattern->match)); + for (int i = pattern->start; i <= pattern->stop; i++) { + char* url = pattern->volume_url(res, pattern->format, base, i); + struct stream *s = stream_create(url, STREAM_READ | STREAM_SAFE_ONLY, + primary_stream->cancel, + primary_stream->global); + if (!s) { + talloc_free(url); + goto done; + } + free_stream(s); + MP_TARRAY_APPEND(res, res, num, url); + } + +done: + MP_TARRAY_APPEND(res, res, num, NULL); + return res; +} + + +static bool add_volume(struct mp_log *log, struct mp_archive *mpa, + struct stream *src, const char* url) +{ + struct mp_archive_volume *vol = talloc_zero(mpa, struct mp_archive_volume); + mp_verbose(log, "Adding volume %s\n", url); + vol->mpa = mpa; + vol->src = src; + vol->url = talloc_strdup(vol, url); + return archive_read_append_callback_data(mpa->arch, vol) == ARCHIVE_OK; +} + struct mp_archive *mp_archive_new(struct mp_log *log, struct stream *src, int flags) { struct mp_archive *mpa = talloc_zero(NULL, struct mp_archive); - mpa->src = src; - stream_seek(mpa->src, 0); mpa->arch = archive_read_new(); + mpa->primary_src = src; if (!mpa->arch) goto err; + // first volume is the primary streame + if (!add_volume(log ,mpa, src, src->url)) + goto err; + + // try to open other volumes + char** volumes = find_volumes(src); + for (int i = 0; volumes[i]; i++) { + if (!add_volume(log, mpa, NULL, volumes[i])) { + talloc_free(volumes); + goto err; + } + } + talloc_free(volumes); + archive_read_support_format_7zip(mpa->arch); archive_read_support_format_iso9660(mpa->arch); archive_read_support_format_rar(mpa->arch); @@ -92,10 +230,12 @@ struct mp_archive *mp_archive_new(struct mp_log *log, struct stream *src, archive_read_support_format_tar(mpa->arch); } - archive_read_set_callback_data(mpa->arch, mpa); archive_read_set_read_callback(mpa->arch, read_cb); archive_read_set_skip_callback(mpa->arch, skip_cb); - if (mpa->src->seekable) + archive_read_set_switch_callback(mpa->arch, switch_cb); + archive_read_set_open_callback(mpa->arch, open_cb); + archive_read_set_close_callback(mpa->arch, close_cb); + if (mpa->primary_src->seekable) archive_read_set_seek_callback(mpa->arch, seek_cb); if (archive_read_open1(mpa->arch) < ARCHIVE_OK) goto err; |