summaryrefslogtreecommitdiffstats
path: root/stream
diff options
context:
space:
mode:
authorKevin Mitchell <kevmitch@gmail.com>2015-11-09 04:51:29 -0800
committerKevin Mitchell <kevmitch@gmail.com>2015-11-09 22:41:19 -0800
commit4efadb280892afb7c051292e3090737420546099 (patch)
tree72e52d0bea2fe7b06135ddb3dbab4e98bfea19a4 /stream
parentcf5b117553307fadad380d6b44dab0599683a077 (diff)
downloadmpv-4efadb280892afb7c051292e3090737420546099.tar.bz2
mpv-4efadb280892afb7c051292e3090737420546099.tar.xz
stream_libarchive: add multivolume support
This commit introduces logic to read other volumes from the same source as the primary archive. Both .rar formats as well as 7z are supported for now. It also changes the libarchive callback structure to be per-volume consistent with the libarchive intenal client data array constructed with archive_read_append_callback_data. Added open, close and switch callbacks. Only the latter is strictly required to make sure that the streams always start at position 0, but leaving all volumes open can eat a lot of memory for archives with many parts.
Diffstat (limited to 'stream')
-rw-r--r--stream/stream_libarchive.c170
-rw-r--r--stream/stream_libarchive.h2
2 files changed, 156 insertions, 16 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;
diff --git a/stream/stream_libarchive.h b/stream/stream_libarchive.h
index f69faade72..ebded5b5ba 100644
--- a/stream/stream_libarchive.h
+++ b/stream/stream_libarchive.h
@@ -2,7 +2,7 @@ struct mp_log;
struct mp_archive {
struct archive *arch;
- struct stream *src;
+ struct stream *primary_src;
char buffer[4096];
};