@@ -1,6 +1,6 @@ | |||||
all: sxiv | all: sxiv | ||||
VERSION=git-20110407 | |||||
VERSION=git-20110408 | |||||
CC?=gcc | CC?=gcc | ||||
PREFIX?=/usr/local | PREFIX?=/usr/local | ||||
@@ -36,6 +36,7 @@ sxiv supports the following command-line options: | |||||
-a Display all given files, do not filter out unsupported files | -a Display all given files, do not filter out unsupported files | ||||
(shorter startup time for long file list or slow file types) | (shorter startup time for long file list or slow file types) | ||||
-C Remove all orphaned cache files from thumbnail cache and exit | |||||
-d Scale all images to 100%, but fit large images into window | -d Scale all images to 100%, but fit large images into window | ||||
-F Use size-hints to make the window fixed/floating | -F Use size-hints to make the window fixed/floating | ||||
-f Start in fullscreen mode | -f Start in fullscreen mode | ||||
@@ -19,7 +19,6 @@ | |||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include <stdio.h> | #include <stdio.h> | ||||
#include <string.h> | #include <string.h> | ||||
#include <dirent.h> | |||||
#include <sys/select.h> | #include <sys/select.h> | ||||
#include <sys/stat.h> | #include <sys/stat.h> | ||||
#include <sys/time.h> | #include <sys/time.h> | ||||
@@ -48,7 +47,6 @@ typedef enum { | |||||
void update_title(); | void update_title(); | ||||
int check_append(const char*); | int check_append(const char*); | ||||
void read_dir_rec(const char*); | |||||
void run(); | void run(); | ||||
appmode_t mode; | appmode_t mode; | ||||
@@ -56,7 +54,6 @@ img_t img; | |||||
tns_t tns; | tns_t tns; | ||||
win_t win; | win_t win; | ||||
#define DNAME_CNT 512 | |||||
#define FNAME_CNT 1024 | #define FNAME_CNT 1024 | ||||
const char **filenames; | const char **filenames; | ||||
int filecnt, fileidx; | int filecnt, fileidx; | ||||
@@ -95,13 +92,24 @@ int load_image(int new) { | |||||
return ret; | return ret; | ||||
} | } | ||||
int fncmp(const void *a, const void *b) { | |||||
return strcoll(*((char* const*) a), *((char* const*) b)); | |||||
} | |||||
int main(int argc, char **argv) { | int main(int argc, char **argv) { | ||||
int i; | |||||
int i, start; | |||||
const char *filename; | const char *filename; | ||||
struct stat fstats; | struct stat fstats; | ||||
r_dir_t dir; | |||||
parse_options(argc, argv); | parse_options(argc, argv); | ||||
if (options->clean_cache) { | |||||
tns_init(&tns, 0); | |||||
tns_clear_cache(&tns); | |||||
exit(0); | |||||
} | |||||
if (!options->filecnt) { | if (!options->filecnt) { | ||||
print_usage(); | print_usage(); | ||||
exit(1); | exit(1); | ||||
@@ -123,13 +131,26 @@ int main(int argc, char **argv) { | |||||
} else { | } else { | ||||
for (i = 0; i < options->filecnt; ++i) { | for (i = 0; i < options->filecnt; ++i) { | ||||
filename = options->filenames[i]; | filename = options->filenames[i]; | ||||
if (!stat(filename, &fstats) && S_ISDIR(fstats.st_mode)) { | |||||
if (options->recursive) | |||||
read_dir_rec(filename); | |||||
else | |||||
warn("ignoring directory: %s", filename); | |||||
} else { | |||||
if (stat(filename, &fstats) || !S_ISDIR(fstats.st_mode)) { | |||||
check_append(filename); | check_append(filename); | ||||
} else { | |||||
if (!options->recursive) { | |||||
warn("ignoring directory: %s", filename); | |||||
continue; | |||||
} | |||||
if (r_opendir(&dir, filename)) { | |||||
warn("could not open directory: %s", filename); | |||||
continue; | |||||
} | |||||
start = fileidx; | |||||
while ((filename = r_readdir(&dir))) { | |||||
if (!check_append(filename)) | |||||
free((void*) filename); | |||||
} | |||||
r_closedir(&dir); | |||||
if (fileidx - start > 1) | |||||
qsort(filenames + start, fileidx - start, sizeof(char*), fncmp); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -215,75 +236,6 @@ int check_append(const char *filename) { | |||||
} | } | ||||
} | } | ||||
int fncmp(const void *a, const void *b) { | |||||
return strcoll(*((char* const*) a), *((char* const*) b)); | |||||
} | |||||
void read_dir_rec(const char *dirname) { | |||||
char *filename; | |||||
const char **dirnames; | |||||
int dircnt, diridx; | |||||
int fcnt, fstart; | |||||
unsigned char first; | |||||
size_t len; | |||||
DIR *dir; | |||||
struct dirent *dentry; | |||||
struct stat fstats; | |||||
if (!dirname) | |||||
return; | |||||
dircnt = DNAME_CNT; | |||||
diridx = first = 1; | |||||
dirnames = (const char**) s_malloc(dircnt * sizeof(const char*)); | |||||
dirnames[0] = dirname; | |||||
fcnt = 0; | |||||
fstart = fileidx; | |||||
while (diridx > 0) { | |||||
dirname = dirnames[--diridx]; | |||||
if (!(dir = opendir(dirname))) { | |||||
warn("could not open directory: %s", dirname); | |||||
} else { | |||||
while ((dentry = readdir(dir))) { | |||||
if (!strcmp(dentry->d_name, ".") || !strcmp(dentry->d_name, "..")) | |||||
continue; | |||||
len = strlen(dirname) + strlen(dentry->d_name) + 2; | |||||
filename = (char*) s_malloc(len * sizeof(char)); | |||||
snprintf(filename, len, "%s%s%s", dirname, | |||||
dirname[strlen(dirname)-1] == '/' ? "" : "/", dentry->d_name); | |||||
if (!stat(filename, &fstats) && S_ISDIR(fstats.st_mode)) { | |||||
if (diridx == dircnt) { | |||||
dircnt *= 2; | |||||
dirnames = (const char**) s_realloc(dirnames, | |||||
dircnt * sizeof(const char*)); | |||||
} | |||||
dirnames[diridx++] = filename; | |||||
} else { | |||||
if (check_append(filename)) | |||||
++fcnt; | |||||
else | |||||
free(filename); | |||||
} | |||||
} | |||||
closedir(dir); | |||||
} | |||||
if (!first) | |||||
free((void*) dirname); | |||||
else | |||||
first = 0; | |||||
} | |||||
if (fcnt > 1) | |||||
qsort(filenames + fstart, fcnt, sizeof(char*), fncmp); | |||||
free(dirnames); | |||||
} | |||||
#if EXT_COMMANDS | #if EXT_COMMANDS | ||||
int run_command(const char *cline, Bool reload) { | int run_command(const char *cline, Bool reload) { | ||||
int fncnt, fnlen; | int fncnt, fnlen; | ||||
@@ -31,7 +31,7 @@ options_t _options; | |||||
const options_t *options = (const options_t*) &_options; | const options_t *options = (const options_t*) &_options; | ||||
void print_usage() { | void print_usage() { | ||||
printf("usage: sxiv [-adFfhpqrstvZ] [-g GEOMETRY] [-z ZOOM] FILES...\n"); | |||||
printf("usage: sxiv [-aCdFfhpqrstvZ] [-g GEOMETRY] [-z ZOOM] FILES...\n"); | |||||
} | } | ||||
void print_version() { | void print_version() { | ||||
@@ -53,9 +53,10 @@ void parse_options(int argc, char **argv) { | |||||
_options.all = 0; | _options.all = 0; | ||||
_options.quiet = 0; | _options.quiet = 0; | ||||
_options.clean_cache = 0; | |||||
_options.recursive = 0; | _options.recursive = 0; | ||||
while ((opt = getopt(argc, argv, "adFfg:hpqrstvZz:")) != -1) { | |||||
while ((opt = getopt(argc, argv, "aCdFfg:hpqrstvZz:")) != -1) { | |||||
switch (opt) { | switch (opt) { | ||||
case '?': | case '?': | ||||
print_usage(); | print_usage(); | ||||
@@ -63,6 +64,9 @@ void parse_options(int argc, char **argv) { | |||||
case 'a': | case 'a': | ||||
_options.all = 1; | _options.all = 1; | ||||
break; | break; | ||||
case 'C': | |||||
_options.clean_cache = 1; | |||||
break; | |||||
case 'd': | case 'd': | ||||
_options.scalemode = SCALE_DOWN; | _options.scalemode = SCALE_DOWN; | ||||
break; | break; | ||||
@@ -37,6 +37,7 @@ typedef struct { | |||||
unsigned char all; | unsigned char all; | ||||
unsigned char quiet; | unsigned char quiet; | ||||
unsigned char clean_cache; | |||||
unsigned char recursive; | unsigned char recursive; | ||||
} options_t; | } options_t; | ||||
@@ -3,7 +3,7 @@ | |||||
sxiv \- Simple (or small or suckless) X Image Viewer | sxiv \- Simple (or small or suckless) X Image Viewer | ||||
.SH SYNOPSIS | .SH SYNOPSIS | ||||
.B sxiv | .B sxiv | ||||
.RB [ \-adFfhpqrstvZ ] | |||||
.RB [ \-aCdFfhpqrstvZ ] | |||||
.RB [ \-g | .RB [ \-g | ||||
.IR GEOMETRY ] | .IR GEOMETRY ] | ||||
.RB [ \-z | .RB [ \-z | ||||
@@ -24,6 +24,9 @@ sxiv has two modes of operation: image and thumbnail mode. The default is image | |||||
mode, in which only the current image is shown. In thumbnail mode a grid of | mode, in which only the current image is shown. In thumbnail mode a grid of | ||||
small previews is displayed, making it easy to choose an image to open. | small previews is displayed, making it easy to choose an image to open. | ||||
.P | .P | ||||
sxiv can also cache its thumbnails. Please see the section THUMBNAIL CACHING | |||||
for information on how to enable this feature. | |||||
.P | |||||
Please note, that the fullscreen mode requires an EWMH/NetWM compliant window | Please note, that the fullscreen mode requires an EWMH/NetWM compliant window | ||||
manager. | manager. | ||||
.SH OPTIONS | .SH OPTIONS | ||||
@@ -33,6 +36,9 @@ Display all given files, do not filter out unsupported files. This might result | |||||
in a much shorter startup time, when the file list is very long or contains | in a much shorter startup time, when the file list is very long or contains | ||||
large files of slow loadable types, e.g. gif and progressive jpg. | large files of slow loadable types, e.g. gif and progressive jpg. | ||||
.TP | .TP | ||||
.B \-C | |||||
Remove all orphaned cache files from the thumbnail cache directory and exit. | |||||
.TP | |||||
.B \-d | .B \-d | ||||
Scale all images to 100%, but fit large images into window. | Scale all images to 100%, but fit large images into window. | ||||
.TP | .TP | ||||
@@ -191,6 +197,23 @@ Pan image left. | |||||
.TP | .TP | ||||
.B Shift+ScrollDown | .B Shift+ScrollDown | ||||
Pan image right. | Pan image right. | ||||
.SH THUMBNAIL CACHING | |||||
To enable thumbnail caching, please make sure to create the directory | |||||
.I ~/.sxiv/ | |||||
with write permissions. sxiv will then store all thumbnails inside this | |||||
directory, but it will not create this directory by itself. It rather uses the | |||||
existance of this directory as an affirmation, that the user wants thumbnails | |||||
to be cached. | |||||
.P | |||||
Use the command line option | |||||
.I \-C | |||||
to keep the cache directory clean by removing all orphaned cache files. | |||||
Additionally, run the following command afterwards inside the cache directory | |||||
to remove empty subdirectories: | |||||
.P | |||||
.RS | |||||
find -type d -empty -delete | |||||
.RE | |||||
.SH AUTHORS | .SH AUTHORS | ||||
.EX | .EX | ||||
Bert Muennich <ber.t at gmx.com> | Bert Muennich <ber.t at gmx.com> | ||||
@@ -18,49 +18,214 @@ | |||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include <string.h> | #include <string.h> | ||||
#include <sys/time.h> | |||||
#include <sys/types.h> | |||||
#include <sys/stat.h> | |||||
#include <unistd.h> | |||||
#include "config.h" | #include "config.h" | ||||
#include "thumbs.h" | #include "thumbs.h" | ||||
#include "util.h" | #include "util.h" | ||||
extern Imlib_Image *im_invalid; | extern Imlib_Image *im_invalid; | ||||
const int thumb_dim = THUMB_SIZE + 10; | const int thumb_dim = THUMB_SIZE + 10; | ||||
char *cache_dir = NULL; | |||||
int tns_cache_enabled() { | |||||
struct stat stats; | |||||
return cache_dir && !stat(cache_dir, &stats) && S_ISDIR(stats.st_mode) && | |||||
!access(cache_dir, W_OK); | |||||
} | |||||
char* tns_cache_filename(const char *filename) { | |||||
size_t len; | |||||
char *cfile = NULL; | |||||
const char *abspath; | |||||
if (!cache_dir || !filename) | |||||
return NULL; | |||||
if (*filename != '/') { | |||||
if (!(abspath = absolute_path(filename))) | |||||
return NULL; | |||||
} else { | |||||
abspath = filename; | |||||
} | |||||
if (strncmp(abspath, cache_dir, strlen(cache_dir))) { | |||||
len = strlen(cache_dir) + strlen(abspath) + 6; | |||||
cfile = (char*) s_malloc(len); | |||||
snprintf(cfile, len, "%s/%s.png", cache_dir, abspath + 1); | |||||
} | |||||
if (abspath != filename) | |||||
free((void*) abspath); | |||||
return cfile; | |||||
} | |||||
Imlib_Image* tns_cache_load(const char *filename) { | |||||
char *cfile; | |||||
struct stat cstats, fstats; | |||||
Imlib_Image *im = NULL; | |||||
if (!filename) | |||||
return NULL; | |||||
if (stat(filename, &fstats)) | |||||
return NULL; | |||||
if ((cfile = tns_cache_filename(filename))) { | |||||
if (!stat(cfile, &cstats) && | |||||
cstats.st_mtim.tv_sec == fstats.st_mtim.tv_sec && | |||||
cstats.st_mtim.tv_nsec == fstats.st_mtim.tv_nsec) | |||||
{ | |||||
im = imlib_load_image(cfile); | |||||
} | |||||
free(cfile); | |||||
} | |||||
return im; | |||||
} | |||||
void tns_cache_write(thumb_t *t, Bool force) { | |||||
char *cfile, *dirend; | |||||
struct stat cstats, fstats; | |||||
struct timeval times[2]; | |||||
Imlib_Load_Error err = 0; | |||||
if (!t || !t->im || !t->filename) | |||||
return; | |||||
if (stat(t->filename, &fstats)) | |||||
return; | |||||
if ((cfile = tns_cache_filename(t->filename))) { | |||||
if (force || stat(cfile, &cstats) || | |||||
cstats.st_mtim.tv_sec != fstats.st_mtim.tv_sec || | |||||
cstats.st_mtim.tv_nsec != fstats.st_mtim.tv_nsec) | |||||
{ | |||||
if ((dirend = strrchr(cfile, '/'))) { | |||||
*dirend = '\0'; | |||||
err = r_mkdir(cfile); | |||||
*dirend = '/'; | |||||
} | |||||
if (!err) { | |||||
imlib_context_set_image(t->im); | |||||
imlib_image_set_format("png"); | |||||
imlib_save_image_with_error_return(cfile, &err); | |||||
} | |||||
if (err) { | |||||
warn("could not cache thumbnail: %s", t->filename); | |||||
} else { | |||||
TIMESPEC_TO_TIMEVAL(×[0], &fstats.st_atim); | |||||
TIMESPEC_TO_TIMEVAL(×[1], &fstats.st_mtim); | |||||
utimes(cfile, times); | |||||
} | |||||
} | |||||
free(cfile); | |||||
} | |||||
} | |||||
void tns_clear_cache(tns_t *tns) { | |||||
int dirlen, delete; | |||||
char *cfile, *filename, *tpos; | |||||
r_dir_t dir; | |||||
if (!cache_dir) | |||||
return; | |||||
if (r_opendir(&dir, cache_dir)) { | |||||
warn("could not open thumbnail cache directory: %s", cache_dir); | |||||
return; | |||||
} | |||||
dirlen = strlen(cache_dir); | |||||
while ((cfile = r_readdir(&dir))) { | |||||
filename = cfile + dirlen; | |||||
delete = 0; | |||||
if ((tpos = strrchr(filename, '.'))) { | |||||
*tpos = '\0'; | |||||
delete = access(filename, F_OK); | |||||
*tpos = '.'; | |||||
} | |||||
if (delete && unlink(cfile)) | |||||
warn("could not delete cache file: %s", cfile); | |||||
free(cfile); | |||||
} | |||||
r_closedir(&dir); | |||||
} | |||||
void tns_init(tns_t *tns, int cnt) { | void tns_init(tns_t *tns, int cnt) { | ||||
int len; | |||||
char *homedir; | |||||
if (!tns) | if (!tns) | ||||
return; | return; | ||||
if (cnt) { | |||||
tns->thumbs = (thumb_t*) s_malloc(cnt * sizeof(thumb_t)); | |||||
memset(tns->thumbs, 0, cnt * sizeof(thumb_t)); | |||||
} else { | |||||
tns->thumbs = NULL; | |||||
} | |||||
tns->cnt = tns->first = tns->sel = 0; | tns->cnt = tns->first = tns->sel = 0; | ||||
tns->thumbs = (thumb_t*) s_malloc(cnt * sizeof(thumb_t)); | |||||
memset(tns->thumbs, 0, cnt * sizeof(thumb_t)); | |||||
tns->cap = cnt; | tns->cap = cnt; | ||||
tns->dirty = 0; | tns->dirty = 0; | ||||
if ((homedir = getenv("HOME"))) { | |||||
if (cache_dir) | |||||
free(cache_dir); | |||||
len = strlen(homedir) + 10; | |||||
cache_dir = (char*) s_malloc(len * sizeof(char)); | |||||
snprintf(cache_dir, len, "%s/.sxiv", homedir); | |||||
} else { | |||||
warn("could not locate thumbnail cache directory"); | |||||
} | |||||
} | } | ||||
void tns_free(tns_t *tns, win_t *win) { | void tns_free(tns_t *tns, win_t *win) { | ||||
int i; | int i; | ||||
if (!tns || !tns->thumbs) | |||||
if (!tns) | |||||
return; | return; | ||||
for (i = 0; i < tns->cnt; ++i) { | |||||
if (tns->thumbs[i].im) { | |||||
imlib_context_set_image(tns->thumbs[i].im); | |||||
imlib_free_image(); | |||||
if (tns->thumbs) { | |||||
for (i = 0; i < tns->cnt; ++i) { | |||||
if (tns->thumbs[i].im) { | |||||
imlib_context_set_image(tns->thumbs[i].im); | |||||
imlib_free_image(); | |||||
} | |||||
} | } | ||||
free(tns->thumbs); | |||||
tns->thumbs = NULL; | |||||
} | } | ||||
free(tns->thumbs); | |||||
tns->thumbs = NULL; | |||||
if (cache_dir) { | |||||
free(cache_dir); | |||||
cache_dir = NULL; | |||||
} | |||||
} | } | ||||
void tns_load(tns_t *tns, win_t *win, int n, const char *filename) { | void tns_load(tns_t *tns, win_t *win, int n, const char *filename) { | ||||
int w, h; | int w, h; | ||||
int use_cache, cached = 0; | |||||
float z, zw, zh; | float z, zw, zh; | ||||
thumb_t *t; | thumb_t *t; | ||||
Imlib_Image *im; | Imlib_Image *im; | ||||
if (!tns || !win || !filename) | |||||
if (!tns || !tns->thumbs || !win || !filename) | |||||
return; | return; | ||||
if (n >= tns->cap) | if (n >= tns->cap) | ||||
@@ -75,7 +240,12 @@ void tns_load(tns_t *tns, win_t *win, int n, const char *filename) { | |||||
imlib_free_image(); | imlib_free_image(); | ||||
} | } | ||||
if ((im = imlib_load_image(filename))) | |||||
if ((use_cache = tns_cache_enabled())) { | |||||
if ((im = tns_cache_load(filename))) | |||||
cached = 1; | |||||
} | |||||
if (cached || (im = imlib_load_image(filename))) | |||||
imlib_context_set_image(im); | imlib_context_set_image(im); | ||||
else | else | ||||
imlib_context_set_image(im_invalid); | imlib_context_set_image(im_invalid); | ||||
@@ -84,10 +254,12 @@ void tns_load(tns_t *tns, win_t *win, int n, const char *filename) { | |||||
h = imlib_image_get_height(); | h = imlib_image_get_height(); | ||||
if (im) { | if (im) { | ||||
t->filename = filename; | |||||
zw = (float) THUMB_SIZE / (float) w; | zw = (float) THUMB_SIZE / (float) w; | ||||
zh = (float) THUMB_SIZE / (float) h; | zh = (float) THUMB_SIZE / (float) h; | ||||
z = MIN(zw, zh); | z = MIN(zw, zh); | ||||
} else { | } else { | ||||
t->filename = NULL; | |||||
z = 1.0; | z = 1.0; | ||||
} | } | ||||
@@ -99,6 +271,8 @@ void tns_load(tns_t *tns, win_t *win, int n, const char *filename) { | |||||
die("could not allocate memory"); | die("could not allocate memory"); | ||||
if (im) | if (im) | ||||
imlib_free_image_and_decache(); | imlib_free_image_and_decache(); | ||||
if (use_cache && !cached) | |||||
tns_cache_write(t, False); | |||||
tns->dirty = 1; | tns->dirty = 1; | ||||
} | } | ||||
@@ -134,7 +308,10 @@ void tns_render(tns_t *tns, win_t *win) { | |||||
int i, cnt, r, x, y; | int i, cnt, r, x, y; | ||||
thumb_t *t; | thumb_t *t; | ||||
if (!tns || !tns->dirty || !win) | |||||
if (!tns || !tns->thumbs || !win) | |||||
return; | |||||
if (!tns->dirty) | |||||
return; | return; | ||||
win_clear(win); | win_clear(win); | ||||
@@ -182,7 +359,7 @@ void tns_highlight(tns_t *tns, win_t *win, int n, Bool hl) { | |||||
thumb_t *t; | thumb_t *t; | ||||
unsigned long col; | unsigned long col; | ||||
if (!tns || !win) | |||||
if (!tns || !tns->thumbs || !win) | |||||
return; | return; | ||||
if (n >= 0 && n < tns->cnt) { | if (n >= 0 && n < tns->cnt) { | ||||
@@ -205,7 +382,7 @@ void tns_highlight(tns_t *tns, win_t *win, int n, Bool hl) { | |||||
int tns_move_selection(tns_t *tns, win_t *win, tnsdir_t dir) { | int tns_move_selection(tns_t *tns, win_t *win, tnsdir_t dir) { | ||||
int old; | int old; | ||||
if (!tns || !win) | |||||
if (!tns || !tns->thumbs || !win) | |||||
return 0; | return 0; | ||||
old = tns->sel; | old = tns->sel; | ||||
@@ -264,7 +441,10 @@ int tns_translate(tns_t *tns, int x, int y) { | |||||
int n; | int n; | ||||
thumb_t *t; | thumb_t *t; | ||||
if (!tns || x < tns->x || y < tns->y) | |||||
if (!tns || !tns->thumbs) | |||||
return -1; | |||||
if (x < tns->x || y < tns->y) | |||||
return -1; | return -1; | ||||
n = tns->first + (y - tns->y) / thumb_dim * tns->cols + | n = tns->first + (y - tns->y) / thumb_dim * tns->cols + | ||||
@@ -32,6 +32,7 @@ typedef enum { | |||||
typedef struct { | typedef struct { | ||||
Imlib_Image *im; | Imlib_Image *im; | ||||
const char *filename; | |||||
int x; | int x; | ||||
int y; | int y; | ||||
int w; | int w; | ||||
@@ -51,6 +52,8 @@ typedef struct { | |||||
unsigned char dirty; | unsigned char dirty; | ||||
} tns_t; | } tns_t; | ||||
void tns_clear_cache(tns_t*); | |||||
void tns_init(tns_t*, int); | void tns_init(tns_t*, int); | ||||
void tns_free(tns_t*, win_t*); | void tns_free(tns_t*, win_t*); | ||||
@@ -18,11 +18,16 @@ | |||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include <string.h> | #include <string.h> | ||||
#include <sys/types.h> | |||||
#include <sys/stat.h> | |||||
#include <unistd.h> | |||||
#include <errno.h> | |||||
#include "options.h" | #include "options.h" | ||||
#include "util.h" | #include "util.h" | ||||
#define FNAME_LEN 512 | |||||
#define DNAME_CNT 512 | |||||
#define FNAME_LEN 1024 | |||||
void cleanup(); | void cleanup(); | ||||
@@ -78,6 +83,221 @@ void size_readable(float *size, const char **unit) { | |||||
*unit = units[MIN(i, LEN(units) - 1)]; | *unit = units[MIN(i, LEN(units) - 1)]; | ||||
} | } | ||||
char* absolute_path(const char *filename) { | |||||
size_t len; | |||||
char *path = NULL; | |||||
const char *basename; | |||||
char *dirname = NULL; | |||||
char *cwd = NULL; | |||||
char *twd = NULL; | |||||
char *dir; | |||||
char *s; | |||||
if (!filename || *filename == '\0' || *filename == '/') | |||||
return NULL; | |||||
len = FNAME_LEN; | |||||
cwd = (char*) s_malloc(len); | |||||
while (!(s = getcwd(cwd, len)) && errno == ERANGE) { | |||||
len *= 2; | |||||
cwd = (char*) s_realloc(cwd, len); | |||||
} | |||||
if (!s) | |||||
goto error; | |||||
s = strrchr(filename, '/'); | |||||
if (s) { | |||||
len = s - filename; | |||||
dirname = (char*) s_malloc(len + 1); | |||||
strncpy(dirname, filename, len); | |||||
dirname[len] = '\0'; | |||||
basename = s + 1; | |||||
if (chdir(cwd)) | |||||
/* we're not able to come back afterwards */ | |||||
goto error; | |||||
if (chdir(dirname)) | |||||
goto error; | |||||
len = FNAME_LEN; | |||||
twd = (char*) s_malloc(len); | |||||
while (!(s = getcwd(twd, len)) && errno == ERANGE) { | |||||
len *= 2; | |||||
twd = (char*) s_realloc(twd, len); | |||||
} | |||||
if (chdir(cwd)) | |||||
die("could not revert to prior working directory"); | |||||
if (!s) | |||||
goto error; | |||||
dir = twd; | |||||
} else { | |||||
/* only a single filename given */ | |||||
basename = filename; | |||||
dir = cwd; | |||||
} | |||||
len = strlen(dir) + strlen(basename) + 2; | |||||
path = (char*) s_malloc(len); | |||||
snprintf(path, len, "%s/%s", dir, basename); | |||||
goto end; | |||||
error: | |||||
if (path) { | |||||
free(path); | |||||
path = NULL; | |||||
} | |||||
end: | |||||
if (dirname) | |||||
free(dirname); | |||||
if (cwd) | |||||
free(cwd); | |||||
if (twd) | |||||
free(twd); | |||||
return path; | |||||
} | |||||
int r_opendir(r_dir_t *rdir, const char *dirname) { | |||||
if (!rdir || !dirname || !*dirname) | |||||
return -1; | |||||
if (!(rdir->dir = opendir(dirname))) { | |||||
rdir->name = NULL; | |||||
rdir->stack = NULL; | |||||
return -1; | |||||
} | |||||
rdir->stcap = DNAME_CNT; | |||||
rdir->stack = (char**) s_malloc(rdir->stcap * sizeof(char*)); | |||||
rdir->stlen = 0; | |||||
rdir->name = (char*) dirname; | |||||
rdir->d = 0; | |||||
return 0; | |||||
} | |||||
int r_closedir(r_dir_t *rdir) { | |||||
int ret = 0; | |||||
if (!rdir) | |||||
return -1; | |||||
if (rdir->stack) { | |||||
while (rdir->stlen > 0) | |||||
free(rdir->stack[--rdir->stlen]); | |||||
free(rdir->stack); | |||||
rdir->stack = NULL; | |||||
} | |||||
if (rdir->dir) { | |||||
if (!(ret = closedir(rdir->dir))) | |||||
rdir->dir = NULL; | |||||
} | |||||
if (rdir->d && rdir->name) { | |||||
free(rdir->name); | |||||
rdir->name = NULL; | |||||
} | |||||
return ret; | |||||
} | |||||
char* r_readdir(r_dir_t *rdir) { | |||||
size_t len; | |||||
char *filename; | |||||
struct dirent *dentry; | |||||
struct stat fstats; | |||||
if (!rdir || !rdir->dir || !rdir->name) | |||||
return NULL; | |||||
while (1) { | |||||
if (rdir->dir && (dentry = readdir(rdir->dir))) { | |||||
if (!strcmp(dentry->d_name, ".") || !strcmp(dentry->d_name, "..")) | |||||
continue; | |||||
len = strlen(rdir->name) + strlen(dentry->d_name) + 2; | |||||
filename = (char*) s_malloc(len); | |||||
snprintf(filename, len, "%s%s%s", rdir->name, | |||||
rdir->name[strlen(rdir->name)-1] == '/' ? "" : "/", | |||||
dentry->d_name); | |||||
if (!stat(filename, &fstats) && S_ISDIR(fstats.st_mode)) { | |||||
/* put subdirectory on the stack */ | |||||
if (rdir->stlen == rdir->stcap) { | |||||
rdir->stcap *= 2; | |||||
rdir->stack = (char**) s_realloc(rdir->stack, | |||||
rdir->stcap * sizeof(char*)); | |||||
} | |||||
rdir->stack[rdir->stlen++] = filename; | |||||
continue; | |||||
} | |||||
return filename; | |||||
} | |||||
if (rdir->stlen > 0) { | |||||
/* open next subdirectory */ | |||||
closedir(rdir->dir); | |||||
if (rdir->d) | |||||
free(rdir->name); | |||||
rdir->name = rdir->stack[--rdir->stlen]; | |||||
rdir->d = 1; | |||||
if (!(rdir->dir = opendir(rdir->name))) | |||||
warn("could not open directory: %s", rdir->name); | |||||
continue; | |||||
} | |||||
/* no more entries */ | |||||
break; | |||||
} | |||||
return NULL; | |||||
} | |||||
int r_mkdir(const char *path) { | |||||
char *dir, *d; | |||||
struct stat stats; | |||||
int err = 0; | |||||
if (!path || !*path) | |||||
return -1; | |||||
if (!stat(path, &stats)) { | |||||
if (S_ISDIR(stats.st_mode)) { | |||||
return 0; | |||||
} else { | |||||
warn("not a directory: %s", path); | |||||
return -1; | |||||
} | |||||
} | |||||
d = dir = (char*) s_malloc(strlen(path) + 1); | |||||
strcpy(dir, path); | |||||
while (d != NULL && !err) { | |||||
d = strchr(d + 1, '/'); | |||||
if (d != NULL) | |||||
*d = '\0'; | |||||
if (access(dir, F_OK) && errno == ENOENT) { | |||||
if (mkdir(dir, 0755)) { | |||||
warn("could not create directory: %s", dir); | |||||
err = -1; | |||||
} | |||||
} else if (stat(dir, &stats) || !S_ISDIR(stats.st_mode)) { | |||||
warn("not a directory: %s", dir); | |||||
err = -1; | |||||
} | |||||
if (d != NULL) | |||||
*d = '/'; | |||||
} | |||||
free(dir); | |||||
return err; | |||||
} | |||||
char* readline(FILE *stream) { | char* readline(FILE *stream) { | ||||
size_t len; | size_t len; | ||||
char *buf, *s, *end; | char *buf, *s, *end; | ||||
@@ -21,6 +21,7 @@ | |||||
#include <stdio.h> | #include <stdio.h> | ||||
#include <stdarg.h> | #include <stdarg.h> | ||||
#include <dirent.h> | |||||
#define ABS(a) ((a) < 0 ? (-(a)) : (a)) | #define ABS(a) ((a) < 0 ? (-(a)) : (a)) | ||||
#define MIN(a,b) ((a) < (b) ? (a) : (b)) | #define MIN(a,b) ((a) < (b) ? (a) : (b)) | ||||
@@ -30,6 +31,21 @@ | |||||
#define TV_TO_DOUBLE(x) ((double) ((x).tv_sec) + 0.000001 * \ | #define TV_TO_DOUBLE(x) ((double) ((x).tv_sec) + 0.000001 * \ | ||||
(double) ((x).tv_usec)) | (double) ((x).tv_usec)) | ||||
#define TIMESPEC_TO_TIMEVAL(tv, ts) { \ | |||||
(tv)->tv_sec = (ts)->tv_sec; \ | |||||
(tv)->tv_usec = (ts)->tv_nsec / 1000; \ | |||||
} | |||||
typedef struct { | |||||
DIR *dir; | |||||
char *name; | |||||
int d; | |||||
char **stack; | |||||
int stcap; | |||||
int stlen; | |||||
} r_dir_t; | |||||
void* s_malloc(size_t); | void* s_malloc(size_t); | ||||
void* s_realloc(void*, size_t); | void* s_realloc(void*, size_t); | ||||
@@ -38,6 +54,13 @@ void die(const char*, ...); | |||||
void size_readable(float*, const char**); | void size_readable(float*, const char**); | ||||
char* absolute_path(const char*); | |||||
int r_opendir(r_dir_t*, const char*); | |||||
int r_closedir(r_dir_t*); | |||||
char* r_readdir(r_dir_t*); | |||||
int r_mkdir(const char *); | |||||
char* readline(FILE*); | char* readline(FILE*); | ||||
#endif /* UTIL_H */ | #endif /* UTIL_H */ |