blob: 33730572bfe1bf5afc85536d02bf490c3a6b9a81 [file] [edit]
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Jean Le Feuvre - Copyright (c) Telecom ParisTech 2000-2026
* Romain Bouqueau - Copyright (c) Romain Bouqueau 2015
* All rights reserved
*
* This file is part of GPAC / common tools 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>
#include <gpac/utf.h>
#if defined(_WIN32_WCE)
#include <winbase.h>
#include <tlhelp32.h>
#elif defined(WIN32)
#include <windows.h>
#include <process.h>
#include <direct.h>
#include <sys/stat.h>
#include <share.h>
#else
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/time.h>
#ifndef __BEOS__
#include <errno.h>
#endif
#endif
GF_EXPORT
GF_Err gf_rmdir(const char *DirPathName)
{
#if defined (_WIN32_WCE)
TCHAR swzName[MAX_PATH];
BOOL res;
CE_CharToWide(DirPathName, swzName);
res = RemoveDirectory(swzName);
if (! res) {
int err = GetLastError();
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Cannot delete directory \"%s\": last error %d\n", DirPathName, err ));
}
#elif defined (WIN32)
int res;
wchar_t* wcsDirPathName = gf_utf8_to_wcs(DirPathName);
if (!wcsDirPathName)
return GF_IO_ERR;
res = _wrmdir(wcsDirPathName);
gf_free(wcsDirPathName);
if (res == -1) {
int err = GetLastError();
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Cannot delete directory \"%s\": last error %d\n", DirPathName, err ));
return GF_IO_ERR;
}
#else
int res = rmdir(DirPathName);
if (res==-1) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Cannot delete directory \"%s\": last error %d\n", DirPathName, errno ));
return GF_IO_ERR;
}
#endif
return GF_OK;
}
GF_EXPORT
GF_Err gf_mkdir(const char* DirPathName)
{
#if defined (_WIN32_WCE)
TCHAR swzName[MAX_PATH];
BOOL res;
CE_CharToWide(DirPathName, swzName);
res = CreateDirectory(swzName, NULL);
if (! res) {
int err = GetLastError();
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Cannot create directory \"%s\": last error %d\n", DirPathName, err ));
}
#elif defined (WIN32)
int res;
//don't try creation for UNC root: \\foo\bar\name will trigger creation for "", "\\foo" and "\\foo\bar", ignore these
if (!strcmp(DirPathName, "")) return GF_OK;
if (!strncmp(DirPathName, "\\\\", 2)) {
char *sep = strchr(DirPathName + 2, '\\');
if (!sep) return GF_OK;
sep = strchr(sep + 1, '\\');
if (!sep) return GF_OK;
}
wchar_t* wcsDirPathName = gf_utf8_to_wcs(DirPathName);
if (!wcsDirPathName)
return GF_IO_ERR;
res = _wmkdir(wcsDirPathName);
gf_free(wcsDirPathName);
if (res==-1) {
int err = GetLastError();
if (err != 183) {
// don't throw error if dir already exists
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Cannot create directory \"%s\": last error %d\n", DirPathName, err));
}
}
#else
int res = mkdir(DirPathName, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
if (res==-1) {
if(errno == 17) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Cannot create directory \"%s\", it already exists: last error %d \n", DirPathName, errno ));
return GF_OK;
} else {
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Cannot create directory \"%s\": last error %d\n", DirPathName, errno ));
return GF_IO_ERR;
}
}
#endif
return GF_OK;
}
GF_EXPORT
Bool gf_dir_exists(const char* DirPathName)
{
#if defined (_WIN32_WCE)
TCHAR swzName[MAX_PATH];
DWORD att;
CE_CharToWide(DirPathName, swzName);
att = GetFileAttributes(swzName);
return (att != INVALID_FILE_ATTRIBUTES && (att & FILE_ATTRIBUTE_DIRECTORY)) ? GF_TRUE : GF_FALSE;
#elif defined (WIN32)
DWORD att;
wchar_t* wcsDirPathName = gf_utf8_to_wcs(DirPathName);
if (!wcsDirPathName)
return GF_FALSE;
att = GetFileAttributesW(wcsDirPathName);
gf_free(wcsDirPathName);
return (att != INVALID_FILE_ATTRIBUTES && (att & FILE_ATTRIBUTE_DIRECTORY)) ? GF_TRUE : GF_FALSE;
#else
struct stat sb;
if (stat(DirPathName, &sb) == 0 && S_ISDIR(sb.st_mode)) {
return GF_TRUE;
}
return GF_FALSE;
// DIR* dir = opendir(DirPathName);
// if (!dir) return GF_FALSE;
// closedir(dir);
// return GF_TRUE;
#endif
return GF_FALSE;
}
static Bool delete_dir(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info)
{
Bool directory_clean_mode = *(Bool*)cbck;
if(directory_clean_mode) {
gf_dir_cleanup(item_path);
gf_rmdir(item_path);
} else {
gf_file_delete(item_path);
}
return GF_FALSE;
}
GF_EXPORT
GF_Err gf_dir_cleanup(const char* DirPathName)
{
Bool directory_clean_mode;
directory_clean_mode = GF_TRUE;
gf_enum_directory(DirPathName, GF_TRUE, delete_dir, &directory_clean_mode, NULL);
directory_clean_mode = GF_FALSE;
gf_enum_directory(DirPathName, GF_FALSE, delete_dir, &directory_clean_mode, NULL);
return GF_OK;
}
#include <gpac/list.h>
GF_List *gfio_delete_handlers = NULL;
GF_EXPORT
GF_Err gf_fileio_register_delete_proc(gfio_delete_proc del_proc)
{
if (!del_proc) return GF_OK;
if (!gfio_delete_handlers) gfio_delete_handlers = gf_list_new();
if (!gfio_delete_handlers) return GF_OUT_OF_MEM;
if (gf_list_find(gfio_delete_handlers, del_proc)>=0) return GF_BAD_PARAM;
return gf_list_add(gfio_delete_handlers, del_proc);
}
GF_EXPORT
void gf_fileio_unregister_delete_proc(gfio_delete_proc del_proc)
{
if (!del_proc || !gfio_delete_handlers) return;
gf_list_del_item(gfio_delete_handlers, del_proc);
if (!gf_list_count(gfio_delete_handlers)) {
gf_list_del(gfio_delete_handlers);
gfio_delete_handlers = NULL;
}
}
GF_Err gf_fileio_file_delete(const char *fileName, const char *parent_gfio)
{
if (!gfio_delete_handlers || !fileName) return GF_OK;
if (!parent_gfio) {
if (strncmp(fileName, "gfio://", 7)) return GF_EOS;
} else {
if (strncmp(parent_gfio, "gfio://", 7)) return GF_EOS;
}
u32 i, count=gf_list_count(gfio_delete_handlers);
for (i=0; i<count; i++) {
gfio_delete_proc del_proc = gf_list_get(gfio_delete_handlers, i);
GF_Err ret = del_proc(fileName, parent_gfio);
if (ret==GF_EOS) continue;
return ret;
}
return GF_OK;
}
GF_EXPORT
GF_Err gf_file_delete(const char *fileName)
{
if (!fileName || !fileName[0]) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("gf_file_delete with no param - ignoring\n"));
return GF_OK;
}
if (!strncmp(fileName, "gfio://", 7)) {
return gf_fileio_file_delete(fileName, NULL);
}
#if defined(_WIN32_WCE)
TCHAR swzName[MAX_PATH];
CE_CharToWide((char*)fileName, swzName);
return (DeleteFile(swzName)==0) ? GF_IO_ERR : GF_OK;
#elif defined(WIN32)
/* success if != 0 */
{
BOOL op_result;
wchar_t* wcsFileName = gf_utf8_to_wcs(fileName);
if (!wcsFileName)
return GF_IO_ERR;
op_result = DeleteFileW(wcsFileName);
gf_free(wcsFileName);
return (op_result==0) ? GF_IO_ERR : GF_OK;
}
#else
/* success is == 0 */
return ( remove(fileName) == 0) ? GF_OK : GF_IO_ERR;
#endif
}
#ifndef WIN32
/**
* Remove existing single-quote from a single-quoted string.
* The caller is responsible for deallocating the returns string with gf_free()
*/
static char* gf_sanetize_single_quoted_string(const char *src) {
int i, j;
char *out = (char*)gf_malloc(4*strlen(src)+3);
out[0] = '\'';
for (i=0, j=1; (out[j]=src[i]); ++i, ++j) {
if (src[i]=='\'') {
out[++j]='\\';
out[++j]='\'';
out[++j]='\'';
}
}
out[j++] = '\'';
out[j++] = 0;
return out;
}
#endif
#if defined(GPAC_CONFIG_IOS) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 80000)
#include <spawn.h>
extern char **environ;
#endif
#if defined(WIN32)
#include <io.h>
#endif
GF_EXPORT
Bool gf_file_exists_ex(const char *fileName, const char *par_name)
{
u32 gfio_type = 0;
if (!fileName) return GF_FALSE;
if (!strncmp(fileName, "gfio://", 7))
gfio_type = 1;
else if (par_name && !strncmp(par_name, "gfio://", 7))
gfio_type = 2;
if (gfio_type) {
GF_FileIO *gfio_ref;
GF_Err e;
Bool res = GF_TRUE;
if (gfio_type==1)
gfio_ref = gf_fileio_from_url(fileName);
else
gfio_ref = gf_fileio_from_url(par_name);
if (!gfio_ref) return GF_FALSE;
gf_fileio_open_url(gfio_ref, (gfio_type==1) ? gf_fileio_resource_url(gfio_ref) : fileName, "probe", &e);
if (e==GF_URL_ERROR)
res = GF_FALSE;
return res;
}
#if defined(WIN32)
Bool res;
wchar_t* wname = gf_utf8_to_wcs(fileName);
if (!wname) return GF_FALSE;
res = (_waccess(wname, 4) == -1) ? GF_FALSE : GF_TRUE;
if (res == GF_TRUE) {
DWORD att;
att = GetFileAttributesW(wname);
if (att != INVALID_FILE_ATTRIBUTES && (att & FILE_ATTRIBUTE_DIRECTORY))
res = GF_FALSE;
}
gf_free(wname);
return res;
#elif defined(GPAC_CONFIG_LINUX) || defined(GPAC_CONFIG_EMSCRIPTEN)
Bool res = (access(fileName, 4) == -1) ? GF_FALSE : GF_TRUE;
if (res && gf_dir_exists(fileName))
res = GF_FALSE;
return res;
#elif defined(__DARWIN__) || defined(__APPLE__)
Bool res = (access(fileName, 4) == -1) ? GF_FALSE : GF_TRUE;
if (res && gf_dir_exists(fileName))
res = GF_FALSE;
return res;
#elif defined(GPAC_CONFIG_IOS) || defined(GPAC_CONFIG_ANDROID)
Bool res = (access(fileName, 4) == -1) ? GF_FALSE : GF_TRUE;
if (res && gf_dir_exists(fileName))
res = GF_FALSE;
return res;
#else
FILE *f = gf_fopen(fileName, "r");
if (f) {
gf_fclose(f);
return GF_TRUE;
}
return GF_FALSE;
#endif
}
GF_EXPORT
Bool gf_file_exists(const char *fileName)
{
return gf_file_exists_ex(fileName, NULL);
}
GF_EXPORT
GF_Err gf_file_move(const char *fileName, const char *newFileName)
{
#if defined(_WIN32_WCE)
GF_Err e = GF_OK;
TCHAR swzName[MAX_PATH];
TCHAR swzNewName[MAX_PATH];
CE_CharToWide((char*)fileName, swzName);
CE_CharToWide((char*)newFileName, swzNewName);
if (MoveFile(swzName, swzNewName) == 0 )
e = GF_IO_ERR;
#elif defined(WIN32)
GF_Err e = GF_OK;
/* success if != 0 */
BOOL op_result;
wchar_t* wcsFileName = gf_utf8_to_wcs(fileName);
wchar_t* wcsNewFileName = gf_utf8_to_wcs(newFileName);
if (!wcsFileName || !wcsNewFileName) {
if (wcsFileName) gf_free(wcsFileName);
if (wcsNewFileName) gf_free(wcsNewFileName);
e = GF_IO_ERR;
} else {
op_result = MoveFileW(wcsFileName, wcsNewFileName);
gf_free(wcsFileName);
gf_free(wcsNewFileName);
if ( op_result == 0 )
e = GF_IO_ERR;
}
#else
GF_Err e = GF_IO_ERR;
char cmd[1024], *arg1, *arg2;
if (!fileName || !newFileName) {
e = GF_IO_ERR;
} else {
//try direct rename - will fail if moving across file systems but faster for most use cases
int res = rename(fileName, newFileName);
if (!res) return GF_OK;
//use mv command
arg1 = gf_sanetize_single_quoted_string(fileName);
arg2 = gf_sanetize_single_quoted_string(newFileName);
if (snprintf(cmd, sizeof cmd, "mv %s %s", arg1, arg2) >= sizeof cmd) goto error;
#if defined(GPAC_CONFIG_IOS) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 80000)
{
pid_t pid;
char *argv[3];
argv[0] = "mv";
argv[1] = cmd;
argv[2] = NULL;
posix_spawn(&pid, argv[0], NULL, NULL, argv, environ);
waitpid(pid, NULL, 0);
}
#else
e = (system(cmd) == 0) ? GF_OK : GF_IO_ERR;
#endif
error:
gf_free(arg1);
gf_free(arg2);
}
#endif
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[core] Failed to move file %s to %s: %s\n", fileName, newFileName, gf_error_to_string(e) ));
}
return e;
}
GF_EXPORT
u64 gf_file_modification_time(const char *filename)
{
#if defined(_WIN32_WCE)
WCHAR _file[GF_MAX_PATH];
WIN32_FIND_DATA FindData;
HANDLE fh;
// ULARGE_INTEGER uli;
ULONGLONG time_us;
BOOL ret;
CE_CharToWide((char *) filename, _file);
fh = FindFirstFile(_file, &FindData);
if (fh == INVALID_HANDLE_VALUE) return 0;
// uli.LowPart = FindData.ftLastWriteTime.dwLowDateTime;
// uli.HighPart = FindData.ftLastWriteTime.dwHighDateTime;
time_us = (u64) ((*(LONGLONG *) &FindData.ftLastWriteTime - TIMESPEC_TO_FILETIME_OFFSET) / 10);
ret = FindClose(fh);
if (!ret) {
DWORD err = GetLastError();
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[core] FindClose() in gf_file_modification_time() error: %d\n", err));
}
// time_ms = uli.QuadPart/10000;
return time_us;
#elif defined(WIN32) && !defined(__GNUC__)
struct _stat64 st;
int op_result;
wchar_t* wcsFilename = gf_utf8_to_wcs(filename);
if (!wcsFilename)
return 0;
op_result = _wstat64(wcsFilename, &st);
gf_free(wcsFilename);
if (op_result != 0) return 0;
u64 time_us = st.st_mtime * 1000000;
#if defined(GPAC_HAS_MTIM_NSEC)
time_us += st.st_mtim.tv_nsec / 1000;
#endif
return time_us;
#else
struct stat st;
if (stat(filename, &st) != 0) return 0;
u64 time_us = st.st_mtime * 1000000;
#if defined(__DARWIN__) || defined(__APPLE__) || defined(GPAC_CONFIG_IOS)
time_us += st.st_mtimespec.tv_nsec / 1000;
#elif defined(GPAC_HAS_MTIM_NSEC)
time_us += st.st_mtim.tv_nsec / 1000;
#elif defined(GPAC_CONFIG_ANDROID)
time_us += st.st_mtime_nsec / 1000;
#endif
return time_us;
#endif
return 0;
}
#include <gpac/list.h>
extern int gf_mem_track_enabled;
static GF_List * gpac_open_files = NULL;
typedef struct
{
FILE *ptr;
char *url;
Bool is_temp;
} GF_FileHandle;
static u32 gpac_file_handles = 0;
GF_EXPORT
u32 gf_file_handles_count()
{
#ifdef GPAC_MEMORY_TRACKING
if (gpac_open_files) {
return gf_list_count(gpac_open_files);
}
#endif
return gpac_file_handles;
}
#include <gpac/thread.h>
extern GF_Mutex *logs_mx;
#ifdef GPAC_MEMORY_TRACKING
const char *enum_open_handles(u32 *idx)
{
GF_FileHandle *h;
gf_mx_p(logs_mx);
u32 count = gf_list_count(gpac_open_files);
if (*idx >= count) {
gf_mx_v(logs_mx);
return NULL;
}
h = gf_list_get(gpac_open_files, *idx);
(*idx)++;
gf_mx_v(logs_mx);
return h->url;
}
#endif
static void gf_register_file_handle(char *filename, FILE *ptr, Bool is_temp_file)
{
if (is_temp_file
#ifdef GPAC_MEMORY_TRACKING
|| gf_mem_track_enabled
#endif
) {
GF_FileHandle *h;
gf_mx_p(logs_mx);
if (!gpac_open_files) gpac_open_files = gf_list_new();
GF_SAFEALLOC(h, GF_FileHandle);
if (h) {
h->ptr = ptr;
if (is_temp_file) {
h->is_temp = GF_TRUE;
h->url = filename;
} else {
h->url = gf_strdup(filename);
}
gf_list_add(gpac_open_files, h);
}
gf_mx_v(logs_mx);
}
gpac_file_handles++;
}
static Bool gf_unregister_file_handle(FILE *ptr)
{
u32 i, count;
Bool res = GF_FALSE;
if (gpac_file_handles)
gpac_file_handles--;
if (!gpac_open_files)
return GF_FALSE;
gf_mx_p(logs_mx);
count = gf_list_count(gpac_open_files);
for (i=0; i<count; i++) {
GF_FileHandle *h = gf_list_get(gpac_open_files, i);
if (h->ptr != ptr) continue;
if (h->is_temp) {
GF_Err e;
fclose(h->ptr);
e = gf_file_delete(h->url);
if (e) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("[Core] Failed to delete temp file %s: %s\n", h->url, gf_error_to_string(e)));
}
res = GF_TRUE;
}
gf_free(h->url);
gf_free(h);
gf_list_rem(gpac_open_files, i);
if (!gf_list_count(gpac_open_files)) {
gf_list_del(gpac_open_files);
gpac_open_files = NULL;
}
break;
}
gf_mx_v(logs_mx);
return res;
}
static FILE *gf_file_temp_os(char ** const fileName)
{
FILE *res;
#if defined(_WIN32_WCE)
TCHAR pPath[MAX_PATH+1];
TCHAR pTemp[MAX_PATH+1];
res = NULL;
if (!GetTempPath(MAX_PATH, pPath)) {
pPath[0] = '.';
pPath[1] = '.';
}
if (GetTempFileName(pPath, TEXT("git"), 0, pTemp))
res = _wfopen(pTemp, TEXT("w+b"));
#elif defined(WIN32)
res = tmpfile();
if (!res) {
wchar_t tmp[MAX_PATH];
GF_LOG(GF_LOG_INFO, GF_LOG_CORE, ("[Win32] system failure for tmpfile(): 0x%08x\n", GetLastError()));
/*tmpfile() may fail under vista ...*/
if (GetEnvironmentVariableW(L"TEMP", tmp, MAX_PATH)) {
wchar_t tmp2[MAX_PATH], *t_file;
char* mbs_t_file;
gf_rand_init(GF_FALSE);
swprintf(tmp2, MAX_PATH, L"gpac_%d_%08x_", _getpid(), gf_rand());
t_file = _wtempnam(tmp, tmp2);
mbs_t_file = gf_wcs_to_utf8(t_file);
if (!mbs_t_file)
return 0;
res = gf_fopen(mbs_t_file, "w+b");
if (res) {
gf_unregister_file_handle(res);
if (fileName) {
*fileName = gf_strdup(mbs_t_file);
} else {
GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("[Win32] temporary file \"%s\" won't be deleted - contact the GPAC team\n", mbs_t_file));
}
}
gf_free(mbs_t_file);
free(t_file);
}
}
#else
res = tmpfile();
#endif
if (res) {
gf_register_file_handle("temp file", res, GF_FALSE);
}
return res;
}
GF_EXPORT
FILE *gf_file_temp(char ** const fileName)
{
const char *tmp_dir = gf_opts_get_key("core", "tmp");
if (tmp_dir && tmp_dir[0]) {
FILE *res;
char *opath=NULL, szFName[100];
u32 len = (u32) strlen(tmp_dir);
gf_dynstrcat(&opath, tmp_dir, NULL);
if (!strchr("/\\", tmp_dir[len-1]))
gf_dynstrcat(&opath, "/", NULL);
if (!opath) return NULL;
sprintf(szFName, "_libgpac_%d_%p_" LLU "_%d", gf_sys_get_process_id(), opath, gf_sys_clock_high_res(), gf_rand() );
gf_dynstrcat(&opath, szFName, NULL);
if (fileName) {
sprintf(szFName, "%p", fileName);
gf_dynstrcat(&opath, szFName, "_");
}
if (gf_file_exists(opath)) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Core] Something went wrong creating temp file path %s, file already exists !\n", opath));
gf_free(opath);
return NULL;
}
GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[Core] Opening new temp file %s\n", opath));
res = gf_fopen_ex(opath, "__temp_file", "w+b", GF_FALSE);
if (!res) {
gf_free(opath);
return NULL;
}
gf_register_file_handle(opath, res, GF_TRUE);
return res;
}
return gf_file_temp_os(fileName);
}
/*enumerate directories*/
GF_EXPORT
GF_Err gf_enum_directory(const char *dir, Bool enum_directory, gf_enum_dir_item enum_dir_fct, void *cbck, const char *filter)
{
#ifdef WIN32
wchar_t item_path[GF_MAX_PATH];
#else
char item_path[GF_MAX_PATH];
#endif
GF_FileEnumInfo file_info;
#if defined(_WIN32_WCE)
char _path[GF_MAX_PATH];
unsigned short path[GF_MAX_PATH];
unsigned short w_filter[GF_MAX_PATH];
char file[GF_MAX_PATH];
#elif defined(WIN32)
wchar_t path[GF_MAX_PATH], *file;
wchar_t w_filter[GF_MAX_PATH];
char *mbs_file, *mbs_item_path;
char _path[GF_MAX_PATH];
const char* tmpdir;
#else
char path[GF_MAX_PATH], *file;
#endif
#ifdef WIN32
WIN32_FIND_DATAW FindData;
HANDLE SearchH;
#else
DIR *the_dir;
struct dirent* the_file;
struct stat st;
#endif
if (!dir || !strlen(dir) || !enum_dir_fct) return GF_BAD_PARAM;
if (filter && (!strcmp(filter, "*") || !filter[0])) filter=NULL;
memset(&file_info, 0, sizeof(GF_FileEnumInfo) );
if (!strcmp(dir, "/")) {
#if defined(WIN32) && !defined(_WIN32_WCE)
u32 len;
char *drives, *volume;
len = GetLogicalDriveStrings(0, NULL);
drives = (char*)gf_malloc(sizeof(char)*(len+1));
drives[0]=0;
GetLogicalDriveStrings(len, drives);
len = (u32) strlen(drives);
volume = drives;
file_info.directory = GF_TRUE;
file_info.drive = GF_TRUE;
while (len) {
enum_dir_fct(cbck, volume, "", &file_info);
volume += len+1;
len = (u32) strlen(volume);
}
gf_free(drives);
return GF_OK;
#elif defined(__SYMBIAN32__)
RFs iFs;
TDriveList aList;
iFs.Connect();
iFs.DriveList(aList);
for (TInt i=0; i<KMaxDrives; i++) {
if (aList[i]) {
char szDrive[10];
TChar aDrive;
iFs.DriveToChar(i, aDrive);
sprintf(szDrive, "%c:", (TUint)aDrive);
enum_dir_fct(cbck, szDrive, "", &file_info);
}
}
iFs.Close();
FlushItemList();
return GF_OK;
#elif defined(GPAC_CONFIG_ANDROID) || defined(GPAC_CONFIG_IOS)
dir = (char *) gf_opts_get_key("core", "docs-dir");
#endif
}
#if defined (_WIN32_WCE)
switch (dir[strlen(dir) - 1]) {
case '/':
case '\\':
sprintf(_path, "%s*", dir);
break;
default:
sprintf(_path, "%s%c*", dir, GF_PATH_SEPARATOR);
break;
}
CE_CharToWide(_path, path);
CE_CharToWide((char *)filter, w_filter);
#elif defined(WIN32)
gf_strcpy(_path, dir);
switch (dir[strlen(dir)] - 1) {
case '/':
case '\\':
snprintf(_path, MAX_PATH, "%s*", dir);
break;
default:
snprintf(_path, MAX_PATH, "%s%c*", dir, GF_PATH_SEPARATOR);
break;
}
tmpdir = _path;
if (gf_utf8_mbstowcs(path, GF_MAX_PATH, &tmpdir) == GF_UTF8_FAIL) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Core] Cannot convert %s to UTF16: broken string\n", dir));
return GF_BAD_PARAM;
}
tmpdir = filter;
if (gf_utf8_mbstowcs(w_filter, sizeof(w_filter), &tmpdir) == GF_UTF8_FAIL) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Core] Cannot convert %s to UTF16: broken string\n", filter));
return GF_BAD_PARAM;
}
#else
size_t dir_len = strlen(dir);
if (dir_len < GF_ARRAY_LENGTH(path)) {
gf_strcpy(path, dir);
}
else {
memcpy(path, dir, GF_ARRAY_LENGTH(path));
path[ GF_ARRAY_LENGTH(path) - 1] = 0;
}
size_t path_len = strlen(path);
if (path_len && path[path_len-1] != '/') {
if (path_len + 1 >= GF_ARRAY_LENGTH(path))
return GF_BAD_PARAM;
gf_strcat(path, "/");
}
#endif
#ifdef WIN32
SearchH= FindFirstFileW(path, &FindData);
if (SearchH == INVALID_HANDLE_VALUE) return GF_IO_ERR;
#if defined (_WIN32_WCE)
_path[strlen(_path) ? strlen(_path)-1:0] = 0;
#else
size_t path_len = wcslen(path);
path[ path_len ? path_len-1 : 0] = 0;
#endif
while (SearchH != INVALID_HANDLE_VALUE) {
#else
the_dir = opendir(path);
if (the_dir == NULL) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Core] Cannot open directory \"%s\" for enumeration: %d\n", path, errno));
return GF_IO_ERR;
}
the_file = readdir(the_dir);
while (the_file) {
#endif
memset(&file_info, 0, sizeof(GF_FileEnumInfo) );
#if defined (_WIN32_WCE)
if (!wcscmp(FindData.cFileName, _T(".") )) goto next;
if (!wcscmp(FindData.cFileName, _T("..") )) goto next;
#elif defined(WIN32)
if (!wcscmp(FindData.cFileName, L".")) goto next;
if (!wcscmp(FindData.cFileName, L"..")) goto next;
#else
if (!strcmp(the_file->d_name, "..")) goto next;
if (the_file->d_name[0] == '.') goto next;
#endif
#ifdef WIN32
file_info.directory = (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? GF_TRUE : GF_FALSE;
if (!enum_directory && file_info.directory) goto next;
if (enum_directory && !file_info.directory) goto next;
#endif
if (filter) {
#if defined (_WIN32_WCE)
short ext[30];
short *sep = wcsrchr(FindData.cFileName, (wchar_t) '.');
short *found_ext;
u32 ext_len;
if (!sep) goto next;
wcscpy(ext, sep+1);
wcslwr(ext);
ext_len = (u32) wcslen(ext);
found_ext = wcsstr(w_filter, ext);
if (!found_ext) goto next;
if (found_ext[ext_len] && (found_ext[ext_len] != (wchar_t) ';')) goto next;
#elif defined(WIN32)
wchar_t ext[30];
wchar_t *sep = wcsrchr(FindData.cFileName, L'.');
wchar_t *found_ext;
u32 ext_len;
if (!sep) goto next;
wcscpy(ext, sep+1);
wcslwr(ext);
ext_len = (u32) wcslen(ext);
found_ext = wcsstr(w_filter, ext);
if (!found_ext) goto next;
if (found_ext[ext_len] && (found_ext[ext_len] != L';')) goto next;
#else
char ext[30];
char *sep = strrchr(the_file->d_name, '.');
char *found_ext;
u32 ext_len;
if (!sep) goto next;
gf_strcpy(ext, sep+1);
strlwr(ext);
ext_len = (u32) strlen(ext);
found_ext = strstr(filter, ext);
if (!found_ext) goto next;
if (found_ext[ext_len] && (found_ext[ext_len]!=';')) goto next;
#endif
}
#if defined(WIN32)
file_info.hidden = (FindData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) ? GF_TRUE : GF_FALSE;
file_info.system = (FindData.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) ? GF_TRUE : GF_FALSE;
file_info.size = MAXDWORD;
file_info.size += 1;
file_info.size *= FindData.nFileSizeHigh;
file_info.size += FindData.nFileSizeLow;
file_info.last_modified = (u64) ((*(LONGLONG *) &FindData.ftLastWriteTime - TIMESPEC_TO_FILETIME_OFFSET) / 10);
#endif
#if defined (_WIN32_WCE)
CE_WideToChar(FindData.cFileName, file);
gf_strcpy(item_path, _path);
gf_strcat(item_path, file);
#elif defined(WIN32)
wcscpy(item_path, path);
wcscat(item_path, FindData.cFileName);
file = FindData.cFileName;
#else
gf_strcpy(item_path, path);
gf_strcat(item_path, the_file->d_name);
GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[Core] Checking file \"%s\" for enum\n", item_path));
if (stat( item_path, &st ) != 0) goto next;
file_info.directory = ((st.st_mode & S_IFMT) == S_IFDIR) ? GF_TRUE : GF_FALSE;
if (enum_directory && !file_info.directory) goto next;
if (!enum_directory && file_info.directory) goto next;
file_info.size = st.st_size;
struct tm _t = * gf_gmtime(& st.st_mtime);
file_info.last_modified = mktime(&_t);
file_info.last_modified *= 1000000;
#if defined(__DARWIN__) || defined(__APPLE__) || defined(GPAC_CONFIG_IOS)
file_info.last_modified += st.st_mtimespec.tv_nsec / 1000;
#elif defined(GPAC_HAS_MTIM_NSEC)
file_info.last_modified += st.st_mtim.tv_nsec / 1000;
#elif defined(GPAC_CONFIG_ANDROID)
file_info.last_modified += st.st_mtime_nsec / 1000;
#endif
file = the_file->d_name;
if (file && file[0]=='.') file_info.hidden = GF_TRUE;
if (file_info.directory) {
char * parent_name = strrchr(item_path, '/');
if (!parent_name) {
file_info.drive = GF_TRUE;
} else {
struct stat st_parent;
parent_name[0] = 0;
if (stat(item_path, &st_parent) == 0) {
if ((st.st_dev != st_parent.st_dev) || (st.st_ino == st_parent.st_ino) ) {
file_info.drive = GF_TRUE;
}
}
parent_name[0] = '/';
}
}
#endif
Bool done = GF_FALSE;
#ifdef WIN32
mbs_file = gf_wcs_to_utf8(file);
mbs_item_path = gf_wcs_to_utf8(item_path);
if (!mbs_file || !mbs_item_path)
{
if (mbs_file) gf_free(mbs_file);
if (mbs_item_path) gf_free(mbs_item_path);
return GF_IO_ERR;
}
if (enum_dir_fct(cbck, mbs_file, mbs_item_path, &file_info)) {
BOOL ret = FindClose(SearchH);
if (!ret) {
DWORD err = GetLastError();
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[core] FindClose() in gf_enum_directory() returned(1) the following error code: %d\n", err));
}
#else
if (enum_dir_fct(cbck, file, item_path, &file_info)) {
#endif
done = GF_TRUE;
}
#ifdef WIN32
gf_free(mbs_file);
gf_free(mbs_item_path);
#endif
if (done) break;
next:
#ifdef WIN32
if (!FindNextFileW(SearchH, &FindData)) {
BOOL ret = FindClose(SearchH);
if (!ret) {
DWORD err = GetLastError();
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[core] FindClose() in gf_enum_directory() returned(2) the following error code: %d\n", err));
}
break;
}
#else
the_file = readdir(the_dir);
#endif
}
#ifndef WIN32
closedir(the_dir);
#endif
return GF_OK;
}
struct __gf_file_io
{
u32 _reserved_null;
void *__this;
GF_FileIO * (*open)(GF_FileIO *fileio_ref, const char *url, const char *mode, GF_Err *out_error);
GF_Err (*seek)(GF_FileIO *fileio, u64 offset, s32 whence);
u32 (*read)(GF_FileIO *fileio, u8 *buffer, u32 bytes);
u32 (*write)(GF_FileIO *fileio, u8 *buffer, u32 bytes);
s64 (*tell)(GF_FileIO *fileio);
Bool (*eof)(GF_FileIO *fileio);
int (*printf)(GF_FileIO *gfio, const char *format, va_list args);
char *(*gets)(GF_FileIO *gfio, char *ptr, u32 size);
char *url;
char *res_url;
void *udta;
u32 creation_time;
u64 bytes_done, file_size_plus_one;
Bool main_th;
GF_FileIOCacheState cache_state;
u32 bytes_per_sec;
u32 write_state;
u32 printf_alloc;
u8* printf_buf;
};
GF_EXPORT
Bool gf_fileio_check(FILE *fp)
{
GF_FileIO *fio = (GF_FileIO *)fp;
if ((fp==stdin) || (fp==stderr) || (fp==stdout))
return GF_FALSE;
if (fio && !fio->_reserved_null && (fio->__this==fio))
return GF_TRUE;
return GF_FALSE;
}
GF_EXPORT
GF_FileIOWriteState gf_fileio_write_ready(FILE *fp)
{
GF_FileIO *fio = (GF_FileIO *)fp;
if ((fp==stdin) || (fp==stderr) || (fp==stdout))
return GF_FIO_WRITE_READY;
//GFIO, check state
if (fio && !fio->_reserved_null && (fio->__this==fio)) {
return fio->write_state;
}
//sync stdio
return GF_FIO_WRITE_READY;
}
u32 gf_fileio_get_write_rate(FILE *fp)
{
GF_FileIO *fio = (GF_FileIO *)fp;
if ((fp==stdin) || (fp==stderr) || (fp==stdout))
return 0;
if (fio && !fio->_reserved_null && (fio->__this==fio)) {
return fio->bytes_per_sec;
}
//sync stdio
return 0;
}
typedef struct
{
u8 *data;
u32 size;
u32 pos;
u32 url_crc;
u32 nb_ref;
char *URL;
Bool sub_open;
} GF_FileIOBlob;
static GF_FileIO *gfio_blob_open(GF_FileIO *fileio_ref, const char *url, const char *mode, GF_Err *out_error)
{
GF_FileIOBlob *blob = gf_fileio_get_udta(fileio_ref);
if (!strcmp(mode, "close") || !strcmp(mode, "unref")) {
if (blob->nb_ref) blob->nb_ref--;
blob->pos = 0;
if (blob->nb_ref)
return NULL;
if (blob->sub_open)
gf_unregister_file_handle((FILE *)fileio_ref);
if (blob->URL) gf_free(blob->URL);
gf_free(blob);
gf_fileio_del(fileio_ref);
return NULL;
}
if (!strcmp(mode, "ref")) {
blob->nb_ref++;
*out_error = GF_OK;
return NULL;
}
if (!strcmp(mode, "url")) {
*out_error = GF_BAD_PARAM;
return NULL;
}
if (!strcmp(mode, "probe")) {
u32 crc = gf_crc_32(url, (u32) strlen(url) );
*out_error = (crc==blob->url_crc) ? GF_OK : GF_URL_ERROR;
return NULL;
}
if (mode[0]!='r') {
*out_error = GF_BAD_PARAM;
return NULL;
}
//no user, we can use this object directly
if (!blob->nb_ref) {
blob->nb_ref = 1;
*out_error = GF_OK;
return fileio_ref;
}
//if already in use, we must allocate a new gfio otherwise read position would get shared across FILE streams !
fileio_ref = gf_fileio_from_mem(blob->URL, blob->data, blob->size);
blob = gf_fileio_get_udta(fileio_ref);
//remember we forced a new gfio, it will have to be unregistered upon close
blob->sub_open = GF_TRUE;
*out_error = fileio_ref ? GF_OK : GF_IO_ERR;
return fileio_ref;
}
static GF_Err gfio_blob_seek(GF_FileIO *fileio, u64 offset, s32 whence)
{
GF_FileIOBlob *blob = gf_fileio_get_udta(fileio);
if (whence==SEEK_END)
blob->pos = blob->size;
else if (whence==SEEK_SET) {
if ((s64)offset<0)
return GF_BAD_PARAM;
if ((u32)offset >= blob->size)
return GF_BAD_PARAM;
blob->pos = (u32) offset;
} else {
if (!offset)
return GF_OK;
if ((s64)offset<0)
return GF_BAD_PARAM;
if (blob->pos + offset > blob->size)
return GF_BAD_PARAM;
blob->pos += (u32) offset;
}
return GF_OK;
}
static u32 gfio_blob_read(GF_FileIO *fileio, u8 *buffer, u32 bytes)
{
GF_FileIOBlob *blob = gf_fileio_get_udta(fileio);
if (bytes + blob->pos > blob->size)
bytes = blob->size - blob->pos;
if (bytes) {
memcpy(buffer, blob->data+blob->pos, bytes);
blob->pos += bytes;
assert(blob->pos<=blob->size);
}
return bytes;
}
static s64 gfio_blob_tell(GF_FileIO *fileio)
{
GF_FileIOBlob *blob = gf_fileio_get_udta(fileio);
return (s64) blob->pos;
}
static Bool gfio_blob_eof(GF_FileIO *fileio)
{
GF_FileIOBlob *blob = gf_fileio_get_udta(fileio);
if (blob->pos==blob->size) return GF_TRUE;
return GF_FALSE;
}
static char *gfio_blob_gets(GF_FileIO *fileio, char *ptr, u32 size)
{
GF_FileIOBlob *blob = gf_fileio_get_udta(fileio);
char *buf = blob->data + blob->pos;
u32 len = blob->size - blob->pos;
if (!len) return NULL;
char *next = memchr(buf, '\n', len);
if (next) {
len = (u32) (next - buf);
if (len + blob->pos<blob->size) len++;
}
if (len + 1 > size) len = size - 1;
memcpy(ptr, blob->data+blob->pos, len);
blob->pos += len;
ptr[len] = 0;
return ptr;
}
GF_List *allocated_gfios = NULL;
GF_EXPORT
GF_FileIO *gf_fileio_new(char *url, void *udta,
gfio_open_proc open,
gfio_seek_proc seek,
gfio_read_proc read,
gfio_write_proc write,
gfio_tell_proc tell,
gfio_eof_proc eof,
gfio_printf_proc printf)
{
char szURL[100];
GF_FileIO *tmp;
if (!open || !seek || !tell || !eof) return NULL;
if (!write && !read) return NULL;
GF_SAFEALLOC(tmp, GF_FileIO);
if (!tmp) return NULL;
tmp->open = open;
tmp->seek = seek;
tmp->write = write;
tmp->read = read;
tmp->tell = tell;
tmp->eof = eof;
tmp->printf = printf;
tmp->udta = udta;
if (url)
tmp->res_url = gf_strdup(url);
//add clock as cookie to avoid creating twice th same gfio url in case the above malloc returns
//the same adress as a previously allocated gfio
tmp->creation_time = gf_sys_clock();
sprintf(szURL, "gfio://%p&%u", tmp, tmp->creation_time);
tmp->url = gf_strdup(szURL);
tmp->__this = tmp;
//track all allocated gfios so that we don't attempt accessing a freed one !
gf_mx_p(logs_mx);
if (!allocated_gfios) allocated_gfios = gf_list_new();
gf_list_add(allocated_gfios, tmp);
gf_mx_v(logs_mx);
return tmp;
}
GF_EXPORT
const char * gf_fileio_url(GF_FileIO *gfio)
{
return gfio ? gfio->url : NULL;
}
GF_EXPORT
const char * gf_fileio_resource_url(GF_FileIO *gfio)
{
return gfio ? gfio->res_url : NULL;
}
GF_EXPORT
void gf_fileio_del(GF_FileIO *gfio)
{
if (!gfio) return;
gf_mx_p(logs_mx);
if (gf_list_del_item(allocated_gfios, gfio)<0) {
gf_mx_v(logs_mx);
return;
}
if (!gf_list_count(allocated_gfios)) {
gf_list_del(allocated_gfios);
allocated_gfios = NULL;
}
gf_mx_v(logs_mx);
if (gfio->url) gf_free(gfio->url);
if (gfio->res_url) gf_free(gfio->res_url);
if (gfio->printf_buf) gf_free(gfio->printf_buf);
gf_free(gfio);
}
GF_EXPORT
void *gf_fileio_get_udta(GF_FileIO *gfio)
{
return gfio ? gfio->udta : NULL;
}
GF_EXPORT
GF_FileIO *gf_fileio_open_url(GF_FileIO *gfio_ref, const char *url, const char *mode, GF_Err *out_err)
{
if (!gfio_ref) {
*out_err = GF_BAD_PARAM;
return NULL;
}
if (!gfio_ref->open) {
*out_err = url ? GF_NOT_SUPPORTED : GF_OK;
return NULL;
}
return gfio_ref->open(gfio_ref, url, mode, out_err);
}
static u32 gf_fileio_read(GF_FileIO *gfio, u8 *buffer, u32 nb_bytes)
{
if (!gfio) return GF_BAD_PARAM;
if (gfio->read)
return gfio->read(gfio, buffer, nb_bytes);
return 0;
}
static u32 gf_fileio_write(GF_FileIO *gfio, u8 *buffer, u32 nb_bytes)
{
if (!gfio) return GF_BAD_PARAM;
if (!gfio->write) return 0;
return gfio->write(gfio, buffer, (u32) nb_bytes);
}
int gf_fileio_printf(GF_FileIO *gfio, const char *format, va_list args)
{
va_list args_copy;
if (!gfio) return -1;
if (gfio->printf) return gfio->printf(gfio, format, args);
if (!gfio->write) return -1;
va_copy(args_copy, args);
u32 len=vsnprintf(NULL, 0, format, args_copy);
va_end(args_copy);
if (len>=gfio->printf_alloc) {
gfio->printf_alloc = len+1;
gfio->printf_buf = gf_realloc(gfio->printf_buf, gfio->printf_alloc);
}
vsnprintf(gfio->printf_buf, len, format, args);
gfio->printf_buf[len] = 0;
return gfio->write(gfio, gfio->printf_buf, len+1);
}
GF_EXPORT
Bool gf_fileio_write_mode(GF_FileIO *gfio)
{
return (gfio && gfio->write) ? GF_TRUE : GF_FALSE;
}
GF_EXPORT
Bool gf_fileio_read_mode(GF_FileIO *gfio)
{
return (gfio && gfio->read) ? GF_TRUE : GF_FALSE;
}
GF_EXPORT
GF_FileIO *gf_fileio_from_url(const char *url)
{
char szURL[100];
u32 cookie=0;
GF_FileIO *ptr=NULL;
if (!url) return NULL;
if (strncmp(url, "gfio://", 7)) return NULL;
sscanf(url, "gfio://%p&%u", &ptr, &cookie);
sprintf(szURL, "gfio://%p&%u", ptr, cookie);
if (strcmp(url, szURL))
return NULL;
gf_mx_p(logs_mx);
if (gf_list_find(allocated_gfios, ptr)<0) {
gf_mx_v(logs_mx);
return NULL;
}
gf_mx_v(logs_mx);
if (ptr && ptr->url && !strcmp(ptr->url, url) && (ptr->creation_time==cookie) ) {
return ptr;
}
return NULL;
}
GF_EXPORT
const char * gf_fileio_translate_url(const char *url)
{
GF_FileIO *gfio = gf_fileio_from_url(url);
return gfio ? gfio->res_url : NULL;
}
Bool gf_fileio_eof(GF_FileIO *fileio)
{
if (!fileio || !fileio->eof) return GF_TRUE;
return fileio->eof(fileio);
}
int gf_fileio_flush(GF_FileIO *fileio)
{
if (!fileio || !fileio->write) return 0;
fileio->write(fileio, NULL, 0);
return 0;
}
GF_EXPORT
const char *gf_fileio_factory(GF_FileIO *gfio, const char *new_res_url)
{
GF_Err e;
if (!gfio || !gfio->open) return NULL;
GF_FileIO *new_res = gfio->open(gfio, new_res_url, "url", &e);
if (e) return NULL;
return gf_fileio_url(new_res);
}
GF_EXPORT
void gf_fileio_set_stats(GF_FileIO *gfio, u64 bytes_done, u64 file_size, GF_FileIOCacheState cache_state, u32 bytes_per_sec)
{
if (!gfio) return;
gfio->bytes_done = bytes_done;
gfio->file_size_plus_one = file_size ? (1 + file_size) : 0;
gfio->cache_state = cache_state;
gfio->bytes_per_sec = bytes_per_sec;
}
GF_EXPORT
void gf_fileio_set_write_state(GF_FileIO *gfio, GF_FileIOWriteState write_state)
{
if (!gfio) return;
gfio->write_state = write_state;
}
GF_EXPORT
Bool gf_fileio_get_stats(GF_FileIO *gfio, u64 *bytes_done, u64 *file_size, GF_FileIOCacheState *cache_state, u32 *bytes_per_sec)
{
if (!gf_fileio_check((FILE *)gfio))
return GF_FALSE;
if (bytes_done) *bytes_done = gfio->bytes_done;
if (file_size) *file_size = gfio->file_size_plus_one ? gfio->file_size_plus_one-1 : 0;
if (cache_state) *cache_state = gfio->cache_state;
if (bytes_per_sec) *bytes_per_sec = gfio->bytes_per_sec;
return GF_TRUE;
}
GF_EXPORT
GF_Err gf_fileio_tag_main_thread(GF_FileIO *fileio)
{
if (!fileio) return GF_BAD_PARAM;
fileio->main_th = GF_TRUE;
return GF_OK;
}
GF_EXPORT
Bool gf_fileio_is_main_thread(const char *url)
{
GF_FileIO *gfio = gf_fileio_from_url(url);
if (!gfio) return GF_FALSE;
return gfio->main_th;
}
GF_EXPORT
u64 gf_ftell(FILE *fp)
{
if (!fp) return -1;
if (gf_fileio_check(fp)) {
GF_FileIO *gfio = (GF_FileIO *)fp;
if (!gfio->tell) return -1;
return gfio->tell(gfio);
}
#if defined(_WIN32_WCE)
return (u64) ftell(fp);
#elif defined(GPAC_CONFIG_WIN32) && (defined(__CYGWIN__) || defined(__MINGW32__))
#if (_FILE_OFFSET_BITS >= 64)
return (u64) ftello64(fp);
#else
return (u64) ftell(fp);
#endif
#elif defined(WIN32)
return (u64) _ftelli64(fp);
#elif defined(GPAC_CONFIG_LINUX) && !defined(GPAC_CONFIG_ANDROID)
return (u64) ftello64(fp);
#elif (defined(GPAC_CONFIG_FREEBSD) || defined(GPAC_CONFIG_DARWIN))
return (u64) ftello(fp);
#else
return (u64) ftell(fp);
#endif
}
GF_EXPORT
s32 gf_fseek(FILE *fp, s64 offset, s32 whence)
{
if (!fp) return -1;
if (gf_fileio_check(fp)) {
GF_FileIO *gfio = (GF_FileIO *)fp;
if (gfio->seek) {
GF_Err e = gfio->seek(gfio, offset, whence);
if (e) return -1;
return 0;
}
return -1;
}
#if defined(_WIN32_WCE)
return (u64) fseek(fp, (s32) offset, whence);
#elif defined(GPAC_CONFIG_WIN32) && defined(__CYGWIN__) /* mingw or cygwin */
#if (_FILE_OFFSET_BITS >= 64)
return (u64) fseeko64(fp, offset, whence);
#else
return (u64) fseek(fp, (s32) offset, whence);
#endif
#elif defined(WIN32)
return (u64) _fseeki64(fp, offset, whence);
#elif defined(GPAC_CONFIG_LINUX) && !defined(GPAC_CONFIG_ANDROID)
return fseeko64(fp, (off64_t) offset, whence);
#elif (defined(GPAC_CONFIG_FREEBSD) || defined(GPAC_CONFIG_DARWIN))
return fseeko(fp, (off_t) offset, whence);
#else
return fseek(fp, (s32) offset, whence);
#endif
}
static GF_FileIO *gf_fileio_from_blob(const char *file_name)
{
u8 *blob_data;
u32 blob_size, flags;
GF_FileIOBlob *gfio_blob;
GF_Err e = gf_blob_get(file_name, &blob_data, &blob_size, &flags);
if (e || !blob_data) return NULL;
gf_blob_release(file_name);
if (flags) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Core] Attempt at creating a GFIO object on blob corrupted or in transfer, not supported !"));
return NULL;
}
GF_SAFEALLOC(gfio_blob, GF_FileIOBlob);
if (!gfio_blob) return NULL;
gfio_blob->data = blob_data;
gfio_blob->size = blob_size;
GF_FileIO *res = gf_fileio_new((char *) file_name, gfio_blob, gfio_blob_open, gfio_blob_seek, gfio_blob_read, NULL, gfio_blob_tell, gfio_blob_eof, NULL);
if (!res) {
gf_free(gfio_blob);
return NULL;
}
res->gets = gfio_blob_gets;
if (file_name)
gfio_blob->url_crc = gf_crc_32(file_name, (u32) strlen(file_name) );
return res;
}
GF_EXPORT
GF_FileIO *gf_fileio_from_mem(const char *URL, const u8 *data, u32 size)
{
GF_FileIOBlob *gfio_blob;
GF_SAFEALLOC(gfio_blob, GF_FileIOBlob);
if (!gfio_blob) return NULL;
gfio_blob->data = (u8 *) data;
gfio_blob->size = size;
gfio_blob->URL = gf_strdup(URL);
GF_FileIO *res = gf_fileio_new((char *) URL, gfio_blob, gfio_blob_open, gfio_blob_seek, gfio_blob_read, NULL, gfio_blob_tell, gfio_blob_eof, NULL);
if (!res) {
gf_free(gfio_blob);
return NULL;
}
res->gets = gfio_blob_gets;
if (URL)
gfio_blob->url_crc = gf_crc_32(URL, (u32) strlen(URL) );
gf_fopen(gf_fileio_url(res), "r");
return res;
}
#ifdef GPAC_CONFIG_EMSCRIPTEN
static u32 mainloop_th_id = 0;
void gf_set_mainloop_thread(u32 thread_id)
{
mainloop_th_id = thread_id;
}
#endif
GF_EXPORT
FILE *gf_fopen_ex(const char *file_name, const char *parent_name, const char *mode, Bool no_warn)
{
FILE *res = NULL;
u32 gfio_type = 0;
Bool is_mkdir = GF_FALSE;
if (!file_name || !mode) return NULL;
if (!strcmp(mode, "mkdir")) {
is_mkdir = GF_TRUE;
mode = "w";
}
if (!strncmp(file_name, "gmem://", 7)) {
GF_FileIO *new_gfio;
if (strstr(mode, "w"))
return NULL;
new_gfio = gf_fileio_from_blob(file_name);
if (new_gfio)
gf_register_file_handle((char*)file_name, (FILE *) new_gfio, GF_FALSE);
return (FILE *) new_gfio;
}
if (!strncmp(file_name, "gfio://", 7))
gfio_type = 1;
else if (parent_name && !strncmp(parent_name, "gfio://", 7))
gfio_type = 2;
if (gfio_type) {
GF_FileIO *gfio_ref;
GF_FileIO *new_gfio;
GF_Err e;
GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[Core] Open GFIO %s in mode %s\n", file_name, mode));
if (gfio_type==1)
gfio_ref = gf_fileio_from_url(file_name);
else
gfio_ref = gf_fileio_from_url(parent_name);
if (!gfio_ref) return NULL;
if (strchr(mode, 'r') && !gf_fileio_read_mode(gfio_ref)) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("FileIO %s is not read-enabled and open mode %s was requested\n", file_name, mode));
return NULL;
}
if (strpbrk(mode, "wa") && !gf_fileio_write_mode(gfio_ref)) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("FileIO %s is not write-enabled and open mode %s was requested\n", file_name, mode));
return NULL;
}
new_gfio = gf_fileio_open_url(gfio_ref, file_name, mode, &e);
if (e) {
if (!no_warn) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("FileIO %s open in mode %s failed: %s\n", file_name, mode, gf_error_to_string(e)));
}
return NULL;
}
if (new_gfio)
gf_register_file_handle((char*)file_name, (FILE *) new_gfio, GF_FALSE);
return (FILE *) new_gfio;
}
GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[Core] Open file %s in mode %s\n", file_name, mode));
if (strchr(mode, 'w')) {
char *fname = gf_strdup(file_name);
char *sep = strchr(fname, '/');
if (!sep) sep = strchr(fname, '\\');
if (file_name[0] == '/') sep = strchr(fname+1, '/');
else if (file_name[2] == '\\') sep = strchr(fname+3, '\\');
while (sep) {
char *n_sep;
char c = sep[0];
sep[0] = 0;
if (!gf_dir_exists(fname)) {
GF_Err e = gf_mkdir(fname);
if (e != GF_OK) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Core] Failed to create directory \"%s\": %s\n", file_name, gf_error_to_string(e) ));
sep[0] = c;
gf_free(fname);
return NULL;
}
}
sep[0] = c;
n_sep = strchr(sep+1, '/');
if (!n_sep) n_sep = strchr(sep+1, '\\');
sep = n_sep;
}
gf_free(fname);
if (is_mkdir) return NULL;
}
#ifdef GPAC_CONFIG_EMSCRIPTEN
if (mainloop_th_id && strchr(mode, 'r') && (gf_th_id() != mainloop_th_id)) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Core] Opening file in read mode outside of main loop will likely deadlock - please report to GPAC devs\n"));
}
#endif
#if defined(WIN32)
wchar_t *wname;
wchar_t *wmode;
wname = gf_utf8_to_wcs(file_name);
wmode = gf_utf8_to_wcs(mode);
if (!wname || !wmode)
{
if (wname) gf_free(wname);
if (wmode) gf_free(wmode);
return NULL;
}
res = _wfsopen(wname, wmode, _SH_DENYNO);
gf_free(wname);
gf_free(wmode);
#elif defined(GPAC_CONFIG_LINUX) && !defined(GPAC_CONFIG_ANDROID)
res = fopen64(file_name, mode);
#elif (defined(GPAC_CONFIG_FREEBSD) || defined(GPAC_CONFIG_DARWIN))
res = fopen(file_name, mode);
#else
res = fopen(file_name, mode);
#endif
if (res) {
if (!parent_name || strcmp(parent_name, "__temp_file"))
gf_register_file_handle((char*)file_name, res, GF_FALSE);
GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[Core] file \"%s\" opened in mode \"%s\" - %d file handles\n", file_name, mode, gpac_file_handles));
} else if (!no_warn) {
if (strpbrk(mode, "wa")) {
#if defined(WIN32)
u32 err = GetLastError();
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Core] system failure for file opening of \"%s\" in mode \"%s\": 0x%08x (%u opened)\n", file_name, mode, err, gpac_file_handles));
#else
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Core] system failure for file opening of \"%s\" in mode \"%s\": %d (%u opened)\n", file_name, mode, errno, gpac_file_handles));
#endif
}
}
return res;
}
GF_EXPORT
s32 gf_fd_open(const char *file_name, u32 oflags, u32 uflags)
{
if (!file_name) return -1;
#if defined(WIN32)
wchar_t *wname = gf_utf8_to_wcs(file_name);
if (!wname) return -1;
int res = _wopen(wname, oflags, uflags);
gf_free(wname);
return res;
#elif defined(GPAC_CONFIG_LINUX) && !defined(GPAC_CONFIG_ANDROID)
return open(file_name, oflags, uflags);
#elif (defined(GPAC_CONFIG_FREEBSD) || defined(GPAC_CONFIG_DARWIN))
return open(file_name, oflags, uflags);
#else
return open(file_name, oflags, uflags);
#endif
return -1;
}
GF_EXPORT
FILE *gf_fopen(const char *file_name, const char *mode)
{
return gf_fopen_ex(file_name, NULL, mode, GF_FALSE);
}
GF_EXPORT
s32 gf_fclose(FILE *file)
{
if (!file)
return 0;
if (gf_unregister_file_handle(file))
return 0;
if (gf_fileio_check(file)) {
GF_Err e;
gf_fileio_open_url((GF_FileIO *) file, NULL, "close", &e);
if (e) return -1;
return 0;
}
return fclose(file);
}
#if (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! defined(_GNU_SOURCE) && !defined(WIN32)
#define HAVE_STRERROR_R 1
#endif
GF_EXPORT
size_t gf_fwrite(const void *ptr, size_t nb_bytes, FILE *stream)
{
size_t result=0;
if (gf_fileio_check(stream)) {
return(size_t) gf_fileio_write((GF_FileIO *)stream, (u8 *) ptr, (u32) nb_bytes);
}
if (ptr)
result = fwrite(ptr, 1, nb_bytes, stream);
if (result != nb_bytes) {
#ifdef _WIN32_WCE
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Error writing data: %d blocks to write but %d blocks written\n", nb_bytes, result));
#else
#if defined WIN32 && !defined(GPAC_CONFIG_WIN32)
errno_t errno_save;
_get_errno(&errno_save);
#else
int errno_save = errno;
#endif
#ifdef HAVE_STRERROR_R
#define ERRSTR_BUF_SIZE 256
char errstr[ERRSTR_BUF_SIZE];
if(strerror_r(errno_save, errstr, ERRSTR_BUF_SIZE) != 0)
{
strerror_r(0, errstr, ERRSTR_BUF_SIZE);
}
#else
char *errstr = (char*)strerror(errno_save);
#endif
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Error writing data (%s): %d blocks to write but %d blocks written\n", errstr, nb_bytes, result));
#endif
}
return result;
}
GF_EXPORT
size_t gf_fread(void *ptr, size_t nbytes, FILE *stream)
{
size_t result;
if (gf_fileio_check(stream)) {
return (size_t) gf_fileio_read((GF_FileIO *)stream, ptr, (u32) nbytes);
}
if (!stream) return 0;
result = fread(ptr, 1, nbytes, stream);
return result;
}
GF_EXPORT
char *gf_fgets(char *ptr, size_t size, FILE *stream)
{
if (gf_fileio_check(stream)) {
GF_FileIO *fio = (GF_FileIO *)stream;
if (fio->gets)
return fio->gets(fio, ptr, (u32) size);
u32 i, read, nb_read=0;
for (i=0; i<size-1; i++) {
u8 buf[1];
read = (u32) gf_fileio_read(fio, buf, 1);
if (!read) break;
ptr[nb_read] = buf[0];
nb_read++;
if (buf[0]=='\n') break;
}
ptr[nb_read]=0;
if (!nb_read) return NULL;
return ptr;
}
return fgets(ptr, (u32) size, stream);
}
GF_EXPORT
int gf_fgetc(FILE *stream)
{
if (gf_fileio_check(stream)) {
GF_FileIO *fio = (GF_FileIO *)stream;
u8 buf[1];
u32 read = gf_fileio_read(fio, buf, 1);
if (!read) return -1;
return buf[0];
}
return fgetc(stream);
}
GF_EXPORT
int gf_fputc(int val, FILE *stream)
{
if (gf_fileio_check(stream)) {
GF_FileIO *fio = (GF_FileIO *)stream;
u32 write;
u8 buf[1];
buf[0] = val;
write = gf_fileio_write(fio, buf, 1);
if (!write) return -1;
return buf[0];
}
return fputc(val, stream);
}
GF_EXPORT
int gf_fputs(const char *buf, FILE *stream)
{
if (gf_fileio_check(stream)) {
GF_FileIO *fio = (GF_FileIO *)stream;
u32 write, len = (u32) strlen(buf);
write = gf_fileio_write(fio, (u8 *) buf, len);
if (write != len) return -1;
return write;
}
return fputs(buf, stream);
}
GF_EXPORT
int gf_fprintf(FILE *stream, const char *format, ...)
{
int res;
va_list args;
va_start(args, format);
if (gf_fileio_check(stream)) {
res = gf_fileio_printf((GF_FileIO *)stream, format, args);
} else {
res = vfprintf(stream, format, args);
}
va_end(args);
return res;
}
GF_EXPORT
int gf_vfprintf(FILE *stream, const char *format, va_list args)
{
int res;
if (gf_fileio_check(stream)) {
res = gf_fileio_printf((GF_FileIO *)stream, format, args);
} else {
res = vfprintf(stream, format, args);
}
return res;
}
GF_EXPORT
int gf_fflush(FILE *stream)
{
if (gf_fileio_check(stream)) {
return gf_fileio_flush((GF_FileIO *)stream);
}
return fflush(stream);
}
GF_EXPORT
int gf_feof(FILE *stream)
{
if (!stream) return 1;
if (gf_fileio_check(stream)) {
return gf_fileio_eof((GF_FileIO *)stream) ? 1 : 0;
}
return feof(stream);
}
GF_EXPORT
int gf_ferror(FILE *stream)
{
if (gf_fileio_check(stream)) {
return 0;
}
return ferror(stream);
}
GF_EXPORT
u64 gf_fsize(FILE *fp)
{
u64 size;
if (gf_fileio_check(fp)) {
GF_FileIO *gfio = (GF_FileIO *)fp;
if (gfio->file_size_plus_one) {
gf_fseek(fp, 0, SEEK_SET);
return gfio->file_size_plus_one - 1;
}
//fall through
}
gf_fseek(fp, 0, SEEK_END);
size = gf_ftell(fp);
gf_fseek(fp, 0, SEEK_SET);
return size;
}
GF_EXPORT
u64 gf_fd_fsize(int fd)
{
u64 size=0;
#ifdef GPAC_HAS_FD
if (fd >= 0) {
#if defined(WIN32)
struct _stat64 sb;
_fstat64(fd, &sb);
#else
struct stat sb;
fstat(fd, &sb);
#endif
size = (u64) sb.st_size;
}
#endif
return size;
}
/**
* Returns a pointer to the start of a filepath basename
**/
GF_EXPORT
char* gf_file_basename(const char* filename)
{
char* lastPathPart = NULL;
if (filename) {
lastPathPart = strrchr(filename , GF_PATH_SEPARATOR);
#if GF_PATH_SEPARATOR != '/'
// windows paths can mix slashes and backslashes
// so we search for the last slash that occurs after the last backslash
// if it occurs before it's not relevant
// if there's no backslashes we search in the whole file path
char* trailingSlash = strrchr(lastPathPart?lastPathPart:filename, '/');
if (trailingSlash)
lastPathPart = trailingSlash;
#endif
if (!lastPathPart)
lastPathPart = (char *)filename;
else
lastPathPart++;
}
return lastPathPart;
}
/**
* Returns a pointer to the start of a filepath extension or null
**/
GF_EXPORT
char* gf_file_ext_start(const char* filename)
{
char* basename;
if (filename && !strncmp(filename, "gfio://", 7)) {
GF_FileIO *gfio = gf_fileio_from_url(filename);
filename = gf_fileio_resource_url(gfio);
}
basename = gf_file_basename(filename);
if (basename) {
char *ext = strrchr(basename, '.');
if (!ext) return NULL;
if (!strcmp(ext, ".gz")) {
ext[0] = 0;
char *ext2 = strrchr(basename, '.');
ext[0] = '.';
if (ext2) return ext2;
}
//consider that if we have a space after a dot and before any common separator, we have no file extension
u32 i;
for (i=1; ext[i] ; i++) {
if ((ext[i]==':') || (ext[i]=='@') || (ext[i]=='#') || (ext[i]=='?')) break;
if (ext[i]==' ') return NULL;
}
return ext;
}
return NULL;
}
GF_EXPORT
char* gf_url_colon_suffix(const char *path, char assign_sep)
{
char *sep = strchr(path, ':');
if (!sep) return NULL;
//handle Z:\ and Z:/
if ((path[1]==':') && ( (path[2]=='/') || (path[2]=='\\') ) )
return gf_url_colon_suffix(path+2, assign_sep);
if (!strncmp(path, "gfio://", 7) || !strncmp(path, "gmem://", 7)) {
return strchr(path+7, ':');
}
//handle "\\foo\Z:\bar"
if ((path[0] == '\\') && (path[1] == '\\')) {
char *next = strchr(path+2, '\\');
if (next) next = strchr(next + 1, '\\');
if (next)
return gf_url_colon_suffix(next + 1, assign_sep);
}
//handle PROTO://ADD:PORT/
if ((sep[1]=='/') && (sep[2]=='/')) {
char *next_colon, *next_slash, *userpass;
sep++;
//skip all // (eg PROTO://////////////mytest/)
while (sep[0]=='/')
sep++;
if (!sep[0]) return NULL;
/*we may now have C:\ or C:/ (eg file://///C:\crazy\ or file://///C:/crazy/)
if sep[1]==':', then sep[2] is valid (0 or something else), no need to check for len
*/
if ((sep[1]==':') && ( (sep[2]=='/') || (sep[2]=='\\') ) ) {
return gf_url_colon_suffix(sep+2, assign_sep);
}
//find closest : or /, if : is before / consider this is a port or an IPv6 address and check next : after /
next_colon = strchr(sep, ':');
next_slash = strchr(sep, '/');
userpass = strchr(sep, '@');
//if ':' is before '@' with '@' before next '/', consider this is `user:pass@SERVER`
if (userpass && next_colon && next_slash && (userpass<next_slash) && (userpass>next_colon))
next_colon = strchr(userpass, ':');
if (next_colon && next_slash && ((next_slash - sep) > (next_colon - sep)) ) {
const char *last_colon;
u32 i, port, nb_colons=0, nb_dots=0, nb_non_alnums=0;
next_slash[0] = 0;
last_colon = strrchr(next_colon, ':');
port = atoi(last_colon+1);
for (i=0; i<strlen(next_colon+1); i++) {
if (next_colon[i+1] == ':') nb_colons++;
else if (next_colon[i+1] == '.') nb_dots++;
//closing bracket of IPv6
else if (next_colon[i+1] == ']') {}
else if (!isalnum(next_colon[i+1])) {
nb_non_alnums++;
break;
}
}
next_slash[0] = '/';
//if no non-alphanum, we must have either a port (ipv4) or extra colons but no dots (ipv6)
if (!nb_non_alnums && (port || (nb_colons && !nb_dots)))
next_colon = strchr(next_slash, ':');
}
return next_colon;
}
if (sep && assign_sep) {
char *file_ext = strchr(path, '.');
char *assign = strchr(path, assign_sep);
if (assign && assign>file_ext) assign = NULL;
if (assign) file_ext = NULL;
if (file_ext && (file_ext>sep)) {
sep = strchr(file_ext, ':');
}
if (assign && (strlen(assign) > 4)) {
if ((assign[2] == ':') && ((assign[3] == '\\') || (assign[3] == '/'))) {
return gf_url_colon_suffix(assign + 1, 0);
}
if ((assign[1] == '\\') && (assign[2] == '\\')) {
char *next = strchr(assign + 3, '\\');
if (next) next = strchr(next+1, '\\');
if (next && (next>sep))
return gf_url_colon_suffix(next, 0);
}
}
}
return sep;
}
GF_EXPORT
char* gf_realpath(const char* path, char* resolved_path) {
#ifdef _WIN32
return _fullpath(resolved_path, path, GF_MAX_PATH);
#else
return realpath(path, resolved_path);
#endif
}