blob: 7851f5ef6194171b4e3fc787559f64ddd941e9e6 [file] [edit]
/*
* GPAC Multimedia Framework
*
* Authors: Jean Le Feuvre, Pierre Souchay
* Copyright (c) Telecom ParisTech 2010-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 "downloader.h"
#ifndef GPAC_DISABLE_NETWORK
#include <gpac/network.h>
#include <gpac/download.h>
#include <gpac/token.h>
#include <gpac/thread.h>
#include <gpac/list.h>
#include <gpac/base_coding.h>
#include <gpac/tools.h>
#include <gpac/config_file.h>
#include <stdio.h>
#include <string.h>
#if defined(_BSD_SOURCE) || _XOPEN_SOURCE >= 500
#include <unistd.h>
#endif
static const char * CACHE_SECTION_NAME = "cache";
static const char * CACHE_SECTION_KEY_URL = "url";
static const char * CACHE_SECTION_KEY_RANGE = "range";
static const char * CACHE_SECTION_KEY_ETAG = "ETag";
static const char * CACHE_SECTION_KEY_MIME_TYPE = "Content-Type";
static const char * CACHE_SECTION_KEY_CONTENT_SIZE = "Content-Length";
static const char * CACHE_SECTION_KEY_LAST_MODIFIED = "Last-Modified";
static const char * CACHE_SECTION_KEY_MAXAGE = "MaxAge";
static const char * CACHE_SECTION_KEY_MUST_REVALIDATE = "MustRevalidate";
static const char * CACHE_SECTION_KEY_NOCACHE = "NoCache";
static const char * CACHE_SECTION_KEY_CREATED = "Created";
static const char * CACHE_SECTION_KEY_LAST_HIT = "LastHit";
static const char * CACHE_SECTION_KEY_NUM_HIT = "NumHit";
static const char * CACHE_SECTION_KEY_INWRITE = "InWrite";
static const char * CACHE_SECTION_KEY_TODELETE = "ToDelete";
static const char * CACHE_SECTION_USERS = "users";
enum CacheValid
{
CORRUPTED = 1,
DELETED = 1<<1,
IN_PROGRESS = 1<<2,
};
/**
* This opaque structure handles the data from the cache
*/
struct __DownloadedCacheEntryStruct
{
// URL of the cache (never NULL)
char *url;
// Hash of the cache (never NULL)
char *hash;
// Name of the cache filename, (NULL for mem)
char *cache_filename;
// Name of the config filename, (NULL for mem)
char *cfg_filename;
// Theorical size of cache if any
u32 contentLength;
// Real size of cache
u32 cacheSize;
// The last modification time on the server
char *serverLastModified;
//The last modification time of the cache if any
char *diskLastModified;
// server ETag if any
char *serverETag;
//disk ETag if any
char * diskETag;
// Mime-type
char * mimeType;
// Write pointer for the cache
FILE * writeFilePtr;
// Bytes written during this cache session
u32 written_in_cache;
// Flag indicating whether we have to revalidate
enum CacheValid flags;
//pointer to write session
const GF_DownloadSession * write_session;
//list of read sessions
GF_List * sessions;
//set to true if disk files for this entry shall be removed upon destroy (due to error)
Bool discard_on_delete;
u64 max_age;
Bool must_revalidate;
Bool other_in_use;
//start and end range of the cache
u64 range_start, range_end;
//append to cache
Bool continue_file;
//contentLength before append
u32 previousRangeContentLength;
//set once headers have been processed
Bool headers_done;
// Set to true if file is not stored on disk
Bool memory_stored;
//allocation
u32 mem_allocated;
u8 *mem_storage;
GF_Blob cache_blob;
//for external blob only
GF_Blob *external_blob;
char *forced_headers;
u32 downtime;
//prevent deleting files when deleting entries, typically for init segments
Bool persistent;
};
Bool gf_sys_check_process_id(u32 pid);
Bool gf_cache_entry_persistent(const DownloadedCacheEntry entry)
{
return entry ? entry->persistent : GF_FALSE;
}
void gf_cache_entry_set_persistent(const DownloadedCacheEntry entry)
{
if (entry) entry->persistent = GF_TRUE;
}
static const char * cache_file_prefix = "gpac_cache_";
typedef struct
{
char *name;
u64 created;
u64 last_hit;
u32 nb_hit;
u32 size;
} CacheInfo;
typedef struct
{
GF_List *files;
u64 tot_size;
} CacheGather;
static Bool cache_cleanup_processes(GF_Config *cfg)
{
u32 i, count=gf_cfg_get_key_count(cfg, CACHE_SECTION_USERS);
for (i=0;i<count;i++) {
const char *opt = gf_cfg_get_key_name(cfg, CACHE_SECTION_USERS, i);
if (!opt) break;
u32 procid;
sscanf(opt, "%u", &procid);
if (gf_sys_check_process_id(procid)) continue;
gf_cfg_set_key(cfg, CACHE_SECTION_USERS, opt, NULL);
count--;
i--;
}
return count ? GF_TRUE : GF_FALSE;
}
static Bool gather_cache_files(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info)
{
CacheGather *gci = (CacheGather *)cbck;
GF_Config *file = gf_cfg_new(NULL, item_path);
u32 i, count, size=0;
const char *opt = gf_cfg_get_key(file, CACHE_SECTION_NAME, CACHE_SECTION_KEY_CONTENT_SIZE);
if (opt) sscanf(opt, "%u", &size);
gci->tot_size += size;
if (cache_cleanup_processes(file)) {
gf_cfg_del(file);
return GF_FALSE;
}
CacheInfo *ci;
GF_SAFEALLOC(ci, CacheInfo);
if (ci) ci->name = gf_strdup(item_path);
if (!ci || !ci->name) {
if (ci) gf_free(ci);
gf_cfg_del(file);
return GF_FALSE;
}
ci->size = size;
opt = gf_cfg_get_key(file, CACHE_SECTION_NAME, CACHE_SECTION_KEY_CREATED);
if (opt) sscanf(opt, LLU, &ci->created);
opt = gf_cfg_get_key(file, CACHE_SECTION_NAME, CACHE_SECTION_KEY_LAST_HIT);
if (opt) sscanf(opt, LLU, &ci->last_hit);
opt = gf_cfg_get_key(file, CACHE_SECTION_NAME, CACHE_SECTION_KEY_NUM_HIT);
if (opt) { sscanf(opt, "%u", &ci->nb_hit); if (ci->nb_hit) ci->nb_hit--; }
gf_cfg_del(file);
count = gf_list_count(gci->files);
for (i=0; i<count; i++) {
CacheInfo *a_ci = gf_list_get(gci->files, i);
//sort by nb hits first
if (ci->nb_hit<a_ci->nb_hit) {
gf_list_insert(gci->files, ci, i);
return GF_FALSE;
} else if (ci->nb_hit>a_ci->nb_hit) continue;
//then by last hit
if (ci->last_hit<a_ci->last_hit) {
gf_list_insert(gci->files, ci, i);
return GF_FALSE;
}
else if (ci->last_hit>a_ci->last_hit) continue;
//then by created
if (ci->created<a_ci->created) {
gf_list_insert(gci->files, ci, i);
return GF_FALSE;
}
else if (ci->created>a_ci->created) continue;
//then by size
if (ci->size<a_ci->size) {
gf_list_insert(gci->files, ci, i);
return GF_FALSE;
}
}
gf_list_add(gci->files, ci);
return 0;
}
u64 gf_cache_cleanup(const char * directory, u64 max_size)
{
char szLOCK[GF_MAX_PATH];
snprintf(szLOCK, sizeof(szLOCK), "%s/.lock", directory);
if (!gf_sys_create_lockfile(szLOCK))
return max_size;
CacheGather gci = {0};
gci.files = gf_list_new();
gf_enum_directory(directory, GF_FALSE, gather_cache_files, &gci, "*.txt");
u64 cache_size = gci.tot_size;
while (gf_list_count(gci.files)) {
CacheInfo *ci = gf_list_pop_back(gci.files);
if (cache_size>max_size) {
if (cache_size>ci->size) cache_size -= ci->size;
else cache_size = 0;
gf_file_delete(ci->name);
char *sep = strstr(ci->name, ".txt");
sep[0] = 0;
gf_file_delete(ci->name);
sep[0] = '.';
}
gf_free(ci->name);
gf_free(ci);
}
gf_list_del(gci.files);
gf_file_delete(szLOCK);
return cache_size;
}
void gf_cache_entry_set_delete_files_when_deleted(const DownloadedCacheEntry entry)
{
if (entry && !entry->persistent) {
entry->discard_on_delete = GF_TRUE;
}
}
#define CHECK_ENTRY if (!entry) { GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("[CACHE] entry is null at " __FILE__ ":%d\n", __LINE__)); return GF_BAD_PARAM; }
/*
* Getters functions
*/
const char * gf_cache_get_etag_on_server ( const DownloadedCacheEntry entry )
{
return entry ? entry->serverETag : NULL;
}
const char * gf_cache_get_mime_type ( const DownloadedCacheEntry entry )
{
return entry ? entry->mimeType : NULL;
}
GF_Err gf_cache_set_headers_processed(const DownloadedCacheEntry entry)
{
if (!entry) return GF_BAD_PARAM;
entry->headers_done = GF_TRUE;
return GF_OK;
}
Bool gf_cache_are_headers_processed(const DownloadedCacheEntry entry)
{
if (!entry) return GF_FALSE;
return entry->headers_done;
}
GF_Err gf_cache_set_etag_on_server(const DownloadedCacheEntry entry, const char * eTag ) {
if (!entry)
return GF_BAD_PARAM;
if (entry->serverETag)
gf_free(entry->serverETag);
entry->serverETag = eTag ? gf_strdup(eTag) : NULL;
return GF_OK;
}
GF_Err gf_cache_set_etag_on_disk(const DownloadedCacheEntry entry, const char * eTag ) {
if (!entry)
return GF_BAD_PARAM;
if (entry->diskETag)
gf_free(entry->diskETag);
entry->diskETag = eTag ? gf_strdup(eTag) : NULL;
return GF_OK;
}
GF_Err gf_cache_set_mime_type(const DownloadedCacheEntry entry, const char * mime_type ) {
if (!entry)
return GF_BAD_PARAM;
if (entry->mimeType)
gf_free(entry->mimeType);
entry->mimeType = mime_type? gf_strdup( mime_type) : NULL;
return GF_OK;
}
u64 gf_cache_get_start_range( const DownloadedCacheEntry entry )
{
return entry ? entry->range_start : 0;
}
u64 gf_cache_get_end_range( const DownloadedCacheEntry entry )
{
return entry ? entry->range_end : 0;
}
const char * gf_cache_get_url ( const DownloadedCacheEntry entry )
{
return entry ? entry->url : NULL;
}
const char * gf_cache_get_last_modified_on_server ( const DownloadedCacheEntry entry )
{
return entry ? entry->serverLastModified : NULL;
}
GF_Err gf_cache_set_last_modified_on_server ( const DownloadedCacheEntry entry, const char * newLastModified )
{
if (!entry)
return GF_BAD_PARAM;
if (entry->serverLastModified)
gf_free(entry->serverLastModified);
entry->serverLastModified = newLastModified ? gf_strdup(newLastModified) : NULL;
return GF_OK;
}
GF_Err gf_cache_set_last_modified_on_disk ( const DownloadedCacheEntry entry, const char * newLastModified )
{
if (!entry)
return GF_BAD_PARAM;
if (entry->diskLastModified)
gf_free(entry->diskLastModified);
entry->diskLastModified = newLastModified ? gf_strdup(newLastModified) : NULL;
return GF_OK;
}
static GF_LockStatus cache_entry_lock(const char *lockfile)
{
GF_LockStatus lock_type;
u32 start=gf_sys_clock();
while (1) {
lock_type = gf_sys_create_lockfile(lockfile);
if (lock_type) break;
if (gf_sys_clock()-start>50) break;
}
return lock_type;
}
GF_Err gf_cache_flush_disk_cache ( const DownloadedCacheEntry entry, Bool success )
{
char buff[100];
CHECK_ENTRY;
if (entry->mem_storage)
return GF_OK;
char szLOCK[GF_MAX_PATH];
sprintf(szLOCK, "%s.lock", entry->cfg_filename);
GF_LockStatus lock_type = cache_entry_lock(szLOCK);
GF_Config *cfg = gf_cfg_new(NULL, entry->cfg_filename);
if (success) {
char szSize[50];
sprintf(szSize, "%u", entry->written_in_cache);
gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_CONTENT_SIZE, szSize);
}
gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_INWRITE, NULL);
gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_URL, entry->url);
if (entry->range_start || entry->range_end) {
sprintf(buff, LLD"-"LLD, entry->range_start, entry->range_end);
gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_RANGE, buff);
}
if (entry->mimeType)
gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_MIME_TYPE, entry->mimeType);
if (entry->diskETag)
gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_ETAG, entry->diskETag);
if (entry->diskLastModified)
gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_LAST_MODIFIED, entry->diskLastModified);
snprintf(buff, 16, "%d", entry->contentLength);
gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_CONTENT_SIZE, buff);
//save and destroy
gf_cfg_del(cfg);
if (lock_type==GF_LOCKFILE_NEW) gf_file_delete(szLOCK);
return GF_OK;
}
u32 gf_cache_get_cache_filesize(const DownloadedCacheEntry entry )
{
return entry ? entry->cacheSize : -1;
}
const char * gf_cache_get_cache_filename( const DownloadedCacheEntry entry )
{
return entry ? entry->cache_filename : NULL;
}
GF_Err gf_cache_get_http_headers(const DownloadedCacheEntry entry, const char **etag, const char **last_modif)
{
if (!entry || !etag || !last_modif)
return GF_BAD_PARAM;
*etag = *last_modif = NULL;
if (entry->flags & (CORRUPTED|DELETED))
return GF_OK;
*etag = entry->diskETag;
*last_modif = entry->diskLastModified;
if (entry->persistent && entry->memory_stored) {
*etag = entry->serverETag;
*last_modif = entry->serverLastModified;
}
return GF_OK;
}
static void gf_cache_check_if_cache_file_is_corrupted(const DownloadedCacheEntry entry, const char *url, GF_Config *cfg)
{
const char * keyValue = gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_URL );
if ( keyValue == NULL || stricmp ( url, keyValue ) ) {
entry->flags |= CORRUPTED;
return;
}
keyValue = gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_RANGE);
if (keyValue) {
u64 s, e;
sscanf(keyValue, LLD"-"LLD, &s, &e);
/*mark as corrupted if not same range (we don't support this for the time being ...*/
if ((s!=entry->range_start) || (e!=entry->range_end)) {
entry->flags |= CORRUPTED;
return;
}
}
if (gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_NOCACHE) ) {
entry->flags |= CORRUPTED;
return;
}
FILE *the_cache = NULL;
if (entry->cache_filename && strncmp(entry->cache_filename, "gmem://", 7))
the_cache = gf_fopen ( entry->cache_filename, "rb" );
if ( the_cache ) {
char * endPtr;
const char * keyValue = gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_INWRITE);
if (keyValue) {
entry->cacheSize = 0;
entry->flags |= IN_PROGRESS;
gf_fclose ( the_cache );
return;
}
keyValue = gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_CONTENT_SIZE );
entry->cacheSize = ( u32 ) gf_fsize(the_cache);
gf_fclose ( the_cache );
if (keyValue) {
entry->contentLength = (u32) strtoul( keyValue, &endPtr, 10);
if (*endPtr!='\0' || entry->contentLength != entry->cacheSize) {
entry->flags |= CORRUPTED;
GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("[CACHE] Corrupted: file and cache info size mismatch\n"));
}
} else {
entry->flags |= CORRUPTED;
GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("[CACHE] Corrupted: missing cache content size\n"));
}
} else if (!entry->mem_storage) {
entry->flags |= CORRUPTED;
}
}
#define _CACHE_HASH_SIZE 20
#define _CACHE_MAX_EXTENSION_SIZE 6
static const char * default_cache_file_suffix = ".dat";
static const char * cache_file_info_suffix = ".txt";
DownloadedCacheEntry gf_cache_create_entry(const char * cache_directory, const char * url , u64 start_range, u64 end_range, Bool mem_storage, GF_Mutex *mx)
{
char tmp[GF_MAX_PATH];
u8 hash[_CACHE_HASH_SIZE];
int sz;
char ext[_CACHE_MAX_EXTENSION_SIZE];
DownloadedCacheEntry entry = NULL;
if (!url || !cache_directory) return NULL;
sz = (u32) strlen ( url );
if ( sz >= GF_MAX_PATH ) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE,
("[CACHE] ERROR, URL is too long (%d chars), more than %d chars.\n", sz, GF_MAX_PATH ));
return NULL;
}
tmp[0] = '\0';
/*generate hash of the full url*/
if (start_range && end_range) {
snprintf(tmp, GF_MAX_PATH, "%s_"LLD"-"LLD, url, start_range, end_range );
} else {
gf_strcpy ( tmp, url );
}
gf_sha1_csum ((u8*) tmp, (u32) strlen ( tmp ), hash );
tmp[0] = 0;
{
int i;
for ( i=0; i<20; i++ )
{
char t[3];
t[2] = 0;
sprintf ( t, "%02X", hash[i] );
gf_strcat ( tmp, t );
}
}
assert ( strlen ( tmp ) == (_CACHE_HASH_SIZE * 2) );
GF_SAFEALLOC(entry, struct __DownloadedCacheEntryStruct);
if ( !entry ) return NULL;
entry->url = gf_strdup ( url );
entry->hash = gf_strdup ( tmp );
entry->memory_stored = mem_storage;
entry->cacheSize = 0;
entry->contentLength = 0;
entry->serverETag = NULL;
entry->diskETag = NULL;
entry->flags = 0;
entry->diskLastModified = NULL;
entry->serverLastModified = NULL;
entry->range_start = start_range;
entry->range_end = end_range;
entry->discard_on_delete = GF_FALSE;
entry->write_session = NULL;
entry->sessions = gf_list_new();
u32 asize;
if (entry->memory_stored) {
asize = (u32) strlen ("gmem://") + 8 + (u32) strlen("@") + 16 + 1;
} else {
/* Sizeof cache directory + hash + possible extension */
asize = (u32) strlen ( cache_directory ) + (u32) strlen(cache_file_prefix) + (u32) strlen(tmp) + _CACHE_MAX_EXTENSION_SIZE + 1;
}
entry->cache_filename = (char*)gf_malloc(asize);
if ( !entry->hash || !entry->url || !entry->cache_filename || !entry->sessions) {
/* Probably out of memory */
GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("[CACHE] Failed to create cache entry, out of memory\n"));
gf_cache_delete_entry ( entry );
return NULL;
}
if (entry->memory_stored) {
entry->cache_blob.mx = mx;
entry->cache_blob.data = entry->mem_storage;
entry->cache_blob.size = entry->contentLength;
char *burl = gf_blob_register(&entry->cache_blob);
if (burl) {
gf_strlcpy(entry->cache_filename, burl, asize);
gf_free(burl);
}
return entry;
}
tmp[0] = '\0';
gf_strlcpy ( entry->cache_filename, cache_directory, asize );
gf_strlcat( entry->cache_filename, cache_file_prefix, asize);
gf_strlcat ( entry->cache_filename, entry->hash, asize);
gf_strcpy ( tmp, url );
{
char * parser;
parser = strrchr ( tmp, '?' );
if ( parser )
parser[0] = '\0';
parser = strrchr ( tmp, '#' );
if ( parser )
parser[0] = '\0';
parser = strrchr ( tmp, '.' );
gf_strcpy(ext, parser ? parser : default_cache_file_suffix);
assert (strlen(ext));
gf_strlcat( entry->cache_filename, ext, asize);
}
gf_dynstrcat(&entry->cfg_filename, entry->cache_filename, NULL);
gf_dynstrcat(&entry->cfg_filename, cache_file_info_suffix, NULL);
char szLOCK[GF_MAX_PATH];
snprintf(szLOCK, sizeof(szLOCK), "%s.lock", entry->cfg_filename);
GF_LockStatus lock_type = cache_entry_lock(szLOCK);
if (!lock_type) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("[CACHE] Failed to grab cache lock for entry %s, request will not be cached\n", url));
gf_cache_delete_entry ( entry );
return NULL;
}
Bool in_cache = gf_file_exists(entry->cfg_filename);
GF_Config *cfg = gf_cfg_force_new ( NULL, entry->cfg_filename);
if ( !cfg || !gf_cfg_get_filename(cfg) ) {
if (cfg) gf_cfg_del(cfg);
GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("[CACHE] Failed to create cache entry for %s, request will not be cached\n", url));
gf_cache_delete_entry ( entry );
if (lock_type==GF_LOCKFILE_NEW) gf_file_delete(szLOCK);
return NULL;
}
if (in_cache) {
gf_cache_check_if_cache_file_is_corrupted(entry, url, cfg);
if ((entry->flags & CORRUPTED) && (gf_cfg_get_key_count(cfg, CACHE_SECTION_USERS)==0)) {
GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("[CACHE] Cache entry for %s corrupted but no other users, recreating\n", url));
entry->flags &= ~CORRUPTED;
gf_file_delete(entry->cache_filename);
gf_file_delete(entry->cfg_filename);
gf_cfg_del_section(cfg, CACHE_SECTION_NAME);
gf_cfg_del_section(cfg, CACHE_SECTION_USERS);
}
}
if (entry->flags & CORRUPTED) {
gf_cfg_del(cfg);
GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("[CACHE] Incompatible cache entry for %s, request will not be cached\n", url));
gf_cache_delete_entry ( entry );
if (lock_type==GF_LOCKFILE_NEW) gf_file_delete(szLOCK);
return NULL;
}
gf_cache_set_etag_on_disk(entry, gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_ETAG));
gf_cache_set_etag_on_server(entry, gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_ETAG));
gf_cache_set_mime_type(entry, gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_MIME_TYPE));
gf_cache_set_last_modified_on_disk(entry, gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_LAST_MODIFIED));
gf_cache_set_last_modified_on_server(entry, gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_LAST_MODIFIED));
const char *opt = gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_MAXAGE);
if (opt) sscanf(opt, LLU, &entry->max_age);
opt = gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_MUST_REVALIDATE);
if (opt && !strcmp(opt, "true")) entry->must_revalidate = GF_TRUE;
char szBUF[20];
sprintf(szBUF, LLU, gf_net_get_utc()/1000);
gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_LAST_HIT, szBUF);
u32 nb_hits=0;
opt = gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_NUM_HIT );
if (opt) nb_hits = atoi(opt);
nb_hits++;
sprintf(szBUF, "%u", nb_hits);
gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_NUM_HIT, szBUF);
if (!mem_storage) {
char szID[20];
if (cache_cleanup_processes(cfg))
entry->other_in_use = GF_TRUE;
sprintf(szID, "%u", gf_sys_get_process_id());
gf_cfg_set_key(cfg, CACHE_SECTION_USERS, szID, "true");
}
//rewrite to disk
gf_cfg_del(cfg);
if (lock_type==GF_LOCKFILE_NEW) gf_file_delete(szLOCK);
return entry;
}
GF_Err gf_cache_set_content_length( const DownloadedCacheEntry entry, u32 length )
{
CHECK_ENTRY;
if (entry->continue_file) {
entry->contentLength = entry->previousRangeContentLength + length;
} else {
entry->contentLength = length;
}
return GF_OK;
}
u32 gf_cache_get_content_length( const DownloadedCacheEntry entry)
{
if (!entry) return 0;
if (entry->external_blob) {
return entry->external_blob->size;
}
return entry->contentLength;
}
GF_Err gf_cache_close_write_cache( const DownloadedCacheEntry entry, const GF_DownloadSession * sess, Bool success )
{
GF_Err e = GF_OK;
CHECK_ENTRY;
if (!sess || !entry->write_session || entry->write_session != sess)
return GF_OK;
gf_assert( sess == entry->write_session );
if (entry->writeFilePtr) {
GF_LOG(GF_LOG_INFO, GF_LOG_CACHE,
("[CACHE] Closing file %s, %d bytes written.\n", entry->cache_filename, entry->written_in_cache));
if (gf_fflush( entry->writeFilePtr ) || gf_fclose( entry->writeFilePtr )) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("[CACHE] Failed to flush/close file on disk\n"));
e = GF_IO_ERR;
}
if (!e && success) {
e = gf_cache_set_last_modified_on_disk( entry, gf_cache_get_last_modified_on_server(entry));
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("[CACHE] Failed to set last-modified\n"));
}
if (!e) {
e = gf_cache_set_etag_on_disk( entry, gf_cache_get_etag_on_server(entry));
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("[CACHE] Failed to set etag\n"));
}
}
}
e = gf_cache_flush_disk_cache(entry, success);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("[CACHE] Failed to flush cache entry on disk after etag/last-modified\n"));
}
#if defined(_BSD_SOURCE) || _XOPEN_SOURCE >= 500
/* On UNIX, be sure to flush all the data */
sync();
#endif
entry->writeFilePtr = NULL;
if (GF_OK != e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("[CACHE] Failed to fully write file on cache, e=%d\n", e));
}
if (!success) {
entry->discard_on_delete = GF_TRUE;
}
}
entry->write_session = NULL;
return e;
}
GF_Err gf_cache_open_write_cache( const DownloadedCacheEntry entry, const GF_DownloadSession * sess)
{
CHECK_ENTRY;
if (!sess)
return GF_BAD_PARAM;
entry->write_session = sess;
if (!entry->continue_file) {
gf_assert( ! entry->writeFilePtr);
entry->written_in_cache = 0;
}
entry->flags &= ~CORRUPTED;
if (entry->memory_stored) {
gf_mx_p(entry->cache_blob.mx);
GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("[CACHE] Opening cache file %s for write (%s)...\n", entry->cache_filename, entry->url));
if (!entry->mem_allocated || (entry->mem_allocated < entry->contentLength)) {
if (entry->contentLength) entry->mem_allocated = entry->contentLength;
else if (!entry->mem_allocated) entry->mem_allocated = 81920;
entry->mem_storage = (u8*)gf_realloc(entry->mem_storage, sizeof(char)* (entry->mem_allocated + 2) );
}
entry->cache_blob.data = entry->mem_storage;
entry->cache_blob.size = entry->contentLength;
char *burl = gf_blob_register(&entry->cache_blob);
if (burl) {
gf_strlcpy(entry->cache_filename, burl, (strlen(entry->cache_filename)+1) );
gf_free(burl);
}
gf_mx_v(entry->cache_blob.mx);
if (!entry->mem_allocated) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("[CACHE] Failed to create memory storage for file %s\n", entry->url));
return GF_OUT_OF_MEM;
}
return GF_OK;
}
GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("[CACHE] Opening cache file %s for write (%s)...\n", entry->cache_filename, entry->url));
entry->writeFilePtr = gf_fopen(entry->cache_filename, entry->continue_file ? "a+b" : "wb");
if (!entry->writeFilePtr) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE,
("[CACHE] Error while opening cache file %s for writting.\n", entry->cache_filename));
entry->write_session = NULL;
return GF_IO_ERR;
}
if (entry->continue_file )
gf_fseek(entry->writeFilePtr, 0, SEEK_END);
char szLOCK[GF_MAX_PATH];
snprintf(szLOCK, sizeof(szLOCK), "%s.lock", entry->cfg_filename);
GF_LockStatus lock_type = cache_entry_lock(szLOCK);
GF_Config *cfg = gf_cfg_new(NULL, entry->cfg_filename);
char szUTC[20];
sprintf(szUTC, LLU, gf_net_get_utc()/1000);
gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_CREATED, szUTC);
gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_INWRITE, "true");
gf_cfg_del(cfg);
if (lock_type==GF_LOCKFILE_NEW) gf_file_delete(szLOCK);
return GF_OK;
}
GF_Err gf_cache_write_to_cache( const DownloadedCacheEntry entry, const GF_DownloadSession * sess, const char * data, const u32 size, GF_Mutex *mx) {
u32 read;
CHECK_ENTRY;
if (!data || (!entry->writeFilePtr && !entry->mem_storage) || sess != entry->write_session) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("Incorrect parameter : data=%p, writeFilePtr=%p mem_storage=%p at "__FILE__"\n", data, entry->writeFilePtr, entry->mem_storage));
return GF_BAD_PARAM;
}
if (entry->memory_stored) {
if (!entry->cache_blob.mx)
entry->cache_blob.mx = mx;
gf_assert(mx);
gf_mx_p(entry->cache_blob.mx);
if (entry->written_in_cache + size > entry->mem_allocated) {
u32 new_size = MAX(entry->mem_allocated*2, entry->written_in_cache + size);
entry->mem_storage = (u8*)gf_realloc(entry->mem_storage, (new_size+2));
entry->mem_allocated = new_size;
entry->cache_blob.data = entry->mem_storage;
entry->cache_blob.size = entry->contentLength;
char *burl = gf_blob_register(&entry->cache_blob);
if (burl) {
gf_strlcpy(entry->cache_filename, burl, (strlen(entry->cache_filename)+1) );
gf_free(burl);
}
GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Reallocating memory cache to %d bytes\n", new_size));
}
memcpy(entry->mem_storage + entry->written_in_cache, data, size);
entry->written_in_cache += size;
memset(entry->mem_storage + entry->written_in_cache, 0, 2);
entry->cache_blob.size = entry->written_in_cache;
gf_mx_v(entry->cache_blob.mx);
GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Storing %d bytes to memory\n", size));
return GF_OK;
}
read = (u32) gf_fwrite(data, size, entry->writeFilePtr);
if (read > 0)
entry->written_in_cache+= read;
if (read != size) {
/* Something bad happened */
GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE,
("[CACHE] Error while writting %d bytes of data to cache : has written only %d bytes.", size, read));
gf_cache_close_write_cache(entry, sess, GF_FALSE);
return GF_IO_ERR;
}
if (gf_fflush(entry->writeFilePtr)) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE,
("[CACHE] Error while flushing data bytes to cache file : %s.", entry->cache_filename));
gf_cache_close_write_cache(entry, sess, GF_FALSE);
return GF_IO_ERR;
}
GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Writing %d bytes to cache\n", size));
return GF_OK;
}
void gf_cache_delete_entry( const DownloadedCacheEntry entry )
{
if ( !entry ) return;
if (entry->writeFilePtr) {
/** Cache should have been close before, abornormal situation */
GF_LOG(GF_LOG_WARNING, GF_LOG_CACHE, ("[CACHE] Cache for %s has not been closed properly\n", entry->url));
gf_fclose(entry->writeFilePtr);
}
gf_blob_unregister(&entry->cache_blob);
if (entry->external_blob) {
gf_blob_unregister(entry->external_blob);
}
if (!entry->mem_storage ) {
char szLOCK[GF_MAX_PATH];
snprintf(szLOCK, sizeof(szLOCK), "%s.lock", entry->cfg_filename);
GF_LockStatus lock_type = cache_entry_lock(szLOCK);
GF_Config *cfg = gf_cfg_new(NULL, entry->cfg_filename);
if (cfg) {
char szID[20];
sprintf(szID, "%u", gf_sys_get_process_id());
gf_cfg_set_key(cfg, CACHE_SECTION_USERS, szID, NULL);
Bool still_in_use = cache_cleanup_processes(cfg);
if (still_in_use && entry->discard_on_delete) {
gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_TODELETE, "true");
entry->discard_on_delete = GF_FALSE;
} else if (!still_in_use) {
if (gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_TODELETE)) {
gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_TODELETE, NULL);
entry->discard_on_delete = GF_TRUE;
}
}
gf_cfg_del(cfg);
}
if (lock_type==GF_LOCKFILE_NEW) gf_file_delete(szLOCK);
if (entry->discard_on_delete) {
gf_file_delete(entry->cache_filename);
gf_file_delete(entry->cfg_filename);
}
}
if (entry->cfg_filename) gf_free(entry->cfg_filename);
if (entry->cache_filename) gf_free(entry->cache_filename);
if (entry->serverETag) gf_free(entry->serverETag);
if (entry->diskETag) gf_free(entry->diskETag);
if (entry->serverLastModified) gf_free(entry->serverLastModified);
if (entry->diskLastModified) gf_free(entry->diskLastModified);
if (entry->hash) gf_free(entry->hash);
if (entry->url) gf_free(entry->url);
if (entry->mimeType) gf_free(entry->mimeType);
if (entry->mem_storage && entry->mem_allocated) gf_free(entry->mem_storage);
if (entry->forced_headers) gf_free(entry->forced_headers);
if (entry->sessions) {
gf_assert( gf_list_count(entry->sessions) == 0);
gf_list_del(entry->sessions);
}
gf_free (entry);
}
s32 gf_cache_remove_session_from_cache_entry(DownloadedCacheEntry entry, GF_DownloadSession * sess) {
u32 i;
s32 count;
if (!entry || !sess || !entry->sessions)
return -1;
count = gf_list_count(entry->sessions);
for (i = 0 ; i < (u32)count; i++) {
GF_DownloadSession *s = (GF_DownloadSession*)gf_list_get(entry->sessions, i);
if (s == sess) {
gf_list_rem(entry->sessions, i);
count --;
break;
}
}
if (entry->write_session == sess) {
/* OK, this is not optimal to close it since we are in a mutex,
* but we don't want to risk to have another session opening
* a not fully closed cache entry */
if (entry->writeFilePtr) {
if (gf_fclose(entry->writeFilePtr)) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CACHE, ("[CACHE] gf_cache_remove_session_from_cache_entry:%d, Failed to properly close cache file '%s' of url '%s', cache may be corrupted !\n", __LINE__, entry->cache_filename, entry->url));
}
}
entry->writeFilePtr = NULL;
entry->write_session = NULL;
}
return count;
}
void gf_cache_set_max_age(const DownloadedCacheEntry entry, u32 max_age, Bool must_revalidate)
{
char szVal[100];
if (!entry || entry->mem_storage) return;
char szLOCK[GF_MAX_PATH];
snprintf(szLOCK, sizeof(szLOCK), "%s.lock", entry->cfg_filename);
GF_LockStatus lock_type = cache_entry_lock(szLOCK);
GF_Config *cfg = gf_cfg_new(NULL, entry->cfg_filename);
entry->must_revalidate = must_revalidate;
if (max_age)
entry->max_age = gf_net_get_utc()/1000 + max_age;
sprintf(szVal, LLU, entry->max_age);
gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_MAXAGE, szVal);
gf_cfg_set_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_MUST_REVALIDATE, must_revalidate ? "true" : "false");
gf_cfg_del(cfg);
if (lock_type==GF_LOCKFILE_NEW) gf_file_delete(szLOCK);
}
static u32 gf_cache_get_sessions_count_for_cache_entry(const DownloadedCacheEntry entry)
{
if (!entry)
return 0;
return gf_list_count(entry->sessions);
}
s32 gf_cache_add_session_to_cache_entry(DownloadedCacheEntry entry, GF_DownloadSession * sess) {
u32 i;
s32 count;
if (!entry || !sess || !entry->sessions)
return -1;
count = gf_list_count(entry->sessions);
for (i = 0 ; i < (u32)count; i++) {
GF_DownloadSession *s = (GF_DownloadSession*)gf_list_get(entry->sessions, i);
if (s == sess) {
return count;
}
}
gf_list_add(entry->sessions, sess);
return count + 1;
}
void gf_cache_set_end_range(DownloadedCacheEntry entry, u64 range_end)
{
if (entry) {
entry->previousRangeContentLength = entry->contentLength;
entry->range_end = range_end;
entry->continue_file = GF_TRUE;
}
}
Bool gf_cache_is_in_progress(const DownloadedCacheEntry entry)
{
if (!entry) return GF_FALSE;
if (entry->writeFilePtr) return GF_TRUE;
if (entry->mem_storage && entry->written_in_cache && entry->contentLength && (entry->written_in_cache<entry->contentLength))
return GF_TRUE;
return GF_FALSE;
}
Bool gf_cache_set_mime(const DownloadedCacheEntry entry, const char *mime)
{
if (!entry || !entry->memory_stored) return GF_FALSE;
if (entry->mimeType) gf_free(entry->mimeType);
entry->mimeType = gf_strdup(mime);
return GF_TRUE;
}
Bool gf_cache_set_range(const DownloadedCacheEntry entry, u64 size, u64 start_range, u64 end_range)
{
if (!entry || !entry->memory_stored) return GF_FALSE;
entry->range_start = start_range;
entry->range_end = end_range;
entry->contentLength = (u32) size;
entry->continue_file = GF_FALSE;
return GF_TRUE;
}
Bool gf_cache_set_headers(const DownloadedCacheEntry entry, const char *headers)
{
if (!entry || !entry->memory_stored) return GF_FALSE;
if (entry->forced_headers) gf_free(entry->forced_headers);
entry->forced_headers = headers ? gf_strdup(headers) : NULL;
return GF_TRUE;
}
char *gf_cache_get_forced_headers(const DownloadedCacheEntry entry)
{
if (!entry) return NULL;
return entry->forced_headers;
}
void gf_cache_set_downtime(const DownloadedCacheEntry entry, u32 download_time_ms)
{
if (entry) entry->downtime = download_time_ms;
}
u32 gf_cache_get_downtime(const DownloadedCacheEntry entry)
{
if (!entry) return 0;
return entry->downtime;
}
u32 gf_cache_is_done(const DownloadedCacheEntry entry)
{
if (!entry) return 1;
u32 res = 1;
if (entry->external_blob) {
gf_mx_p(entry->external_blob->mx);
res = (entry->external_blob->flags & GF_BLOB_IN_TRANSFER) ? 0 : 1;
if (res && (entry->external_blob->flags & GF_BLOB_CORRUPTED))
res = 2;
gf_mx_v(entry->external_blob->mx);
} else if (entry->mem_storage) {
res = (entry->cache_blob.flags & GF_BLOB_IN_TRANSFER) ? 0 : 1;
} else {
if (!(entry->flags & IN_PROGRESS)) return 1;
char szLOCK[GF_MAX_PATH];
sprintf(szLOCK, "%s.lock", entry->cfg_filename);
GF_LockStatus lock_type = cache_entry_lock(szLOCK);
GF_Config *cfg = gf_cfg_new(NULL, entry->cfg_filename);
const char *opt = gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_INWRITE);
res = opt ? 0 : 1;
if (res) {
entry->flags &= ~IN_PROGRESS;
opt = gf_cfg_get_key(cfg, CACHE_SECTION_NAME, CACHE_SECTION_KEY_CONTENT_SIZE);
entry->contentLength = atoi(opt);
}
gf_cfg_del(cfg);
if (lock_type==GF_LOCKFILE_NEW) gf_file_delete(szLOCK);
}
return res;
}
const u8 *gf_cache_get_content(const DownloadedCacheEntry entry, u32 *size, u32 *max_valid_size, Bool *was_modified)
{
if (!entry) return NULL;
*was_modified = GF_FALSE;
if (entry->external_blob) {
u8 *data;
GF_Err e = gf_blob_get_ex(entry->external_blob, &data, size, NULL);
if (e) return NULL;
*max_valid_size = *size;
if (entry->external_blob->range_valid) {
gf_mx_p(entry->external_blob->mx);
entry->external_blob->range_valid(entry->external_blob, GF_FALSE, 0, max_valid_size);
gf_mx_v(entry->external_blob->mx);
}
if (entry->external_blob->last_modification_time != entry->cache_blob.last_modification_time) {
*was_modified = GF_TRUE;
entry->cache_blob.last_modification_time = entry->external_blob->last_modification_time;
}
return data;
}
*max_valid_size = *size = entry->cache_blob.size;
return entry->cache_blob.data;
}
void gf_cache_release_content(const DownloadedCacheEntry entry)
{
if (!entry) return;
if (!entry->external_blob) return;
gf_blob_release_ex(entry->external_blob);
}
Bool gf_cache_is_deleted(const DownloadedCacheEntry entry)
{
if (!entry) return GF_TRUE;
if (entry->flags & DELETED) return GF_TRUE;
return GF_FALSE;
}
Bool gf_cache_set_content(const DownloadedCacheEntry entry, GF_Blob *blob, Bool copy, GF_Mutex *mx)
{
if (!entry || !entry->memory_stored) return GF_FALSE;
if (!blob) {
entry->flags |= DELETED;
if (entry->external_blob) {
gf_blob_unregister(entry->external_blob);
entry->external_blob = NULL;
}
return GF_TRUE;
}
if (blob->mx)
gf_mx_p(blob->mx);
if (!copy) {
if (entry->mem_allocated) gf_free(entry->mem_storage);
entry->mem_storage = (u8 *) blob->data;
if (!entry->written_in_cache) {
char *burl = gf_blob_register(blob);
if (burl) {
gf_strlcpy(entry->cache_filename, burl, (strlen(entry->cache_filename)+1) );
gf_free(burl);
}
}
entry->written_in_cache = blob->size;
entry->mem_allocated = 0;
entry->cache_blob.data = NULL;
entry->cache_blob.size = 0;
entry->external_blob = blob;
GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Storing %d bytes to memory from external module\n", blob->size));
} else {
if (!entry->cache_blob.mx)
entry->cache_blob.mx = mx;
gf_mx_p(entry->cache_blob.mx);
if (blob->size >= entry->mem_allocated) {
u32 new_size;
new_size = MAX(entry->mem_allocated*2, blob->size+1);
entry->mem_storage = (u8*)gf_realloc(entry->mem_allocated ? entry->mem_storage : NULL, (new_size+2));
entry->mem_allocated = new_size;
entry->cache_blob.data = entry->mem_storage;
entry->cache_blob.size = entry->contentLength;
if (!entry->written_in_cache) {
char *burl = gf_blob_register(&entry->cache_blob);
if (burl) {
gf_strlcpy(entry->cache_filename, burl, (strlen(entry->cache_filename)+1) );
gf_free(burl);
}
}
GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Reallocating memory cache to %d bytes\n", new_size));
}
memcpy(entry->mem_storage, blob->data, blob->size);
entry->mem_storage[blob->size] = 0;
entry->cache_blob.size = entry->written_in_cache = blob->size;
GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Storing %d bytes to cache memory\n", blob->size));
gf_mx_v(entry->cache_blob.mx);
entry->cache_blob.flags = blob->flags;
}
if (blob->flags & GF_BLOB_IN_TRANSFER)
entry->contentLength = 0;
else
entry->contentLength = blob->size;
if (blob->mx)
gf_mx_v(blob->mx);
return GF_TRUE;
}
static Bool gf_cache_entry_can_reuse(const DownloadedCacheEntry entry, Bool skip_revalidate)
{
if (!entry) return GF_FALSE;
//if corrupted or deleted, we cannot reuse
if (entry->flags & (CORRUPTED|DELETED) ) return GF_FALSE;
if (!entry->max_age) return GF_FALSE;
s64 expires = entry->max_age;
expires -= gf_net_get_utc()/1000;
//max age not reached, allow reuse
if (expires>0)
return GF_TRUE;
if (entry->must_revalidate)
return GF_FALSE;
//we consider that if not expired and no must-revalidate, we can go on
return GF_TRUE;
}
Bool gf_cache_entry_is_shared(const DownloadedCacheEntry entry)
{
return entry ? entry->other_in_use : GF_FALSE;
}
Bool gf_cache_is_mem(const DownloadedCacheEntry entry)
{
return entry->mem_storage ? GF_TRUE : GF_FALSE;
}
FILE *gf_cache_open_read(const DownloadedCacheEntry entry)
{
if (!entry) return NULL;
if (entry->mem_storage) return NULL;
return gf_fopen(entry->cache_filename, "r");
}
/*!
* Finds an existing entry in the cache for a given URL
\param sess The session configured with the URL
\return NULL if none found, the DownloadedCacheEntry otherwise
*/
static DownloadedCacheEntry gf_cache_find_entry_by_url(GF_DownloadSession * sess)
{
u32 i, count;
gf_assert( sess && sess->dm && sess->dm->cache_entries );
gf_mx_p( sess->dm->cache_mx );
count = gf_list_count(sess->dm->cache_entries);
for (i = 0 ; i < count; i++) {
const char * url;
DownloadedCacheEntry e = (DownloadedCacheEntry)gf_list_get(sess->dm->cache_entries, i);
gf_assert(e);
url = gf_cache_get_url(e);
gf_assert( url );
if (!strncmp(url, "http://gmcast/", 14)) {
char *sep_1 = strchr(url+14, '/');
char *sep_2 = strchr(sess->orig_url+14, '/');
if (!sep_1 || !sep_2 || strcmp(sep_1, sep_2))
continue;
} else if (strcmp(url, sess->orig_url)) continue;
if (! sess->is_range_continuation) {
if (sess->range_start != gf_cache_get_start_range(e)) continue;
if (sess->range_end != gf_cache_get_end_range(e)) continue;
}
/*OK that's ours*/
gf_mx_v( sess->dm->cache_mx );
return e;
}
gf_mx_v( sess->dm->cache_mx );
return NULL;
}
/**
* Removes a cache entry from cache and performs a cleanup if possible.
* If the cache entry is marked for deletion and has no sessions associated with it, it will be
* removed (so some modules using a streaming like cache will still work).
*/
void gf_cache_remove_entry_from_session(GF_DownloadSession * sess)
{
if (!sess || !sess->cache_entry) return;
gf_cache_remove_session_from_cache_entry(sess->cache_entry, sess);
if (sess->dm
/*JLF - not sure what the rationale of this test is, and it prevents cleanup of cache entry
which then results to crash when restarting the session (entry->writeFilePtr is not set back to NULL)*/
&& sess->cache_entry->discard_on_delete
&& (0 == gf_cache_get_sessions_count_for_cache_entry(sess->cache_entry)))
{
u32 i, count;
gf_mx_p( sess->dm->cache_mx );
count = gf_list_count( sess->dm->cache_entries );
for (i = 0; i < count; i++) {
DownloadedCacheEntry ex = (DownloadedCacheEntry)gf_list_get(sess->dm->cache_entries, i);
if (ex == sess->cache_entry) {
gf_list_rem(sess->dm->cache_entries, i);
gf_cache_delete_entry( sess->cache_entry );
sess->cache_entry = NULL;
break;
}
}
gf_mx_v( sess->dm->cache_mx );
}
}
void gf_dm_configure_cache(GF_DownloadSession *sess)
{
DownloadedCacheEntry entry;
gf_cache_remove_entry_from_session(sess);
//session is not cached and we don't cache the first URL
if ((sess->flags & GF_NETIO_SESSION_NOT_CACHED) && !(sess->flags & GF_NETIO_SESSION_KEEP_FIRST_CACHE)) {
sess->reused_cache_entry = GF_FALSE;
if (sess->cache_entry)
gf_cache_close_write_cache(sess->cache_entry, sess, GF_FALSE);
sess->cache_entry = NULL;
return;
}
Bool found = GF_FALSE;
u32 i, count;
entry = gf_cache_find_entry_by_url(sess);
if (!entry) {
if (sess->local_cache_only) {
sess->cache_entry = NULL;
SET_LAST_ERR(GF_URL_ERROR)
return;
}
/* We found the existing session */
if (sess->cache_entry) {
Bool delete_cache = GF_TRUE;
if (sess->flags & GF_NETIO_SESSION_KEEP_CACHE) {
delete_cache = GF_FALSE;
}
if (gf_cache_entry_persistent(sess->cache_entry))
delete_cache = GF_FALSE;
/*! indicate we can destroy file upon destruction, except if disabled at session level*/
if (delete_cache)
gf_cache_entry_set_delete_files_when_deleted(sess->cache_entry);
if (!gf_cache_entry_persistent(sess->cache_entry)
&& !gf_cache_get_sessions_count_for_cache_entry(sess->cache_entry)
) {
gf_mx_p( sess->dm->cache_mx );
/* No session attached anymore... we can delete it */
gf_list_del_item(sess->dm->cache_entries, sess->cache_entry);
gf_mx_v( sess->dm->cache_mx );
gf_cache_delete_entry(sess->cache_entry);
}
sess->cache_entry = NULL;
}
Bool use_mem = (sess->flags & (GF_NETIO_SESSION_MEMORY_CACHE | GF_NETIO_SESSION_NO_STORE)) ? GF_TRUE : GF_FALSE;
entry = gf_cache_create_entry(sess->dm->cache_directory, sess->orig_url, sess->range_start, sess->range_end, use_mem, sess->dm->cache_mx);
if (!entry) {
SET_LAST_ERR(GF_IO_ERR)
return;
}
gf_mx_p( sess->dm->cache_mx );
gf_list_add(sess->dm->cache_entries, entry);
gf_mx_v( sess->dm->cache_mx );
sess->is_range_continuation = GF_FALSE;
}
sess->cache_entry = entry;
sess->reused_cache_entry = gf_cache_is_in_progress(entry);
gf_mx_p(sess->dm->cache_mx);
count = gf_list_count(sess->dm->all_sessions);
for (i=0; i<count; i++) {
GF_DownloadSession *a_sess = (GF_DownloadSession*)gf_list_get(sess->dm->all_sessions, i);
gf_assert(a_sess);
if (a_sess==sess) continue;
if (a_sess->cache_entry==entry) {
found = GF_TRUE;
break;
}
}
gf_mx_v(sess->dm->cache_mx);
if (!found) {
sess->reused_cache_entry = GF_FALSE;
if (sess->cache_entry)
gf_cache_close_write_cache(sess->cache_entry, sess, GF_FALSE);
}
gf_cache_add_session_to_cache_entry(sess->cache_entry, sess);
if (sess->needs_range)
gf_cache_set_range(sess->cache_entry, 0, sess->range_start, sess->range_end);
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[CACHE] Cache for %s setup %s\n", sess->orig_url, gf_cache_get_cache_filename(sess->cache_entry)));
Bool no_revalidate = GF_FALSE;
if (sess->allow_direct_reuse)
no_revalidate = GF_TRUE;
if (sess->cache_entry) {
if (sess->flags & GF_NETIO_SESSION_KEEP_FIRST_CACHE) {
sess->flags &= ~GF_NETIO_SESSION_KEEP_FIRST_CACHE;
gf_cache_entry_set_persistent(sess->cache_entry);
}
//this one is only used for init segments in dash/hls
if ((sess->flags & GF_NETIO_SESSION_MEMORY_CACHE) && (sess->flags & GF_NETIO_SESSION_KEEP_CACHE) ) {
gf_cache_entry_set_persistent(sess->cache_entry);
}
if (gf_cache_entry_can_reuse(sess->cache_entry, sess->dm->allow_offline_cache)) {
no_revalidate = GF_TRUE;
}
//we cannot reuse and others are using the cache file - for now we don't have any other possibility
//than disabling the cache to avoid overwriting the resource
else if (gf_cache_entry_is_shared(sess->cache_entry)) {
gf_cache_remove_entry_from_session(sess);
sess->cache_entry = NULL;
sess->reused_cache_entry = GF_FALSE;
return;
}
}
if (no_revalidate)
sess->cached_file = gf_cache_open_read(sess->cache_entry);
if (sess->cached_file) {
sess->connect_time = 0;
sess->status = GF_NETIO_CONNECTED;
const char *mime = gf_cache_get_mime_type(sess->cache_entry);
if (mime)
gf_dm_sess_set_header_ex(sess, "Content-Type", mime, GF_TRUE);
u32 size = gf_cache_get_content_length(sess->cache_entry);
if (size) {
char szHdr[20];
sprintf(szHdr, "%u", size);
gf_dm_sess_set_header_ex(sess, "Content-Length", szHdr, GF_TRUE);
}
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[%s] using existing cache entry\n", sess->log_name));
gf_dm_sess_notify_state(sess, GF_NETIO_CONNECTED, GF_OK);
}
}
void gf_dm_delete_cached_file_entry(const GF_DownloadManager * dm, const char * url)
{
GF_Err e;
u32 count, i;
char * realURL;
GF_URL_Info info;
if (!url || !dm)
return;
gf_mx_p( dm->cache_mx );
gf_dm_url_info_init(&info);
e = gf_dm_get_url_info(url, &info, NULL);
if (e != GF_OK) {
gf_mx_v( dm->cache_mx );
gf_dm_url_info_del(&info);
return;
}
realURL = gf_strdup(info.canonicalRepresentation);
gf_dm_url_info_del(&info);
gf_assert( realURL );
count = gf_list_count(dm->cache_entries);
for (i = 0 ; i < count; i++) {
const char * e_url;
DownloadedCacheEntry cache_ent = (DownloadedCacheEntry)gf_list_get(dm->cache_entries, i);
gf_assert(cache_ent);
e_url = gf_cache_get_url(cache_ent);
gf_assert( e_url );
if (!strcmp(e_url, realURL)) {
/* We found the existing session */
gf_cache_entry_set_delete_files_when_deleted(cache_ent);
if (0 == gf_cache_get_sessions_count_for_cache_entry( cache_ent )) {
/* No session attached anymore... we can delete it */
gf_list_rem(dm->cache_entries, i);
gf_cache_delete_entry(cache_ent);
}
/* If deleted or not, we don't search further */
gf_mx_v( dm->cache_mx );
gf_free(realURL);
return;
}
}
/* If we are here it means we did not found this URL in cache */
gf_mx_v( dm->cache_mx );
gf_free(realURL);
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[CACHE] Cannot find URL %s, cache file won't be deleted.\n", url));
}
GF_EXPORT
void gf_dm_delete_cached_file_entry_session(const GF_DownloadSession * sess, const char * url, Bool force)
{
if (sess && sess->dm && url) {
GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[CACHE] Removing cache entry for %s\n", url));
gf_dm_delete_cached_file_entry(sess->dm, url);
if (sess->local_cache_only && sess->dm->local_cache_url_provider_cbk)
sess->dm->local_cache_url_provider_cbk(sess->dm->lc_udta, (char *) url, GF_TRUE);
}
}
GF_EXPORT
GF_Err gf_dm_set_localcache_provider(GF_DownloadManager *dm, Bool (*local_cache_url_provider_cbk)(void *udta, char *url, Bool is_cache_destroy), void *lc_udta)
{
if (!dm)
return GF_BAD_PARAM;
dm->local_cache_url_provider_cbk = local_cache_url_provider_cbk;
dm->lc_udta = lc_udta;
return GF_OK;
}
GF_EXPORT
DownloadedCacheEntry gf_dm_add_cache_entry(GF_DownloadManager *dm, const char *szURL, GF_Blob *blob, u64 start_range, u64 end_range, const char *mime, Bool clone_memory, u32 download_time_ms)
{
u32 i, count;
DownloadedCacheEntry the_entry = NULL;
gf_mx_p(dm->cache_mx );
if (blob)
GF_LOG(GF_LOG_INFO, GF_LOG_CACHE, ("[CACHE] Pushing %s to cache "LLU" bytes (done %s)\n", szURL, blob->size, (blob->flags & GF_BLOB_IN_TRANSFER) ? "no" : "yes"));
count = gf_list_count(dm->cache_entries);
for (i = 0 ; i < count; i++) {
const char * url;
DownloadedCacheEntry e = (DownloadedCacheEntry)gf_list_get(dm->cache_entries, i);
gf_assert(e);
url = gf_cache_get_url(e);
gf_assert( url );
if (strcmp(url, szURL)) continue;
if (end_range) {
if (start_range != gf_cache_get_start_range(e)) continue;
if (end_range != gf_cache_get_end_range(e)) continue;
}
/*OK that's ours*/
the_entry = e;
break;
}
if (!the_entry) {
the_entry = gf_cache_create_entry("", szURL, 0, 0, GF_TRUE, dm->cache_mx);
if (!the_entry) {
gf_mx_v(dm->cache_mx );
return NULL;
}
gf_list_add(dm->cache_entries, the_entry);
}
gf_cache_set_mime(the_entry, mime);
if (blob && ! (blob->flags & GF_BLOB_IN_TRANSFER))
gf_cache_set_range(the_entry, blob->size, start_range, end_range);
gf_cache_set_content(the_entry, blob, clone_memory ? GF_TRUE : GF_FALSE, dm->cache_mx);
gf_cache_set_downtime(the_entry, download_time_ms);
gf_mx_v(dm->cache_mx );
return the_entry;
}
void gf_dm_sess_reload_cached_headers(GF_DownloadSession *sess)
{
char *hdrs;
if (!sess || !sess->local_cache_only) return;
hdrs = gf_cache_get_forced_headers(sess->cache_entry);
gf_dm_sess_clear_headers(sess);
while (hdrs) {
char *sep2, *sepL = strstr(hdrs, "\r\n");
if (sepL) sepL[0] = 0;
sep2 = strchr(hdrs, ':');
if (sep2) {
GF_HTTPHeader *hdr;
GF_SAFEALLOC(hdr, GF_HTTPHeader);
if (!hdr) break;
sep2[0]=0;
hdr->name = gf_strdup(hdrs);
sep2[0]=':';
sep2++;
while (sep2[0]==' ') sep2++;
hdr->value = gf_strdup(sep2);
gf_list_add(sess->headers, hdr);
}
if (!sepL) break;
sepL[0] = '\r';
hdrs = sepL + 2;
}
}
GF_EXPORT
GF_Err gf_dm_force_headers(GF_DownloadManager *dm, const DownloadedCacheEntry entry, const char *headers)
{
u32 i, count;
Bool res;
if (!entry)
return GF_BAD_PARAM;
gf_mx_p(dm->cache_mx);
res = gf_cache_set_headers(entry, headers);
count = gf_list_count(dm->all_sessions);
for (i=0; i<count; i++) {
GF_DownloadSession *sess = gf_list_get(dm->all_sessions, i);
if (sess->cache_entry != entry) continue;
gf_dm_sess_reload_cached_headers(sess);
}
gf_mx_v(dm->cache_mx);
if (res) return GF_OK;
return GF_BAD_PARAM;
}
#endif //GPAC_DISABLE_NETWORK