Merge branch 'tcache'

Conflicts:
	main.c
This commit is contained in:
Bert 2011-04-11 08:52:07 +02:00
commit bac610ddc4
10 changed files with 506 additions and 99 deletions

View File

@ -1,6 +1,6 @@
all: sxiv
VERSION=git-20110407
VERSION=git-20110408
CC?=gcc
PREFIX?=/usr/local

View File

@ -36,6 +36,7 @@ sxiv supports the following command-line options:
-a Display all given files, do not filter out unsupported files
(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
-F Use size-hints to make the window fixed/floating
-f Start in fullscreen mode

110
main.c
View File

@ -19,7 +19,6 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/time.h>
@ -48,7 +47,6 @@ typedef enum {
void update_title();
int check_append(const char*);
void read_dir_rec(const char*);
void run();
appmode_t mode;
@ -56,7 +54,6 @@ img_t img;
tns_t tns;
win_t win;
#define DNAME_CNT 512
#define FNAME_CNT 1024
const char **filenames;
int filecnt, fileidx;
@ -95,13 +92,24 @@ int load_image(int new) {
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 i;
int i, start;
const char *filename;
struct stat fstats;
r_dir_t dir;
parse_options(argc, argv);
if (options->clean_cache) {
tns_init(&tns, 0);
tns_clear_cache(&tns);
exit(0);
}
if (!options->filecnt) {
print_usage();
exit(1);
@ -123,13 +131,26 @@ int main(int argc, char **argv) {
} else {
for (i = 0; i < options->filecnt; ++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);
} 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
int run_command(const char *cline, Bool reload) {
int fncnt, fnlen;

View File

@ -31,7 +31,7 @@ options_t _options;
const options_t *options = (const options_t*) &_options;
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() {
@ -53,9 +53,10 @@ void parse_options(int argc, char **argv) {
_options.all = 0;
_options.quiet = 0;
_options.clean_cache = 0;
_options.recursive = 0;
while ((opt = getopt(argc, argv, "adFfg:hpqrstvZz:")) != -1) {
while ((opt = getopt(argc, argv, "aCdFfg:hpqrstvZz:")) != -1) {
switch (opt) {
case '?':
print_usage();
@ -63,6 +64,9 @@ void parse_options(int argc, char **argv) {
case 'a':
_options.all = 1;
break;
case 'C':
_options.clean_cache = 1;
break;
case 'd':
_options.scalemode = SCALE_DOWN;
break;

View File

@ -37,6 +37,7 @@ typedef struct {
unsigned char all;
unsigned char quiet;
unsigned char clean_cache;
unsigned char recursive;
} options_t;

25
sxiv.1
View File

@ -3,7 +3,7 @@
sxiv \- Simple (or small or suckless) X Image Viewer
.SH SYNOPSIS
.B sxiv
.RB [ \-adFfhpqrstvZ ]
.RB [ \-aCdFfhpqrstvZ ]
.RB [ \-g
.IR GEOMETRY ]
.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
small previews is displayed, making it easy to choose an image to open.
.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
manager.
.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
large files of slow loadable types, e.g. gif and progressive jpg.
.TP
.B \-C
Remove all orphaned cache files from the thumbnail cache directory and exit.
.TP
.B \-d
Scale all images to 100%, but fit large images into window.
.TP
@ -191,6 +197,23 @@ Pan image left.
.TP
.B Shift+ScrollDown
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
.EX
Bert Muennich <ber.t at gmx.com>

210
thumbs.c
View File

@ -18,49 +18,214 @@
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "config.h"
#include "thumbs.h"
#include "util.h"
extern Imlib_Image *im_invalid;
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(&times[0], &fstats.st_atim);
TIMESPEC_TO_TIMEVAL(&times[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) {
int len;
char *homedir;
if (!tns)
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->thumbs = (thumb_t*) s_malloc(cnt * sizeof(thumb_t));
memset(tns->thumbs, 0, cnt * sizeof(thumb_t));
tns->cap = cnt;
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) {
int i;
if (!tns || !tns->thumbs)
if (!tns)
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) {
int w, h;
int use_cache, cached = 0;
float z, zw, zh;
thumb_t *t;
Imlib_Image *im;
if (!tns || !win || !filename)
if (!tns || !tns->thumbs || !win || !filename)
return;
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();
}
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);
else
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();
if (im) {
t->filename = filename;
zw = (float) THUMB_SIZE / (float) w;
zh = (float) THUMB_SIZE / (float) h;
z = MIN(zw, zh);
} else {
t->filename = NULL;
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");
if (im)
imlib_free_image_and_decache();
if (use_cache && !cached)
tns_cache_write(t, False);
tns->dirty = 1;
}
@ -134,7 +308,10 @@ void tns_render(tns_t *tns, win_t *win) {
int i, cnt, r, x, y;
thumb_t *t;
if (!tns || !tns->dirty || !win)
if (!tns || !tns->thumbs || !win)
return;
if (!tns->dirty)
return;
win_clear(win);
@ -182,7 +359,7 @@ void tns_highlight(tns_t *tns, win_t *win, int n, Bool hl) {
thumb_t *t;
unsigned long col;
if (!tns || !win)
if (!tns || !tns->thumbs || !win)
return;
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 old;
if (!tns || !win)
if (!tns || !tns->thumbs || !win)
return 0;
old = tns->sel;
@ -264,7 +441,10 @@ int tns_translate(tns_t *tns, int x, int y) {
int n;
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;
n = tns->first + (y - tns->y) / thumb_dim * tns->cols +

View File

@ -32,6 +32,7 @@ typedef enum {
typedef struct {
Imlib_Image *im;
const char *filename;
int x;
int y;
int w;
@ -51,6 +52,8 @@ typedef struct {
unsigned char dirty;
} tns_t;
void tns_clear_cache(tns_t*);
void tns_init(tns_t*, int);
void tns_free(tns_t*, win_t*);

222
util.c
View File

@ -18,11 +18,16 @@
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include "options.h"
#include "util.h"
#define FNAME_LEN 512
#define DNAME_CNT 512
#define FNAME_LEN 1024
void cleanup();
@ -78,6 +83,221 @@ void size_readable(float *size, const char **unit) {
*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) {
size_t len;
char *buf, *s, *end;

23
util.h
View File

@ -21,6 +21,7 @@
#include <stdio.h>
#include <stdarg.h>
#include <dirent.h>
#define ABS(a) ((a) < 0 ? (-(a)) : (a))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
@ -30,6 +31,21 @@
#define TV_TO_DOUBLE(x) ((double) ((x).tv_sec) + 0.000001 * \
(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_realloc(void*, size_t);
@ -38,6 +54,13 @@ void die(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*);
#endif /* UTIL_H */