Add some comments
[gstfs.git] / gstfs.c
blob7e551187418f3ab27af6ebbe6802a115d85556bb
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 <fuse.h>
11 #include <errno.h>
12 #include <glib.h>
13 #include <gst/gst.h>
14 #include "xcode.h"
16 #define min(a,b) ((a)<(b)?(a):(b))
17 #define max(a,b) ((a)>(b)?(a):(b))
19 /* per-mount options and data structures */
20 struct gstfs_mount_info
22 pthread_mutex_t cache_mutex; /* protects file_cache, cache_lru accesses */
23 GHashTable *file_cache; /* cache of transcoded audio */
24 GQueue *cache_lru; /* queue of items in LRU order */
25 int max_cache_entries; /* max # of entries in the cache */
26 char *source_mount; /* directory we are mirroring */
27 char *src_ext; /* extension of files we transcode */
28 char *dest_ext; /* extension of target files */
31 /* This stuff is stored into file_cache by filename */
32 struct gstfs_file_info
34 char *filename; /* hash key */
35 char *source_filename; /* filename in other mount */
36 pthread_mutex_t mutex; /* protects this file info */
37 size_t len; /* size of file */
38 size_t alloc_len; /* allocated size of buf */
39 char *buf; /* completely converted file */
40 GList *list_node; /* pointer for cache_lru */
44 static struct gstfs_mount_info mount_info;
46 static char *get_source_path(const char *filename);
49 * Create a new gstfs_file_info object using the specified destination
50 * filename.
52 struct gstfs_file_info *get_file_info(const char *filename)
54 struct gstfs_file_info *fi;
56 fi = calloc(1, sizeof(struct gstfs_file_info));
57 fi->filename = g_strdup(filename);
58 fi->source_filename = get_source_path(filename);
59 pthread_mutex_init(&fi->mutex, NULL);
60 return fi;
64 * Release a previously allocated gstfs_file_info object
66 void put_file_info(struct gstfs_file_info *fi)
68 g_free(fi->filename);
69 g_free(fi->source_filename);
70 free(fi);
74 * Given a filename with extension "search", return a possibly reallocated
75 * string with "replace" on the end.
77 char *replace_ext(char *filename, char *search, char *replace)
79 char *ext = strrchr(filename, '.');
80 if (ext && strcmp(ext+1, search) == 0)
82 *(ext+1) = 0;
83 filename = g_strconcat(filename, replace, NULL);
85 return filename;
89 * Return true if filename has extension dest_ext
91 int is_target_type(const char *filename)
93 char *ext = strrchr(filename, '.');
94 return (ext && strcmp(ext+1, mount_info.dest_ext) == 0);
97 /*
98 * Remove items from the file cache until below the maximum.
99 * This is relatively quick since we can find elements by looking at the
100 * head of the lru list and then do a single hash lookup to remove from
101 * the hash table.
103 * Called with cache_mutex held.
105 static void expire_cache()
107 struct gstfs_file_info *fi;
109 while (g_queue_get_length(mount_info.cache_lru) >
110 mount_info.max_cache_entries)
112 fi = (struct gstfs_file_info *) g_queue_pop_head(mount_info.cache_lru);
113 g_hash_table_remove(mount_info.file_cache, fi);
114 put_file_info(fi);
119 * If the path represents a file in the mirror filesystem, then
120 * look for it in the cache. If not, create a new file info.
122 * If it isn't a mirror file, return NULL.
124 static struct gstfs_file_info *gstfs_lookup(const char *path)
126 struct gstfs_file_info *ret;
128 if (!is_target_type(path))
129 return NULL;
131 pthread_mutex_lock(&mount_info.cache_mutex);
132 ret = g_hash_table_lookup(mount_info.file_cache, path);
133 if (!ret)
135 ret = get_file_info(path);
136 if (!ret)
137 goto out;
139 g_hash_table_replace(mount_info.file_cache, ret->filename, ret);
142 // move to end of LRU
143 if (ret->list_node)
144 g_queue_unlink(mount_info.cache_lru, ret->list_node);
146 g_queue_push_tail(mount_info.cache_lru, ret);
147 ret->list_node = mount_info.cache_lru->tail;
149 expire_cache();
151 out:
152 pthread_mutex_unlock(&mount_info.cache_mutex);
153 return ret;
157 * Given a filename from the fuse mount, return the corresponding filename
158 * in the mirror.
160 static char *get_source_path(const char *filename)
162 char *source;
164 source = g_strdup_printf("%s%s", mount_info.source_mount, filename);
165 source = replace_ext(source, mount_info.dest_ext, mount_info.src_ext);
166 return source;
169 int gstfs_statfs(const char *path, struct statvfs *buf)
171 char *source_path;
173 source_path = get_source_path(path);
174 if (statvfs(source_path, buf))
175 return -errno;
177 g_free(source_path);
178 return 0;
181 int gstfs_getattr(const char *path, struct stat *stbuf)
183 int ret = 0;
184 char *source_path;
185 struct gstfs_file_info *converted;
187 source_path = get_source_path(path);
189 if (stat(source_path, stbuf))
190 ret = -errno;
191 else if ((converted = gstfs_lookup(path)))
192 stbuf->st_size = converted->len;
194 g_free(source_path);
195 return ret;
198 static int read_cb(char *buf, size_t size, void *data)
200 struct gstfs_file_info *info = (struct gstfs_file_info *) data;
202 size_t newsz = info->len + size;
204 if (info->alloc_len < newsz)
206 info->alloc_len = max(info->alloc_len * 2, newsz);
207 info->buf = realloc(info->buf, info->alloc_len);
208 if (!info->buf)
209 return -ENOMEM;
212 memcpy(&info->buf[info->len], buf, size);
213 info->len += size;
214 return 0;
217 int gstfs_read(const char *path, char *buf, size_t size, off_t offset,
218 struct fuse_file_info *fi)
220 struct gstfs_file_info *info = gstfs_lookup(path);
221 size_t count = 0;
223 if (!info)
224 return -ENOENT;
226 pthread_mutex_lock(&info->mutex);
228 if (!info->buf)
229 transcode(info->source_filename, read_cb, info);
231 if (info->len <= offset)
232 goto out;
234 count = min(info->len - offset, size);
236 memcpy(buf, &info->buf[offset], count);
238 out:
239 pthread_mutex_unlock(&info->mutex);
240 return count;
243 int gstfs_open(const char *path, struct fuse_file_info *fi)
245 struct gstfs_file_info *info = gstfs_lookup(path);
246 if (!info)
247 return -ENOENT;
249 return 0;
253 * copy all entries from source mount
255 int gstfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
256 off_t offset, struct fuse_file_info *fi)
258 struct dirent *dirent;
259 DIR *dir;
260 char *source_path;
262 source_path = get_source_path(path);
263 dir = opendir(source_path);
265 if (!dir)
266 return -ENOENT;
268 while ((dirent = readdir(dir)))
270 char *s = g_strdup(dirent->d_name);
271 s = replace_ext(s, mount_info.src_ext, mount_info.dest_ext);
272 filler(buf, s, NULL, 0);
274 g_free(s);
276 closedir(dir);
278 return 0;
281 static struct fuse_operations gstfs_ops = {
282 .readdir = gstfs_readdir,
283 .statfs = gstfs_statfs,
284 .getattr = gstfs_getattr,
285 .open = gstfs_open,
286 .read = gstfs_read
289 int main(int argc, char *argv[])
291 pthread_mutex_init(&mount_info.cache_mutex, NULL);
292 mount_info.file_cache = g_hash_table_new(g_str_hash, g_str_equal);
293 mount_info.cache_lru = g_queue_new();
294 mount_info.max_cache_entries = 50;
295 mount_info.source_mount = "ogg";
296 mount_info.src_ext = "ogg";
297 mount_info.dest_ext = "mp3";
299 gst_init(&argc, &argv);
300 return fuse_main(argc, argv, &gstfs_ops, NULL);