blob: bafa29b26031edb5ccd638cf1ba2a4ffc79a616e [file] [edit]
/*
* GPAC Multimedia Framework
*
* Authors: Jean Le Feuvre
* Copyright (c) Telecom ParisTech 2005-2026
* All rights reserved
*
* This file is part of GPAC / downloader sub-project
*
* GPAC is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* GPAC is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <gpac/tools.h>
#if defined(GPAC_CONFIG_EMSCRIPTEN)
#include <gpac/download.h>
#include <gpac/list.h>
#include <gpac/thread.h>
#include <gpac/filters.h>
struct __gf_download_manager
{
GF_FilterSession *fsess;
GF_List *all_sessions;
GF_Mutex *mx;
};
GF_DownloadManager *gf_dm_new(GF_DownloadFilterSession *fsess)
{
GF_DownloadManager *tmp;
GF_SAFEALLOC(tmp, GF_DownloadManager);
if (!tmp) return NULL;
tmp->fsess = fsess;
tmp->all_sessions = gf_list_new();
tmp->mx = gf_mx_new("Downloader");
return tmp;
}
void gf_dm_del(GF_DownloadManager *dm)
{
if (!dm) return;
gf_list_del(dm->all_sessions);
gf_mx_del(dm->mx);
gf_free(dm);
}
u32 gf_dm_get_global_rate(GF_DownloadManager *dm)
{
return 0;
}
void gf_dm_set_data_rate(GF_DownloadManager *dm, u32 rate_in_bits_per_sec)
{
}
typedef struct __cache_blob
{
GF_Blob blob;
char *url, *cache_name;
char *mime;
u64 start_range, end_range;
Bool persistent;
} GF_CacheBlob;
struct __gf_download_session
{
GF_DownloadManager *dm;
gf_dm_user_io user_io;
void *usr_cbk;
char method[100];
char *req_url;
char *rsp_url;
/*request headers*/
char **req_hdrs;
u32 nb_req_hdrs;
u32 req_body_size;
char *req_body;
u64 start_range, end_range;
/*response headers*/
char **rsp_hdrs;
u32 nb_rsp_hdrs;
char *mime; //pointer to content-type header or blob
u32 state, bps;
u64 start_time, start_time_utc;
u64 total_size, bytes_done;
GF_Err last_error;
GF_NetIOStatus netio_status;
u32 dl_flags;
Bool reuse_cache;
u32 ftask;
GF_List *cached_blobs;
GF_CacheBlob *active_cache;
Bool destroy, in_callback;
};
EM_JS(int, dm_fetch_cancel, (int sess), {
let fetcher = libgpac._get_fetcher(sess);
if (!fetcher) return -1;
fetcher._controller.abort();
libgpac._del_fetcher(fetcher);
return 0;
});
static void clear_headers(char ***_hdrs, u32 *nb_hdrs)
{
char **hdrs = *_hdrs;
if (hdrs) {
u32 i=0;
for (i=0; i<*nb_hdrs; i++) {
if (hdrs[i]) gf_free(hdrs[i]);
}
gf_free(hdrs);
*_hdrs = NULL;
*nb_hdrs = 0;
}
}
void cache_blob_del(GF_CacheBlob *b)
{
if (!b) return;
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[Downloader] Removing cache entry of %s (%s - range "LLU"-"LLU")\n", b->url, b->cache_name, b->start_range, b->end_range));
gf_blob_unregister(&b->blob);
if (b->blob.data) gf_free(b->blob.data);
if (b->url) gf_free(b->url);
if (b->cache_name) gf_free(b->cache_name);
if (b->mime) gf_free(b->mime);
gf_free(b);
}
static void gf_dm_sess_reset(GF_DownloadSession *sess)
{
if (!sess) return;
clear_headers(&sess->req_hdrs, &sess->nb_req_hdrs);
clear_headers(&sess->rsp_hdrs, &sess->nb_rsp_hdrs);
if (sess->req_body) gf_free(sess->req_body);
sess->req_body = NULL;
if (sess->state==1) {
dm_fetch_cancel(EM_CAST_PTR sess);
}
if (sess->req_url) gf_free(sess->req_url);
sess->req_url = NULL;
if (sess->rsp_url) gf_free(sess->rsp_url);
sess->rsp_url = NULL;
sess->mime = NULL;
sess->state = 0;
sess->total_size = 0;
sess->bytes_done = 0;
sess->start_time = 0;
sess->start_range = 0;
sess->end_range = 0;
sess->bps = 0;
sess->last_error = GF_OK;
sess->reuse_cache = GF_FALSE;
sess->netio_status = GF_NETIO_SETUP;
}
EM_JS(int, fs_fetch_setup, (), {
if ((typeof libgpac.gpac_fetch == 'boolean') && !libgpac.gpac_fetch) return 2;
if (typeof libgpac._fetcher_set_header == 'function') return 1;
try {
libgpac._fetchers = [];
libgpac._get_fetcher = (sess) => {
for (let i=0; i<libgpac._fetchers.length; i++) {
if (libgpac._fetchers[i].sess==sess) return libgpac._fetchers[i];
}
return null;
};
libgpac._del_fetcher = (fetcher) => {
let i = libgpac._fetchers.indexOf(fetcher);
if (i>=0) libgpac._fetchers.splice(i, 1);
};
libgpac._fetcher_set_header = cwrap('gf_dm_sess_push_header', null, ['number', 'string', 'string']);
libgpac._fetcher_set_reply = cwrap('gf_dm_sess_async_reply', null, ['number', 'number', 'string']);
} catch (e) {
return 0;
}
return 1;
});
static GF_Err gf_dm_setup_cache(GF_DownloadSession *sess)
{
if (sess->active_cache) {
if (!sess->cached_blobs) {
cache_blob_del(sess->active_cache);
}
else if (sess->active_cache->blob.flags & GF_BLOB_CORRUPTED) {
gf_list_del_item(sess->cached_blobs, sess->active_cache);
cache_blob_del(sess->active_cache);
}
//remove if not persistent
else if (!sess->active_cache->persistent) {
gf_list_del_item(sess->cached_blobs, sess->active_cache);
cache_blob_del(sess->active_cache);
}
sess->active_cache = NULL;
}
//look in session for cache
if (sess->cached_blobs) {
u32 i, count = gf_list_count(sess->cached_blobs);
for (i=0; i<count; i++) {
GF_CacheBlob *cb = gf_list_get(sess->cached_blobs, i);
if (strcmp(cb->url, sess->req_url)) continue;
if (cb->start_range != sess->start_range) continue;
if (cb->end_range != sess->end_range) continue;
if (!cb->blob.size) continue;
sess->active_cache = cb;
sess->reuse_cache = GF_TRUE;
sess->total_size = cb->blob.size;
sess->bytes_done = 0;
sess->netio_status = GF_NETIO_DATA_EXCHANGE;
sess->last_error = GF_OK;
sess->mime = cb->mime;
sess->dl_flags &= ~GF_NETIO_SESSION_KEEP_FIRST_CACHE;
return GF_OK;
}
}
if (!(sess->dl_flags & GF_NETIO_SESSION_MEMORY_CACHE)) {
return GF_OK;
}
GF_SAFEALLOC(sess->active_cache, GF_CacheBlob);
if (!sess->active_cache) return GF_OUT_OF_MEM;
if (!sess->cached_blobs) sess->cached_blobs = gf_list_new();
gf_list_add(sess->cached_blobs, sess->active_cache);
sess->active_cache->cache_name = gf_blob_register(&sess->active_cache->blob);
sess->active_cache->blob.flags = GF_BLOB_IN_TRANSFER;
sess->active_cache->url = gf_strdup(sess->req_url);
sess->active_cache->start_range = sess->start_range;
sess->active_cache->end_range = sess->end_range;
sess->reuse_cache = GF_FALSE;
//only files marked with GF_NETIO_SESSION_KEEP_FIRST_CACHE are kept in our local cache
//all other cache settings are ignored, we rely on browser cache for this
if (sess->dl_flags & GF_NETIO_SESSION_KEEP_FIRST_CACHE) {
sess->active_cache->persistent = GF_TRUE;
sess->dl_flags &= ~GF_NETIO_SESSION_KEEP_FIRST_CACHE;
} else {
sess->active_cache->persistent = GF_FALSE;
}
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[Downloader] Setting up memory cache for %s - persistent %d - name %s\n", sess->req_url, sess->active_cache->persistent, sess->active_cache->cache_name));
return GF_OK;
}
GF_EXPORT
GF_DownloadSession *gf_dm_sess_new(GF_DownloadManager *dm, const char *url, u32 dl_flags,
gf_dm_user_io user_io,
void *usr_cbk,
GF_Err *e)
{
GF_NETIO_Parameter par;
GF_DownloadSession *sess;
int fetch_init = fs_fetch_setup();
if (fetch_init==2) {
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[Downloader] fecth() disabled\n"));
if (e) *e = GF_NOT_SUPPORTED;
return NULL;
} else if (fetch_init==0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[Downloader] Failed to initialize fetch\n"));
if (e) *e = GF_SERVICE_ERROR;
return NULL;
}
GF_SAFEALLOC(sess, GF_DownloadSession);
if (!sess) {
if (e) *e = GF_OUT_OF_MEM;
return NULL;
}
sess->dm = dm;
sess->req_url = gf_strdup(url);
if (!sess->req_url) {
gf_free(sess);
if (e) *e = GF_OUT_OF_MEM;
return NULL;
}
sess->user_io = user_io;
sess->usr_cbk = usr_cbk;
gf_strcpy(sess->method, "GET");
sess->dl_flags = dl_flags;
if (dl_flags & GF_NETIO_SESSION_MEMORY_CACHE) {
sess->cached_blobs = gf_list_new();
}
if (user_io) {
memset(&par, 0, sizeof(GF_NETIO_Parameter));
par.msg_type = GF_NETIO_GET_METHOD;
sess->user_io(sess->usr_cbk, &par);
if (par.name) gf_strcpy(sess->method, par.name);
sess->nb_req_hdrs=0;
while (1) {
par.msg_type = GF_NETIO_GET_HEADER;
par.value = NULL;
sess->user_io(sess->usr_cbk, &par);
if (!par.value) break;
sess->req_hdrs = gf_realloc(sess->req_hdrs, sizeof(char*) * (sess->nb_req_hdrs+2));
if (!sess->req_hdrs) {
if (e) *e = GF_OUT_OF_MEM;
gf_free(sess);
return NULL;
}
sess->req_hdrs[sess->nb_req_hdrs] = gf_strdup(par.name);
sess->req_hdrs[sess->nb_req_hdrs+1] = gf_strdup(par.value);
sess->nb_req_hdrs+=2;
}
memset(&par, 0, sizeof(GF_NETIO_Parameter));
par.msg_type = GF_NETIO_GET_CONTENT;
sess->user_io(sess->usr_cbk, &par);
sess->req_body_size = par.size;
if (par.size && par.data) {
sess->req_body = gf_malloc(sizeof(u8)*par.size);
if (!sess->req_body) {
gf_dm_sess_reset(sess);
gf_list_del(sess->cached_blobs);
if (e) *e = GF_OUT_OF_MEM;
gf_free(sess);
return NULL;
}
memcpy(sess->req_body, par.data, par.size);
}
}
if (e) *e = GF_OK;
*e = gf_dm_setup_cache(sess);
if (*e) {
gf_dm_sess_del(sess);
return NULL;
}
if (dm) {
gf_mx_p(dm->mx);
gf_list_add(dm->all_sessions, sess);
gf_mx_v(dm->mx);
}
sess->user_io = user_io;
sess->usr_cbk = usr_cbk;
return sess;
}
void gf_dm_sess_abort(GF_DownloadSession * sess)
{
if (sess->state==1) {
dm_fetch_cancel(EM_CAST_PTR sess);
sess->state = 0;
sess->netio_status = GF_NETIO_DISCONNECTED;
sess->last_error = GF_IP_CONNECTION_CLOSED;
if (sess->active_cache && !sess->reuse_cache && (sess->active_cache->blob.flags & GF_BLOB_IN_TRANSFER)) {
sess->active_cache->blob.flags = GF_BLOB_CORRUPTED;
}
}
clear_headers(&sess->rsp_hdrs, &sess->nb_rsp_hdrs);
if (sess->ftask) sess->ftask = 2;
}
void gf_dm_sess_del(GF_DownloadSession *sess)
{
if (sess->in_callback) {
sess->destroy = GF_TRUE;
return;
}
gf_dm_sess_reset(sess);
while (gf_list_count(sess->cached_blobs)) {
cache_blob_del( gf_list_pop_back(sess->cached_blobs) );
}
gf_list_del(sess->cached_blobs);
if (sess->dm) {
gf_mx_p(sess->dm->mx);
gf_list_del_item(sess->dm->all_sessions, sess);
gf_mx_v(sess->dm->mx);
}
gf_free(sess);
}
const char *gf_dm_sess_get_cache_name(GF_DownloadSession *sess)
{
if (!sess || !sess->active_cache) return NULL;
return sess->active_cache->cache_name;
}
void gf_dm_sess_force_memory_mode(GF_DownloadSession *sess, u32 force_keep)
{
if (!sess) return;
sess->dl_flags |= GF_NETIO_SESSION_MEMORY_CACHE;
if (force_keep==1) {
//cache is handled by the browser if any, or forced by fetch headers provied by users
//sess->dl_flags |= GF_NETIO_SESSION_KEEP_CACHE;
} else if (force_keep==2) {
sess->dl_flags |= GF_NETIO_SESSION_KEEP_FIRST_CACHE;
}
}
GF_Err gf_dm_sess_setup_from_url(GF_DownloadSession *sess, const char *url, Bool allow_direct_reuse)
{
gf_dm_sess_reset(sess);
sess->req_url = gf_strdup(url);
if (!sess->req_url) {
sess->state=2;
return GF_OUT_OF_MEM;
}
return gf_dm_setup_cache(sess);
}
GF_Err gf_dm_sess_set_range(GF_DownloadSession *sess, u64 start_range, u64 end_range, Bool discontinue_cache)
{
if (!sess) return GF_BAD_PARAM;
if (sess->state==1) return GF_BAD_PARAM;
sess->start_range = start_range;
sess->end_range = end_range;
Bool cache_reset = GF_FALSE;
if ((sess->state!=2) || discontinue_cache) {
cache_reset = GF_TRUE;
}
else if (sess->active_cache) {
if (sess->active_cache->start_range + sess->active_cache->end_range + 1 == start_range) {
sess->active_cache->end_range = end_range;
sess->active_cache->blob.flags |= GF_BLOB_IN_TRANSFER;
} else {
cache_reset = GF_TRUE;
}
}
//set range called before starting session, just adjust cache
if (sess->active_cache && cache_reset && !sess->state) {
sess->active_cache->start_range = sess->start_range;
sess->active_cache->end_range = sess->end_range;
cache_reset=GF_FALSE;
}
if (cache_reset) {
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[Downloader] Cache discontinuity, reset\n", sess->req_url, sess->active_cache->persistent, sess->active_cache->cache_name));
GF_Err e = gf_dm_setup_cache(sess);
if (e) return e;
sess->last_error = GF_OK;
}
sess->netio_status = GF_NETIO_CONNECTED;
sess->state = 0;
return GF_OK;
}
EM_JS(int, dm_fetch_init, (int sess, int _url, int _method, int _headers, int nb_headers, int req_body, int req_body_size), {
let url = _url ? UTF8ToString(_url) : null;
let ret = 0; // libgpac.OK
let fetcher = libgpac._get_fetcher(sess);
if (!fetcher) {
fetcher = {};
fetcher.sess = sess;
libgpac._fetchers.push(fetcher);
}
fetcher._controller = new AbortController();
let options = {
signal: fetcher._controller.signal,
method: _method ? UTF8ToString(_method) : "GET",
mode: libgpac.gpac_fetch_mode || 'cors',
};
let mime_type = 'application/octet-stream';
options.headers = {};
if (_headers) {
for (let i=0; i<nb_headers; i+=2) {
let _s = getValue(_headers+i*4, 'i32');
let h_name = _s ? UTF8ToString(_s).toLowerCase() : "";
_s = getValue(_headers+(i+1)*4, 'i32');
let h_val = _s ? UTF8ToString(_s) : "";
if (h_name.length && h_val.length) {
options.headers[h_name] = h_val;
if (h_name=='content-type')
mime_type = h_val;
}
}
}
if (typeof libgpac.gpac_extra_headers == 'object') {
for (const hdr in libgpac.gpac_extra_headers) {
options.headers[hdr] = object[hdr];
}
}
if (req_body) {
let body_ab = new Uint8Array(HEAPU8.buffer, req_body, req_body_size);
options.body = new Blob(body_ab, {type: mime_type} );
}
fetcher._state = 0;
fetch(url, options).then( (response) => {
if (response.ok) {
fetcher._state = 1;
fetcher._bytes = 0;
fetcher._reader = response.body.getReader();
let final_url = null;
if (response.redirected) final_url = response.url;
libgpac._fetcher_set_reply(fetcher.sess, response.status, final_url);
response.headers.forEach((value, key) => {
libgpac._fetcher_set_header(fetcher.sess, key, value);
});
libgpac._fetcher_set_header(fetcher.sess, 0, 0);
} else {
libgpac._fetcher_set_reply(fetcher.sess, response.status, null);
fetcher._state = 3;
}
})
.catch( (e) => {
if (e.name !== 'AbortError') printErr('fetcher exception ' + e);
ret = -14; // libgpac.REMOTE_SERVICE_ERROR
libgpac._del_fetcher(fetcher);
});
return ret;
});
EM_JS(int, dm_fetch_data, (int sess, int buffer, int buffer_size, int read_size), {
let f = libgpac._get_fetcher(sess);
if (!f) return -1;
if (f._state==0) return -44;
if (f._state==3) return -12;
if (f._state==4) return 1;
if (f._state == 1) {
f._state = 0;
f._reader.read().then( (block) => {
if (block.done) {
f._state = 4;
} else {
f._ab = block.value;
f._bytes += f._ab.byteLength;
f._block_pos = 0;
f._state = 2;
}
})
.catch( e => {
f._state = 0;
});
if (f._state==0) return -44;
}
let avail = f._ab.byteLength - f._block_pos;
if (avail<buffer_size) buffer_size = avail;
//copy array buffer
let src = f._ab.subarray(f._block_pos, f._block_pos+buffer_size);
let dst = new Uint8Array(HEAPU8.buffer, buffer, buffer_size);
dst.set(src);
setValue(read_size, buffer_size, 'i32');
f._block_pos += buffer_size;
if (f._ab.byteLength == f._block_pos) {
f._ab = null;
f._state = 1;
}
return 0;
});
GF_Err gf_dm_sess_fetch_data(GF_DownloadSession *sess, char *buffer, u32 buffer_size, u32 *read_size)
{
if (sess->reuse_cache) {
u32 remain = sess->active_cache->blob.size - sess->bytes_done;
if (!remain) {
*read_size = 0;
sess->last_error = GF_EOS;
sess->netio_status = GF_NETIO_DATA_TRANSFERED;
return GF_EOS;
}
if (remain < buffer_size) buffer_size = remain;
memcpy(buffer, sess->active_cache->blob.data + sess->bytes_done, buffer_size);
*read_size = buffer_size;
sess->bytes_done += buffer_size;
return GF_OK;
}
if (sess->state == 0) {
if (sess->start_range || sess->end_range) {
char szHdr[100];
if (sess->end_range)
sprintf(szHdr, "bytes="LLU"-"LLU, sess->start_range, sess->end_range);
else
sprintf(szHdr, "bytes="LLU"-", sess->start_range);
sess->req_hdrs = gf_realloc(sess->req_hdrs, sizeof(char*) * (sess->nb_req_hdrs+2));
if (!sess->req_hdrs) {
sess->state = 2;
return sess->last_error = GF_OUT_OF_MEM;
}
sess->req_hdrs[sess->nb_req_hdrs] = gf_strdup("Range");
sess->req_hdrs[sess->nb_req_hdrs+1] = gf_strdup(szHdr);
sess->nb_req_hdrs+=2;
}
sess->start_time_utc = gf_net_get_utc();
sess->start_time = gf_sys_clock();
sess->netio_status = GF_NETIO_CONNECTED;
if (sess->start_range || sess->end_range) {
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[Downloader] Starting download of %s (%s - range "LLU"-"LLU")\n", sess->req_url, sess->active_cache ? sess->active_cache->cache_name : "no cache", sess->start_range, sess->end_range));
} else {
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[Downloader] Starting download of %s (%s)\n", sess->req_url, sess->active_cache ? sess->active_cache->cache_name : "no cache"));
}
GF_Err fetch_e = dm_fetch_init(
EM_CAST_PTR sess,
EM_CAST_PTR sess->req_url,
EM_CAST_PTR sess->method,
EM_CAST_PTR sess->req_hdrs,
sess->nb_req_hdrs,
EM_CAST_PTR sess->req_body,
sess->req_body_size
);
if (fetch_e) {
gf_dm_sess_reset(sess);
sess->state = 2;
sess->netio_status = GF_NETIO_DISCONNECTED;
return sess->last_error = fetch_e;
}
sess->state = 1;
sess->netio_status = GF_NETIO_WAIT_FOR_REPLY;
}
if (sess->state==2) return sess->last_error;
*read_size = 0;
GF_Err fetch_err = dm_fetch_data(
EM_CAST_PTR sess,
EM_CAST_PTR buffer,
buffer_size,
EM_CAST_PTR read_size
);
if (*read_size) {
u64 ellapsed = gf_sys_clock() - sess->start_time;
sess->bytes_done += *read_size;
if (ellapsed)
sess->bps = (u32) (sess->bytes_done * 1000 / ellapsed);
sess->netio_status = GF_NETIO_DATA_EXCHANGE;
if (sess->active_cache) {
u32 nb_bytes= *read_size;
sess->active_cache->blob.data = gf_realloc(sess->active_cache->blob.data, sess->active_cache->blob.size + nb_bytes);
if (!sess->active_cache->blob.data) {
sess->active_cache->blob.flags |= GF_BLOB_CORRUPTED;
} else {
memcpy(sess->active_cache->blob.data + sess->active_cache->blob.size, buffer, nb_bytes);
sess->active_cache->blob.size += nb_bytes;
}
}
if (sess->total_size == sess->bytes_done) fetch_err = GF_EOS;
} else if (!fetch_err) {
return GF_IP_NETWORK_EMPTY;
} else if (fetch_err!=GF_IP_NETWORK_EMPTY) {
sess->state=2;
sess->last_error = fetch_err;
sess->netio_status = GF_NETIO_STATE_ERROR;
if (sess->active_cache)
sess->active_cache->blob.flags = GF_BLOB_CORRUPTED;
}
if (fetch_err==GF_EOS) {
sess->total_size = sess->bytes_done;
sess->state=2;
sess->last_error = GF_EOS;
sess->netio_status = GF_NETIO_DATA_TRANSFERED;
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[Downloader] Done downloading %s - rate %d bps\n", sess->req_url, sess->bps));
if (sess->active_cache)
sess->active_cache->blob.flags &= ~GF_BLOB_IN_TRANSFER;
}
return fetch_err;
}
GF_Err gf_dm_sess_get_stats(GF_DownloadSession * sess, const char **server, const char **path, u64 *total_size, u64 *bytes_done, u32 *bytes_per_sec, GF_NetIOStatus *net_status)
{
if (!sess) return GF_OUT_OF_MEM;
if (total_size) *total_size = sess->total_size;
if (bytes_done) *bytes_done = sess->bytes_done;
if (bytes_per_sec) *bytes_per_sec = sess->bps;
if (net_status) *net_status = sess->netio_status;
return sess->last_error;
}
const char *gf_dm_sess_mime_type(GF_DownloadSession * sess)
{
return sess->mime;
}
GF_Err gf_dm_sess_enum_headers(GF_DownloadSession *sess, u32 *idx, const char **hdr_name, const char **hdr_val)
{
if( !sess || !idx || !hdr_name || !hdr_val)
return GF_BAD_PARAM;
if (*idx >= sess->nb_rsp_hdrs/2) return GF_EOS;
u32 i = *idx * 2;
(*hdr_name) = sess->rsp_hdrs[i];
(*hdr_val) = sess->rsp_hdrs[i+1];
(*idx) = (*idx) + 1;
return GF_OK;
}
void gf_dm_delete_cached_file_entry_session(const GF_DownloadSession * sess, const char * url, Bool force)
{
//unused we automatically purge cache when setting up session
if (!sess || !force) return;
if (sess->state==1) return;
if (sess->active_cache) {
gf_list_del_item(sess->cached_blobs, sess->active_cache);
cache_blob_del(sess->active_cache);
((GF_DownloadSession *)sess)->active_cache = NULL;
}
}
GF_Err gf_dm_sess_process_headers(GF_DownloadSession *sess)
{
//no control of this for fetch
return GF_OK;
}
GF_EXPORT
void gf_dm_sess_push_header(GF_DownloadSession *sess, const char *hdr, const char *value)
{
if (!sess) return;
if (!hdr || !value) return;
if (!stricmp(hdr, "Content-Length")) {
sscanf(value, LLU, &sess->total_size);
}
sess->rsp_hdrs = gf_realloc(sess->rsp_hdrs, sizeof(char*) * (sess->nb_rsp_hdrs+2));
if (!sess->rsp_hdrs) return;
sess->rsp_hdrs[sess->nb_rsp_hdrs] = gf_strdup(hdr);
sess->rsp_hdrs[sess->nb_rsp_hdrs+1] = gf_strdup(value);
if (!stricmp(hdr, "Content-Type")) {
sess->mime = sess->rsp_hdrs[sess->nb_rsp_hdrs+1];
//keep a copy of mime
if (sess->active_cache && sess->active_cache->persistent)
sess->active_cache->mime = gf_strdup(sess->mime);
}
sess->nb_rsp_hdrs+=2;
}
GF_EXPORT
void gf_dm_sess_async_reply(GF_DownloadSession *sess, int rsp_code, const char *final_url)
{
if (!sess) return;
GF_Err e;
switch (rsp_code) {
case 404: e = GF_URL_ERROR; break;
case 400: e = GF_SERVICE_ERROR; break;
case 401: e = GF_AUTHENTICATION_FAILURE; break;
case 405: e = GF_AUTHENTICATION_FAILURE; break;
case 416: e = GF_SERVICE_ERROR; break; //range error
default:
if (rsp_code>=500) e = GF_REMOTE_SERVICE_ERROR;
else if (rsp_code>=400) e = GF_SERVICE_ERROR;
else e = GF_OK;
break;
}
sess->last_error = e;
if (final_url) {
if (sess->rsp_url) gf_free(sess->rsp_url);
sess->rsp_url = final_url ? gf_strdup(final_url) : NULL;
}
}
GF_Err gf_dm_sess_fetch_once(GF_DownloadSession *sess)
{
GF_NETIO_Parameter par;
char buffer[10000];
u32 read = 0;
GF_Err e = gf_dm_sess_fetch_data(sess, buffer, 10000, &read);
if (e==GF_IP_NETWORK_EMPTY) {
} else if ((e==GF_OK) || (e==GF_EOS)) {
if (sess->user_io && read) {
memset(&par, 0, sizeof(GF_NETIO_Parameter));
par.sess = sess;
par.error = sess->last_error;
if (e) {
par.msg_type = GF_NETIO_DATA_TRANSFERED;
} else {
par.msg_type = GF_NETIO_DATA_EXCHANGE;
par.data = buffer;
par.size = read;
}
sess->in_callback = GF_FALSE;
sess->user_io(sess->usr_cbk, &par);
sess->in_callback = GF_FALSE;
}
} else {
if (sess->user_io) {
memset(&par, 0, sizeof(GF_NETIO_Parameter));
par.sess = sess;
par.msg_type = sess->netio_status;
par.error = sess->last_error;
sess->in_callback = GF_TRUE;
sess->user_io(sess->usr_cbk, &par);
sess->in_callback = GF_FALSE;
}
}
return e;
}
Bool gf_dm_session_task(GF_FilterSession *fsess, void *callback, u32 *reschedule_ms)
{
Bool ret = GF_TRUE;
GF_DownloadSession *sess = callback;
if (!sess) return GF_FALSE;
gf_assert(sess->ftask);
if (sess->ftask==2) {
sess->ftask = 0;
return GF_FALSE;
}
GF_Err e = gf_dm_sess_fetch_once(sess);
if (e==GF_EOS) ret = GF_FALSE;
else if (e && (e!=GF_IP_NETWORK_EMPTY)) ret = GF_FALSE;
if (ret) {
*reschedule_ms = 1;
return GF_TRUE;
}
sess->ftask = 0;
if (sess->destroy) {
sess->destroy = GF_FALSE;
gf_dm_sess_del(sess);
}
return GF_FALSE;
}
GF_Err gf_dm_sess_process(GF_DownloadSession *sess)
{
if (sess->netio_status == GF_NETIO_DATA_TRANSFERED) {
return GF_OK;
}
if ((sess->dl_flags & GF_NETIO_SESSION_NOT_THREADED) || !sess->dm->fsess) {
return gf_dm_sess_fetch_once(sess);
}
//ignore if task already allocated
if (sess->ftask) return GF_OK;
sess->ftask = GF_TRUE;
return gf_fs_post_user_task(sess->dm->fsess, gf_dm_session_task, sess, "download");
}
const char *gf_dm_sess_get_resource_name(GF_DownloadSession *sess)
{
if (!sess) return NULL;
return sess->rsp_url ? sess->rsp_url : sess->req_url;
}
const char *gf_dm_sess_get_header(GF_DownloadSession *sess, const char *name)
{
u32 i;
if (!sess || !name) return NULL;
for (i=0; i<sess->nb_rsp_hdrs; i+=2) {
char *h = sess->rsp_hdrs[i];
if (!stricmp(h, name)) return sess->rsp_hdrs[i+1];
}
return NULL;
}
u64 gf_dm_sess_get_utc_start(GF_DownloadSession * sess)
{
if (!sess) return 0;
return sess->start_time_utc;
}
//only used for dash playing local files
u32 gf_dm_get_data_rate(GF_DownloadManager *dm)
{
return gf_opts_get_int("core", "maxrate");
}
void gf_dm_sess_detach_async(GF_DownloadSession *sess)
{
if (!sess) return;
sess->dl_flags &= ~GF_NETIO_SESSION_NOT_THREADED;
gf_dm_sess_force_memory_mode(sess, 1);
}
void gf_dm_sess_set_netcap_id(GF_DownloadSession *sess, const char *netcap_id)
{
}
void gf_dm_sess_set_max_rate(GF_DownloadSession *sess, u32 max_rate)
{
}
u32 gf_dm_sess_get_max_rate(GF_DownloadSession *sess)
{
return 0;
}
Bool gf_dm_sess_is_regulated(GF_DownloadSession *sess)
{
return GF_FALSE;
}
u32 gf_dm_sess_get_resource_size(GF_DownloadSession * sess)
{
return sess ? sess->total_size : 0;
}
#endif // GPAC_CONFIG_EMSCRIPTEN