Add a README file
[gstfs.git] / gstfs.c
blob8063ae6e0f6a276fdf1483e1c1ec5baf0c03b33e
1 /*
2 * gstfs - a gstreamer filesystem
3 */
4 #include <sys/types.h>
5 #include <dirent.h>
6 #include <pthread.h>
7 #include <stdio.h>
8 #include <string.h>
9 #include <stdlib.h>
10 #include <stddef.h>
11 #include <fuse.h>
12 #include <errno.h>
13 #include <glib.h>
14 #include <gst/gst.h>
15 #include "xcode.h"
17 #define min(a,b) ((a)<(b)?(a):(b))
18 #define max(a,b) ((a)>(b)?(a):(b))
20 #define GSTFS_OPT_KEY(templ, elem, key) \
21 { templ, offsetof(struct gstfs_mount_info, elem), key }
23 /* per-mount options and data structures */
24 struct gstfs_mount_info
26 pthread_mutex_t cache_mutex; /* protects file_cache, cache_lru accesses */
27 GHashTable *file_cache; /* cache of transcoded audio */
28 GQueue *cache_lru; /* queue of items in LRU order */
29 int max_cache_entries; /* max # of entries in the cache */
30 char *src_mnt; /* directory we are mirroring */
31 char *src_ext; /* extension of files we transcode */
32 char *dst_ext; /* extension of target files */
33 char *pipeline; /* gstreamer pipeline */
36 /* This stuff is stored into file_cache by filename */
37 struct gstfs_file_info
39 char *filename; /* hash key */
40 char *src_filename; /* filename in other mount */
41 pthread_mutex_t mutex; /* protects this file info */
42 size_t len; /* size of file */
43 size_t alloc_len; /* allocated size of buf */
44 char *buf; /* completely converted file */
45 GList *list_node; /* pointer for cache_lru */
47 static char *get_source_path(const char *filename);
49 static struct gstfs_mount_info mount_info;
51 void usage(const char *prog)
53 printf("Usage: %s -o [options] mount_point\n\n"
54 "where options can be:\n"
55 " src=[source directory] (required)\n"
56 " src_ext=[mp3|ogg|...] (required)\n"
57 " dst_ext=[mp3|ogg|...] (required)\n"
58 " pipeline=[gst pipeline] (required)\n"
59 " ncache=[0-9]* (optional)\n",
60 prog);
64 * Create a new gstfs_file_info object using the specified destination file.
66 struct gstfs_file_info *get_file_info(const char *filename)
68 struct gstfs_file_info *fi;
70 fi = calloc(1, sizeof(struct gstfs_file_info));
71 fi->filename = g_strdup(filename);
72 fi->src_filename = get_source_path(filename);
73 pthread_mutex_init(&fi->mutex, NULL);
74 return fi;
78 * Release a previously allocated gstfs_file_info object.
80 void put_file_info(struct gstfs_file_info *fi)
82 g_free(fi->filename);
83 g_free(fi->src_filename);
84 free(fi);
88 * Given a filename with extension "search", return a possibly reallocated
89 * string with "replace" on the end.
91 char *replace_ext(char *filename, char *search, char *replace)
93 char *ext = strrchr(filename, '.');
94 if (ext && strcmp(ext+1, search) == 0)
96 *(ext+1) = 0;
97 filename = g_strconcat(filename, replace, NULL);
99 return filename;
103 * Return true if filename has extension dst_ext.
105 int is_target_type(const char *filename)
107 char *ext = strrchr(filename, '.');
108 return (ext && strcmp(ext+1, mount_info.dst_ext) == 0);
112 * Remove items from the file cache until below the maximum.
113 * This is relatively quick since we can find elements by looking at the
114 * head of the lru list and then do a single hash lookup to remove from
115 * the hash table.
117 * Called with cache_mutex held.
119 static void expire_cache()
121 struct gstfs_file_info *fi;
123 while (g_queue_get_length(mount_info.cache_lru) >
124 mount_info.max_cache_entries)
126 fi = (struct gstfs_file_info *) g_queue_pop_head(mount_info.cache_lru);
127 g_hash_table_remove(mount_info.file_cache, fi);
128 put_file_info(fi);
133 * If the path represents a file in the mirror filesystem, then
134 * look for it in the cache. If not, create a new file info.
136 * If it isn't a mirror file, return NULL.
138 static struct gstfs_file_info *gstfs_lookup(const char *path)
140 struct gstfs_file_info *ret;
142 if (!is_target_type(path))
143 return NULL;
145 pthread_mutex_lock(&mount_info.cache_mutex);
146 ret = g_hash_table_lookup(mount_info.file_cache, path);
147 if (!ret)
149 ret = get_file_info(path);
150 if (!ret)
151 goto out;
153 g_hash_table_replace(mount_info.file_cache, ret->filename, ret);
156 // move to end of LRU
157 if (ret->list_node)
158 g_queue_unlink(mount_info.cache_lru, ret->list_node);
160 g_queue_push_tail(mount_info.cache_lru, ret);
161 ret->list_node = mount_info.cache_lru->tail;
163 expire_cache();
165 out:
166 pthread_mutex_unlock(&mount_info.cache_mutex);
167 return ret;
171 * Given a filename from the fuse mount, return the corresponding filename
172 * in the mirror.
174 static char *get_source_path(const char *filename)
176 char *source;
178 source = g_strdup_printf("%s%s", mount_info.src_mnt, filename);
179 source = replace_ext(source, mount_info.dst_ext, mount_info.src_ext);
180 return source;
183 int gstfs_statfs(const char *path, struct statvfs *buf)
185 char *source_path;
187 source_path = get_source_path(path);
188 if (statvfs(source_path, buf))
189 return -errno;
191 g_free(source_path);
192 return 0;
195 int gstfs_getattr(const char *path, struct stat *stbuf)
197 int ret = 0;
198 char *source_path;
199 struct gstfs_file_info *converted;
201 source_path = get_source_path(path);
203 if (stat(source_path, stbuf))
204 ret = -errno;
205 else if ((converted = gstfs_lookup(path)))
206 stbuf->st_size = converted->len;
208 g_free(source_path);
209 return ret;
212 static int read_cb(char *buf, size_t size, void *data)
214 struct gstfs_file_info *info = (struct gstfs_file_info *) data;
216 size_t newsz = info->len + size;
218 if (info->alloc_len < newsz)
220 info->alloc_len = max(info->alloc_len * 2, newsz);
221 info->buf = realloc(info->buf, info->alloc_len);
222 if (!info->buf)
223 return -ENOMEM;
226 memcpy(&info->buf[info->len], buf, size);
227 info->len += size;
228 return 0;
231 int gstfs_read(const char *path, char *buf, size_t size, off_t offset,
232 struct fuse_file_info *fi)
234 struct gstfs_file_info *info = gstfs_lookup(path);
235 size_t count = 0;
237 if (!info)
238 return -ENOENT;
240 pthread_mutex_lock(&info->mutex);
242 if (!info->buf)
243 transcode(mount_info.pipeline, info->src_filename, read_cb, info);
245 if (info->len <= offset)
246 goto out;
248 count = min(info->len - offset, size);
250 memcpy(buf, &info->buf[offset], count);
252 out:
253 pthread_mutex_unlock(&info->mutex);
254 return count;
257 int gstfs_open(const char *path, struct fuse_file_info *fi)
259 struct gstfs_file_info *info = gstfs_lookup(path);
260 if (!info)
261 return -ENOENT;
263 return 0;
267 * Copy all entries from source mount, replacing extensions along the way.
269 int gstfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
270 off_t offset, struct fuse_file_info *fi)
272 struct dirent *dirent;
273 DIR *dir;
274 char *source_path;
276 source_path = get_source_path(path);
277 dir = opendir(source_path);
279 if (!dir)
280 return -ENOENT;
282 while ((dirent = readdir(dir)))
284 char *s = g_strdup(dirent->d_name);
285 s = replace_ext(s, mount_info.src_ext, mount_info.dst_ext);
286 filler(buf, s, NULL, 0);
288 g_free(s);
290 closedir(dir);
292 return 0;
295 static struct fuse_operations gstfs_opers = {
296 .readdir = gstfs_readdir,
297 .statfs = gstfs_statfs,
298 .getattr = gstfs_getattr,
299 .open = gstfs_open,
300 .read = gstfs_read
303 static struct fuse_opt gstfs_opts[] = {
304 GSTFS_OPT_KEY("src=%s", src_mnt, 0),
305 GSTFS_OPT_KEY("src_ext=%s", src_ext, 0),
306 GSTFS_OPT_KEY("dst_ext=%s", dst_ext, 0),
307 GSTFS_OPT_KEY("ncache=%d", max_cache_entries, 0),
308 GSTFS_OPT_KEY("pipeline=%s", pipeline, 0),
309 FUSE_OPT_END
313 int main(int argc, char *argv[])
315 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
317 if (fuse_opt_parse(&args, &mount_info, gstfs_opts, NULL) == -1)
318 return -1;
320 if (!mount_info.src_mnt ||
321 !mount_info.src_ext ||
322 !mount_info.dst_ext ||
323 !mount_info.pipeline)
325 usage(argv[0]);
326 return -1;
329 if (mount_info.max_cache_entries == 0)
330 mount_info.max_cache_entries = 50;
332 pthread_mutex_init(&mount_info.cache_mutex, NULL);
333 mount_info.file_cache = g_hash_table_new(g_str_hash, g_str_equal);
334 mount_info.cache_lru = g_queue_new();
336 gst_init(&argc, &argv);
337 return fuse_main(args.argc, args.argv, &gstfs_opers, NULL);