/* 2004.02.01 first released source code for IOMP */ /* * Copyright (C) 2000-2003 the xine project * * This file is part of xine, a free video player. * * xine is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * xine 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA * * $Id: input_file.c,v 1.2 2003/11/25 04:25:47 georgedon Exp $ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include "xine_internal.h" #include "xineutils.h" #include "compat.h" #include "input_plugin.h" #define MAXFILES 65535 typedef struct { input_class_t input_class; xine_t *xine; config_values_t *config; int show_hidden_files; char *origin_path; int mrls_allocated_entries; xine_mrl_t **mrls; } file_input_class_t; typedef struct { input_plugin_t input_plugin; xine_stream_t *stream; int fh; char *mrl; } file_input_plugin_t; static uint32_t file_plugin_get_capabilities (input_plugin_t *this_gen) { struct stat buf ; file_input_plugin_t *this = (file_input_plugin_t *) this_gen; if (this->fh <0) return 0; #ifdef _MSC_VER /*return INPUT_CAP_SEEKABLE | INPUT_CAP_GET_DIR;*/ return INPUT_CAP_SEEKABLE; #else if (fstat (this->fh, &buf) == 0) { if (S_ISREG(buf.st_mode)) return INPUT_CAP_SEEKABLE; else return 0; } else perror ("system call fstat"); return 0; #endif /* _MSC_VER */ } static off_t file_plugin_read (input_plugin_t *this_gen, char *buf, off_t len) { file_input_plugin_t *this = (file_input_plugin_t *) this_gen; return read (this->fh, buf, len); } static buf_element_t *file_plugin_read_block (input_plugin_t *this_gen, fifo_buffer_t *fifo, off_t todo) { off_t num_bytes, total_bytes; file_input_plugin_t *this = (file_input_plugin_t *) this_gen; buf_element_t *buf = fifo->buffer_pool_alloc (fifo); buf->content = buf->mem; buf->type = BUF_DEMUX_BLOCK; total_bytes = 0; while (total_bytes < todo) { num_bytes = read (this->fh, buf->mem + total_bytes, todo-total_bytes); if (num_bytes <= 0) { if (num_bytes < 0) { xine_log (this->stream->xine, XINE_LOG_MSG, _("input_file: read error (%s)\n"), strerror(errno)); xine_message(this->stream, XINE_MSG_READ_ERROR, this->mrl, NULL); } buf->free_buffer (buf); buf = NULL; break; } total_bytes += num_bytes; } if( buf != NULL ) buf->size = total_bytes; return buf; } static off_t file_plugin_seek (input_plugin_t *this_gen, off_t offset, int origin) { file_input_plugin_t *this = (file_input_plugin_t *) this_gen; return lseek (this->fh, offset, origin); } static off_t file_plugin_get_current_pos (input_plugin_t *this_gen){ file_input_plugin_t *this = (file_input_plugin_t *) this_gen; if (this->fh <0) return 0; return lseek (this->fh, 0, SEEK_CUR); } static off_t file_plugin_get_length (input_plugin_t *this_gen) { struct stat buf ; file_input_plugin_t *this = (file_input_plugin_t *) this_gen; if (this->fh <0) return 0; if (fstat (this->fh, &buf) == 0) { return buf.st_size; } else perror ("system call fstat"); return 0; } static uint32_t file_plugin_get_blocksize (input_plugin_t *this_gen) { return 0; } /* * Return 1 is filepathname is a directory, otherwise 0 */ static int is_a_dir(char *filepathname) { struct stat pstat; stat(filepathname, &pstat); return (S_ISDIR(pstat.st_mode)); } static char* file_plugin_get_mrl (input_plugin_t *this_gen) { file_input_plugin_t *this = (file_input_plugin_t *) this_gen; return this->mrl; } static int file_plugin_get_optional_data (input_plugin_t *this_gen, void *data, int data_type) { return INPUT_OPTIONAL_UNSUPPORTED; } static void file_plugin_dispose (input_plugin_t *this_gen ) { file_input_plugin_t *this = (file_input_plugin_t *) this_gen; if (this->fh != -1) close(this->fh); free (this->mrl); free (this); } static char *decode_uri (char *uri) { int len = strlen (uri); int i; for (i=0; imrl, "file:", 5)) filename = decode_uri (&(this->mrl[5])); else filename = decode_uri(this->mrl); #ifdef _MSC_VER /* added O_BINARY flag, -Z char is interperated as EOF on win32. */ this->fh = open (filename, O_RDONLY|O_BINARY); #else this->fh = open (filename, O_RDONLY); #endif /* _MSC_VER */ if (this->fh == -1) { return 0; } return 1; } static input_plugin_t *file_class_get_instance (input_class_t *cls_gen, xine_stream_t *stream, const char *data) { /* file_input_class_t *cls = (file_input_class_t *) cls_gen; */ file_input_plugin_t *this; char *mrl = strdup(data); #ifdef LOG printf("file_class_get_instance\n"); #endif if ((strncasecmp (mrl, "file:", 5)) && (strstr (mrl, ":/"))) { free (mrl); return NULL; } this = (file_input_plugin_t *) xine_xmalloc (sizeof (file_input_plugin_t)); this->stream = stream; this->mrl = mrl; this->fh = -1; this->input_plugin.open = file_plugin_open; this->input_plugin.get_capabilities = file_plugin_get_capabilities; this->input_plugin.read = file_plugin_read; this->input_plugin.read_block = file_plugin_read_block; this->input_plugin.seek = file_plugin_seek; this->input_plugin.get_current_pos = file_plugin_get_current_pos; this->input_plugin.get_length = file_plugin_get_length; this->input_plugin.get_blocksize = file_plugin_get_blocksize; this->input_plugin.get_mrl = file_plugin_get_mrl; this->input_plugin.get_optional_data = file_plugin_get_optional_data; this->input_plugin.dispose = file_plugin_dispose; this->input_plugin.input_class = cls_gen; return &this->input_plugin; } /* * plugin class functions */ #ifndef S_ISLNK #define S_ISLNK(mode) 0 #endif #ifndef S_ISFIFO #define S_ISFIFO(mode) 0 #endif #ifndef S_ISSOCK #define S_ISSOCK(mode) 0 #endif #ifndef S_ISCHR #define S_ISCHR(mode) 0 #endif #ifndef S_ISBLK #define S_ISBLK(mode) 0 #endif #ifndef S_ISREG #define S_ISREG(mode) 0 #endif #if !S_IXUGO #define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH) #endif /* * Callback for config changes. */ static void hidden_bool_cb(void *data, xine_cfg_entry_t *cfg) { file_input_class_t *this = (file_input_class_t *) data; this->show_hidden_files = cfg->num_value; } static void origin_change_cb(void *data, xine_cfg_entry_t *cfg) { file_input_class_t *this = (file_input_class_t *) data; this->origin_path = cfg->str_value; } /* * Sorting function, it comes from GNU fileutils package. */ #define S_N 0x0 #define S_I 0x4 #define S_F 0x8 #define S_Z 0xC #define CMP 2 #define LEN 3 #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9) static int strverscmp(const char *s1, const char *s2) { const unsigned char *p1 = (const unsigned char *) s1; const unsigned char *p2 = (const unsigned char *) s2; unsigned char c1, c2; int state; int diff; static const unsigned int next_state[] = { S_N, S_I, S_Z, S_N, S_N, S_I, S_I, S_I, S_N, S_F, S_F, S_F, S_N, S_F, S_Z, S_Z }; static const int result_type[] = { CMP, CMP, CMP, CMP, CMP, LEN, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, -1, -1, CMP, 1, LEN, LEN, CMP, 1, LEN, LEN, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, LEN, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, 1, 1, CMP, -1, CMP, CMP, CMP, -1, CMP, CMP, CMP }; if(p1 == p2) return 0; c1 = *p1++; c2 = *p2++; state = S_N | ((c1 == '0') + (ISDIGIT(c1) != 0)); while((diff = c1 - c2) == 0 && c1 != '\0') { state = next_state[state]; c1 = *p1++; c2 = *p2++; state |= (c1 == '0') + (ISDIGIT(c1) != 0); } state = result_type[state << 2 | ((c2 == '0') + (ISDIGIT(c2) != 0))]; switch(state) { case CMP: return diff; case LEN: while(ISDIGIT(*p1++)) if(!ISDIGIT(*p2++)) return 1; return ISDIGIT(*p2) ? -1 : diff; default: return state; } } /* * Wrapper to strverscmp() for qsort() calls, which sort mrl_t type array. */ static int _sortfiles_default(const xine_mrl_t *s1, const xine_mrl_t *s2) { return(strverscmp(s1->mrl, s2->mrl)); } /* * Return the type (OR'ed) of the given file *fully named* */ static uint32_t get_file_type(char *filepathname, char *origin, xine_t *xine) { struct stat pstat; int mode; uint32_t file_type = 0; char buf[XINE_PATH_MAX + XINE_NAME_MAX + 1]; if((lstat(filepathname, &pstat)) < 0) { sprintf(buf, "%s/%s", origin, filepathname); if((lstat(buf, &pstat)) < 0) { #ifdef LOG printf ("lstat failed for %s{%s}\n", filepathname, origin); #endif file_type |= mrl_unknown; return file_type; } } file_type |= mrl_file; mode = pstat.st_mode; if(S_ISLNK(mode)) file_type |= mrl_file_symlink; else if(S_ISDIR(mode)) file_type |= mrl_file_directory; else if(S_ISCHR(mode)) file_type |= mrl_file_chardev; else if(S_ISBLK(mode)) file_type |= mrl_file_blockdev; else if(S_ISFIFO(mode)) file_type |= mrl_file_fifo; else if(S_ISSOCK(mode)) file_type |= mrl_file_sock; else { if(S_ISREG(mode)) { file_type |= mrl_file_normal; } if(mode & S_IXUGO) file_type |= mrl_file_exec; } if(filepathname[strlen(filepathname) - 1] == '~') file_type |= mrl_file_backup; return file_type; } /* * Return the file size of the given file *fully named* */ static off_t get_file_size(char *filepathname, char *origin) { struct stat pstat; char buf[XINE_PATH_MAX + XINE_NAME_MAX + 1]; if((lstat(filepathname, &pstat)) < 0) { sprintf(buf, "%s/%s", origin, filepathname); if((lstat(buf, &pstat)) < 0) return (off_t) 0; } return pstat.st_size; } static char *file_class_get_description (input_class_t *this_gen) { return _("file input plugin"); } static char *file_class_get_identifier (input_class_t *this_gen) { return "file"; } static xine_mrl_t **file_class_get_dir (input_class_t *this_gen, const char *filename, int *nFiles) { /* FIXME: this code needs cleanup badly */ file_input_class_t *this = (file_input_class_t *) this_gen; struct dirent *pdirent; DIR *pdir; xine_mrl_t *hide_files, *dir_files, *norm_files; char current_dir[XINE_PATH_MAX + 1]; char current_dir_slashed[XINE_PATH_MAX + 1]; char fullfilename[XINE_PATH_MAX + XINE_NAME_MAX + 1]; int num_hide_files = 0; int num_dir_files = 0; int num_norm_files = 0; int num_files = -1; int (*func) () = _sortfiles_default; int already_tried = 0; *nFiles = 0; memset(current_dir, 0, sizeof(current_dir)); /* * No origin location, so got the content of the current directory */ if(!filename) { snprintf(current_dir, XINE_PATH_MAX, "%s", this->origin_path); } else { snprintf(current_dir, XINE_PATH_MAX, "%s", filename); /* Remove exceed '/' */ while((current_dir[strlen(current_dir) - 1] == '/') && strlen(current_dir) > 1) current_dir[strlen(current_dir) - 1] = '\0'; } /* Store new origin path */ __try_again_from_home: this->config->update_string(this->config, "input.file_origin_path", current_dir); if(strcasecmp(current_dir, "/")) sprintf(current_dir_slashed, "%s/", current_dir); else sprintf(current_dir_slashed, "/"); /* * Ooch! */ if((pdir = opendir(current_dir)) == NULL) { if(!already_tried) { /* Try one more time with user homedir */ snprintf(current_dir, XINE_PATH_MAX, "%s", xine_get_homedir()); already_tried++; goto __try_again_from_home; } return NULL; } dir_files = (xine_mrl_t *) xine_xmalloc(sizeof(xine_mrl_t) * MAXFILES); hide_files = (xine_mrl_t *) xine_xmalloc(sizeof(xine_mrl_t) * MAXFILES); norm_files = (xine_mrl_t *) xine_xmalloc(sizeof(xine_mrl_t) * MAXFILES); while((pdirent = readdir(pdir)) != NULL) { memset(fullfilename, 0, sizeof(fullfilename)); sprintf(fullfilename, "%s/%s", current_dir, pdirent->d_name); if(is_a_dir(fullfilename)) { /* if user don't want to see hidden files, ignore them */ if(this->show_hidden_files == 0 && ((strlen(pdirent->d_name) > 1) && (pdirent->d_name[0] == '.' && pdirent->d_name[1] != '.'))) { ; } else { dir_files[num_dir_files].mrl = (char *) xine_xmalloc(strlen(current_dir_slashed) + 1 + strlen(pdirent->d_name) + 1); dir_files[num_dir_files].origin = strdup(current_dir); sprintf(dir_files[num_dir_files].mrl, "%s%s", current_dir_slashed, pdirent->d_name); dir_files[num_dir_files].link = NULL; dir_files[num_dir_files].type = get_file_type(fullfilename, current_dir, this->xine); dir_files[num_dir_files].size = get_file_size(fullfilename, current_dir); /* The file is a link, follow it */ if(dir_files[num_dir_files].type & mrl_file_symlink) { char linkbuf[XINE_PATH_MAX + XINE_NAME_MAX + 1]; int linksize; memset(linkbuf, 0, sizeof(linkbuf)); linksize = readlink(fullfilename, linkbuf, XINE_PATH_MAX + XINE_NAME_MAX); if(linksize < 0) printf ("input_file: readlink() failed: %s\n", strerror(errno)); else { dir_files[num_dir_files].link = (char *) xine_xmalloc(linksize + 1); strncpy(dir_files[num_dir_files].link, linkbuf, linksize); dir_files[num_dir_files].type |= get_file_type(dir_files[num_dir_files].link, current_dir, this->xine); } } num_dir_files++; } } /* Hmmmm, an hidden file ? */ else if((strlen(pdirent->d_name) > 1) && (pdirent->d_name[0] == '.' && pdirent->d_name[1] != '.')) { /* if user don't want to see hidden files, ignore them */ if(this->show_hidden_files) { hide_files[num_hide_files].mrl = (char *) xine_xmalloc(strlen(current_dir_slashed) + 1 + strlen(pdirent->d_name) + 1); hide_files[num_hide_files].origin = strdup(current_dir); sprintf(hide_files[num_hide_files].mrl, "%s%s", current_dir_slashed, pdirent->d_name); hide_files[num_hide_files].link = NULL; hide_files[num_hide_files].type = get_file_type(fullfilename, current_dir, this->xine); hide_files[num_hide_files].size = get_file_size(fullfilename, current_dir); /* The file is a link, follow it */ if(hide_files[num_hide_files].type & mrl_file_symlink) { char linkbuf[XINE_PATH_MAX + XINE_NAME_MAX + 1]; int linksize; memset(linkbuf, 0, sizeof(linkbuf)); linksize = readlink(fullfilename, linkbuf, XINE_PATH_MAX + XINE_NAME_MAX); if(linksize < 0) { printf ("input_file: readlink() failed: %s\n", strerror(errno)); } else { hide_files[num_hide_files].link = (char *) xine_xmalloc(linksize + 1); strncpy(hide_files[num_hide_files].link, linkbuf, linksize); hide_files[num_hide_files].type |= get_file_type(hide_files[num_hide_files].link, current_dir, this->xine); } } num_hide_files++; } } /* So a *normal* one. */ else { norm_files[num_norm_files].mrl = (char *) xine_xmalloc(strlen(current_dir_slashed) + 1 + strlen(pdirent->d_name) + 1); norm_files[num_norm_files].origin = strdup(current_dir); sprintf(norm_files[num_norm_files].mrl, "%s%s", current_dir_slashed, pdirent->d_name); norm_files[num_norm_files].link = NULL; norm_files[num_norm_files].type = get_file_type(fullfilename, current_dir, this->xine); norm_files[num_norm_files].size = get_file_size(fullfilename, current_dir); /* The file is a link, follow it */ if(norm_files[num_norm_files].type & mrl_file_symlink) { char linkbuf[XINE_PATH_MAX + XINE_NAME_MAX + 1]; int linksize; memset(linkbuf, 0, sizeof(linkbuf)); linksize = readlink(fullfilename, linkbuf, XINE_PATH_MAX + XINE_NAME_MAX); if(linksize < 0) { printf ("input_file: readlink() failed: %s\n", strerror(errno)); } else { norm_files[num_norm_files].link = (char *) xine_xmalloc(linksize + 1); strncpy(norm_files[num_norm_files].link, linkbuf, linksize); norm_files[num_norm_files].type |= get_file_type(norm_files[num_norm_files].link, current_dir, this->xine); } } num_norm_files++; } num_files++; } closedir(pdir); /* * Ok, there are some files here, so sort * them then store them into global mrls array. */ if(num_files > 0) { int i; num_files = 0; /* * Sort arrays */ if(num_dir_files) qsort(dir_files, num_dir_files, sizeof(xine_mrl_t), func); if(num_hide_files) qsort(hide_files, num_hide_files, sizeof(xine_mrl_t), func); if(num_norm_files) qsort(norm_files, num_norm_files, sizeof(xine_mrl_t), func); /* * Add directories entries */ for(i = 0; i < num_dir_files; i++) { if(num_files >= this->mrls_allocated_entries) { ++this->mrls_allocated_entries; this->mrls = realloc(this->mrls, (this->mrls_allocated_entries+1) * sizeof(xine_mrl_t*)); this->mrls[num_files] = (xine_mrl_t *) xine_xmalloc(sizeof(xine_mrl_t)); } else memset(this->mrls[num_files], 0, sizeof(xine_mrl_t)); MRL_DUPLICATE(&dir_files[i], this->mrls[num_files]); num_files++; } /* * Add hidden files entries */ for(i = 0; i < num_hide_files; i++) { if(num_files >= this->mrls_allocated_entries) { ++this->mrls_allocated_entries; this->mrls = realloc(this->mrls, (this->mrls_allocated_entries+1) * sizeof(xine_mrl_t*)); this->mrls[num_files] = (xine_mrl_t *) xine_xmalloc(sizeof(xine_mrl_t)); } else memset(this->mrls[num_files], 0, sizeof(xine_mrl_t)); MRL_DUPLICATE(&hide_files[i], this->mrls[num_files]); num_files++; } /* * Add other files entries */ for(i = 0; i < num_norm_files; i++) { if(num_files >= this->mrls_allocated_entries) { ++this->mrls_allocated_entries; this->mrls = realloc(this->mrls, (this->mrls_allocated_entries+1) * sizeof(xine_mrl_t*)); this->mrls[num_files] = (xine_mrl_t *) xine_xmalloc(sizeof(xine_mrl_t)); } else memset(this->mrls[num_files], 0, sizeof(xine_mrl_t)); MRL_DUPLICATE(&norm_files[i], this->mrls[num_files]); num_files++; } /* Some cleanups before leaving */ for(i = num_dir_files; i == 0; i--) MRL_ZERO(&dir_files[i]); free(dir_files); for(i = num_hide_files; i == 0; i--) MRL_ZERO(&hide_files[i]); free(hide_files); for(i = num_norm_files; i == 0; i--) MRL_ZERO(&norm_files[i]); free(norm_files); } else return NULL; /* * Inform caller about files found number. */ *nFiles = num_files; /* * Freeing exceeded mrls if exists. */ while(this->mrls_allocated_entries > num_files) { MRL_ZERO(this->mrls[this->mrls_allocated_entries - 1]); free(this->mrls[this->mrls_allocated_entries--]); } /* * This is useful to let UI know where it should stops ;-). */ this->mrls[num_files] = NULL; /* * Some debugging info */ /* { int j = 0; while(this->mrls[j]) { printf("mrl[%d] = '%s'\n", j, this->mrls[j]->mrl); j++; } } */ return this->mrls; } static void file_class_dispose (input_class_t *this_gen) { file_input_class_t *this = (file_input_class_t *) this_gen; free (this->mrls); free (this); } static void *init_plugin (xine_t *xine, void *data) { file_input_class_t *this; config_values_t *config; this = (file_input_class_t *) xine_xmalloc (sizeof (file_input_class_t)); this->xine = xine; this->config = xine->config; config = xine->config; this->input_class.get_instance = file_class_get_instance; this->input_class.get_identifier = file_class_get_identifier; this->input_class.get_description = file_class_get_description; this->input_class.get_dir = file_class_get_dir; this->input_class.get_autoplay_list = NULL; this->input_class.dispose = file_class_dispose; this->input_class.eject_media = NULL; this->mrls = (xine_mrl_t **) xine_xmalloc(sizeof(xine_mrl_t*)); this->mrls_allocated_entries = 0; { char current_dir[XINE_PATH_MAX + 1]; if(getcwd(current_dir, sizeof(current_dir)) == NULL) strcpy(current_dir, "."); this->origin_path = config->register_string(config, "input.file_origin_path", current_dir, _("file browsing start location"), NULL, 0, origin_change_cb, (void *) this); } this->show_hidden_files = config->register_bool(config, "input.file_hidden_files", 1, _("list hidden files"), NULL, 10, hidden_bool_cb, (void *) this); return this; } /* * exported plugin catalog entry */ plugin_info_t xine_plugin_info[] = { /* type, API, "name", version, special_info, init_function */ { PLUGIN_INPUT, 13, "FILE", XINE_VERSION_CODE, NULL, init_plugin }, { PLUGIN_NONE, 0, "", 0, NULL, NULL } };