diff options
author | Johns <johns98@gmx.net> | 2012-09-03 22:46:08 +0200 |
---|---|---|
committer | Johns <johns98@gmx.net> | 2012-09-03 22:46:08 +0200 |
commit | bcbc5de872c7b773e47a146d465723ea2f76f66e (patch) | |
tree | 2c3840dd6e91a3c7559820cdc63ea7c2eaf3dac4 | |
parent | 9428376c613e7dfba6541df413d31d1df1ad0a9a (diff) | |
download | vdr-plugin-play-bcbc5de872c7b773e47a146d465723ea2f76f66e.tar.gz vdr-plugin-play-bcbc5de872c7b773e47a146d465723ea2f76f66e.tar.bz2 |
Read directory support.
-rw-r--r-- | Makefile | 14 | ||||
-rw-r--r-- | readdir.c | 392 | ||||
-rw-r--r-- | readdir.h | 43 |
3 files changed, 445 insertions, 4 deletions
@@ -19,7 +19,7 @@ GIT_REV = $(shell git describe --always 2>/dev/null) ### Configuration (edit this for your needs) CONFIG := #-DDEBUG -CONFIG += +CONFIG += -DUSE_AVFS # use a virtual file system ### The C++ compiler and options: @@ -60,18 +60,24 @@ DEFINES += $(CONFIG) -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' \ $(if $(GIT_REV), -DGIT_REV='"$(GIT_REV)"') _CFLAGS = $(DEFINES) $(INCLUDES) \ - $(shell pkg-config --cflags xcb xcb-event xcb-keysyms xcb-icccm xcb-image) + $(shell pkg-config --cflags xcb xcb-event xcb-keysyms xcb-icccm \ + xcb-image) \ + $(if $(findstring USE_AVFS,$(CONFIG)), `avfs-config --cflags`) \ + $(if $(findstring USE_PNG,$(CONFIG)), `pkg-config --cflags libpng`) #override _CFLAGS += -Werror override CXXFLAGS += $(_CFLAGS) override CFLAGS += $(_CFLAGS) LIBS += \ - $(shell pkg-config --libs xcb xcb-keysyms xcb-event xcb-icccm xcb-image) + $(shell pkg-config --libs xcb xcb-keysyms xcb-event xcb-icccm \ + xcb-image) \ + $(if $(findstring USE_AVFS,$(CONFIG)), `avfs-config --libs`) \ + $(if $(findstring USE_PNG,$(CONFIG)), `pkg-config --libs libpng`) ### The object files (add further files here): -OBJS = $(PLUGIN).o video.o +OBJS = $(PLUGIN).o dia.o video.o readdir.o SRCS = $(wildcard $(OBJS:.o=.c)) $(PLUGIN).cpp ### The main target: diff --git a/readdir.c b/readdir.c new file mode 100644 index 0000000..457098a --- /dev/null +++ b/readdir.c @@ -0,0 +1,392 @@ +/// +/// @file readdir.c @brief directory reading module +/// +/// Copyright (c) 2012 by Johns. All Rights Reserved. +/// +/// Contributor(s): +/// +/// License: AGPLv3 +/// +/// This program is free software: you can redistribute it and/or modify +/// it under the terms of the GNU Affero General Public License as +/// published by the Free Software Foundation, either version 3 of the +/// License. +/// +/// This program 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 Affero General Public License for more details. +/// +/// $Id$ +////////////////////////////////////////////////////////////////////////////// + +#define __USE_ZZIPLIB ///< zip archives support +#define __USE_AVFS ///< A Virtual File System support + +#include <sys/types.h> +#include <sys/stat.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> + +#include <dirent.h> + +#include <libintl.h> +#define _(str) gettext(str) ///< gettext shortcut +#define _N(str) str ///< gettext_noop shortcut + +#ifdef USE_ZZIPLIB +#include <zzip/lib.h> +#endif +#ifdef USE_AVFS +#include <virtual.h> +#else +#define virt_stat stat ///< universal stat +#define virt_fstat fstat ///< universal fstat +#define virt_opendir opendir ///< universal opendir +#define virt_readdir readdir ///< universal readdir +#define virt_closedir closedir ///< universal closedir +#endif + +#include "misc.h" +#include "readdir.h" + +////////////////////////////////////////////////////////////////////////////// +// Variables +////////////////////////////////////////////////////////////////////////////// + +const char ConfigShowHiddenFiles = 0; ///< config show hidden files +static const char *BaseDir; ///< current directory +static const NameFilter *NameFilters; ///< current name filter table + +////////////////////////////////////////////////////////////////////////////// +// Functions +////////////////////////////////////////////////////////////////////////////// + +/** +** Check if filename is a directory. +** +** @param filename path and file name +** +** @retval true directory +** @retval false no directory +*/ +int IsDirectory(const char *filename) +{ + struct stat stat_buf; + + if (virt_stat(filename, &stat_buf) < 0) { + Error("play/readdir: can't stat '%s': %s\n", filename, + strerror(errno)); + return -1; + } + return S_ISDIR(stat_buf.st_mode); +} + +/** +** Check if filename is an archive. +** +** @param filename path and file name +** +** @retval true archive +** @retval false no archive +*/ +int IsArchive(const char *filename) +{ +#ifdef USE_AVFS + + /** + ** Table of supported archive suffixes. + */ + static const NameFilter ArchiveFilters[] = { +#define FILTER(x) { sizeof(x) - 1, x } + FILTER(".cbz"), + FILTER(".cbr"), + FILTER(".zip"), + FILTER(".rar"), + FILTER(".tar"), + FILTER(".tar.gz"), + FILTER(".tgz"), +#undef FILTER + {0, NULL} + }; + int i; + int len; + + len = strlen(filename); + for (i = 0; ArchiveFilters[i].String; ++i) { + if (len >= ArchiveFilters[i].Length + && !strcasecmp(filename + len - ArchiveFilters[i].Length, + ArchiveFilters[i].String)) { + return 1; + } + } +#else + (void)filename; +#endif + return 0; +} + +/** +** Filter for scandir, only directories. +** +** @param dirent current directory entry +** +** @returns true if the @p dirent is a directories. +*/ +static int FilterIsDirectory(const struct dirent *dirent) +{ + char *tmp; + int dir; + size_t len; + + len = _D_EXACT_NAMLEN(dirent); + if (len && dirent->d_name[0] == '.') { + // hide hidden files + if (!ConfigShowHiddenFiles) { + return 0; + } + // ignore . and .. + if (len == 1 || (len == 2 && dirent->d_name[1] == '.')) { + return 0; + } + } +#ifdef _DIRENT_HAVE_D_TYPE + if (dirent->d_type == DT_DIR) { // only directories files + return 1; +#ifdef DT_LNK + } else if (dirent->d_type == DT_LNK) { // symbolic link +#endif + } else if (dirent->d_type != DT_UNKNOWN) { // no looser filesystem + return 0; + } +#endif + + // DT_UNKOWN or DT_LNK + tmp = (char *)malloc(strlen(BaseDir) + strlen(dirent->d_name) + 1); + stpcpy(stpcpy(tmp, BaseDir), dirent->d_name); + dir = IsDirectory(tmp); + free(tmp); + return dir; +} + +/** +** Filter for scandir, only files. +** +** @param dirent current directory entry +** +** @returns true if the @p dirent is a video. +*/ +static int FilterIsFile(const struct dirent *dirent) +{ + char *tmp; + int dir; + int len; + int i; + + len = _D_EXACT_NAMLEN(dirent); + if (len && dirent->d_name[0] == '.') { + if (!ConfigShowHiddenFiles) { // hide hidden files + return 0; + } + } + // look through name filter table + if (NameFilters) { + for (i = 0; NameFilters[i].String; ++i) { + if (len >= NameFilters[i].Length + && !strcasecmp(dirent->d_name + len - NameFilters[i].Length, + NameFilters[i].String)) { + goto found; + } + } + // no file name matched + return 0; + } + found: + +#ifdef _DIRENT_HAVE_D_TYPE + if (dirent->d_type == DT_REG) { // only regular files + return 1; +#ifdef DT_LNK + } else if (dirent->d_type == DT_LNK) { // symbolic link +#endif + } else if (dirent->d_type != DT_UNKNOWN) { // no looser filesystem + return 0; + } +#endif + + // DT_UNKOWN or DT_LNK + tmp = (char *)malloc(strlen(BaseDir) + strlen(dirent->d_name) + 1); + stpcpy(stpcpy(tmp, BaseDir), dirent->d_name); + dir = IsDirectory(tmp); + free(tmp); + return !dir; +} + +/** +** ScanDirectory qsort compare function. +** +** @param s1 table index 1 +** @param s2 table index 2 +** +** @returns an integer less than, equal to, or greater than zero if s1 +** is found, respectively, to be less than, to match, or be greater +** than s2. +*/ +static int q_cmp(const void *s1, const void *s2) +{ + return strcmp(*(char *const *)s1, *(char *const *)s2); +} + +/** +** Scan a directory for matching entries. +** +** @param name directory path and name +** @param flag_dir only directories or files +** @param filter list of name suffix filters +** @param[out] namelist list of matching names in directory +** +** @retval <0 if any error occurs +** @retval 0 empty directory, no errors occurs +** @retval n number of files +** +** @todo support reading and sorting files and directories +** @todo flag disable sort +*/ +int ScanDirectory(const char *name, int flag_dir, const NameFilter * filter, + char ***namelist) +{ + DIR *dir; + struct dirent *entry; + struct stat stat_buf; + int n; + char **names; + int arraysz; + int save; + + Debug(3, "play/scandir: scan directory '%s'\n", name); + + // FIXME: threads remove global variables + BaseDir = name; + NameFilters = filter; + + if (!(dir = virt_opendir(name))) { + Error("play/scandir: can't open dir '%s': %s\n", name, + strerror(errno)); + return -1; + } + if (virt_fstat(dirfd(dir), &stat_buf) < 0) { + Error("play/scandir: can't stat dir '%s': %s\n", name, + strerror(errno)); + return -1; + } + // approximate size of name array + if (stat_buf.st_size) { + arraysz = stat_buf.st_size / (3 * 8); + } else { + arraysz = 16; + } + if (!(names = malloc(arraysz * sizeof(*names)))) { + Error("play/scandir: dir '%s': out of memory\n", name); + return -1; + } + + n = 0; + errno = 0; + while ((entry = virt_readdir(dir))) { + int len; + char *tmp; + + // skip hidden files, wrong kind, wrong suffix + if (flag_dir ? !FilterIsDirectory(entry) : !FilterIsFile(entry)) { + continue; + } + + len = _D_ALLOC_NAMLEN(entry); + if (!(tmp = malloc(len))) { + Error("play/scandir: dir '%s': out of memory\n", name); + break; + } + memcpy(tmp, entry->d_name, len); + + if (++n >= arraysz) { // array full + char **new; + + if (virt_fstat(dirfd(dir), &stat_buf) < 0) { + Error("play/scandir: can't stat dir '%s': %s\n", name, + strerror(errno)); + --n; + break; + } + // dir size grown and valid + if (stat_buf.st_size / (3 * 4) > arraysz) { + arraysz = stat_buf.st_size / (3 * 4); + } else { + arraysz *= 2; + } + if (!(new = realloc(names, arraysz * sizeof(*names)))) { + Error("play/scandir: dir '%s': out of memory\n", name); + --n; + break; + } + names = new; + } + names[n - 1] = tmp; + } + + save = errno; + virt_closedir(dir); + + if (save) { // error happened + while (n > 0) { // free used memory + free(names[--n]); + } + free(names); + errno = save; + *namelist = NULL; + return -1; + } + // sort names + qsort(names, n, sizeof(*names), (int (*)(const void *, + const void *))q_cmp); + *namelist = names; + + return n; +} + +/** +** Read directory for menu. +** +** @param name directory path and name +** @param flag_dir only directories or files +** @param filter list of name suffix filters +** @param cb_add call back to handle directory entries +** @param opaque privat parameter for the call back +** +** @retval <0 if any error occurs +** @retval false if no errors occurs +*/ +int ReadDirectory(const char *name, int flag_dir, const NameFilter * filter, + void (*cb_add) (void *, const char *), void *opaque) +{ + int i; + int n; + char **names; + + n = ScanDirectory(name, flag_dir, filter, &names); + if (n >= 0) { + + for (i = 0; i < n; ++i) { // add names to menu + cb_add(opaque, names[i]); + free(names[i]); + } + + free(names); + } + + return n; +} diff --git a/readdir.h b/readdir.h new file mode 100644 index 0000000..e8fa22e --- /dev/null +++ b/readdir.h @@ -0,0 +1,43 @@ +/// +/// @file readdir.h @brief directory reading module header file +/// +/// Copyright (c) 2012 by Johns. All Rights Reserved. +/// +/// Contributor(s): +/// +/// License: AGPLv3 +/// +/// This program is free software: you can redistribute it and/or modify +/// it under the terms of the GNU Affero General Public License as +/// published by the Free Software Foundation, either version 3 of the +/// License. +/// +/// This program 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 Affero General Public License for more details. +/// +/// $Id$ +////////////////////////////////////////////////////////////////////////////// + +/// +/// Readdir name filter typedef +/// +typedef struct __name_filter_ +{ + int Length; ///< filter string length + const char *String; ///< filter string +} NameFilter; + + /// check if filename is a directory +extern int IsDirectory(const char *); + + /// check if filename is an archive +extern int IsArchive(const char *); + + /// scan a directory +extern int ScanDirectory(const char *, int, const NameFilter *, char ***); + + /// read a directory +extern int ReadDirectory(const char *, int, const NameFilter *, + void (*cb_add) (void *, const char *), void *); |