2 * gstfs - a gstreamer filesystem
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",
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
);
78 * Release a previously allocated gstfs_file_info object.
80 void put_file_info(struct gstfs_file_info
*fi
)
83 g_free(fi
->src_filename
);
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)
97 filename
= g_strconcat(filename
, replace
, NULL
);
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
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
);
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
))
145 pthread_mutex_lock(&mount_info
.cache_mutex
);
146 ret
= g_hash_table_lookup(mount_info
.file_cache
, path
);
149 ret
= get_file_info(path
);
153 g_hash_table_replace(mount_info
.file_cache
, ret
->filename
, ret
);
156 // move to end of LRU
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
;
166 pthread_mutex_unlock(&mount_info
.cache_mutex
);
171 * Given a filename from the fuse mount, return the corresponding filename
174 static char *get_source_path(const char *filename
)
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
);
183 int gstfs_statfs(const char *path
, struct statvfs
*buf
)
187 source_path
= get_source_path(path
);
188 if (statvfs(source_path
, buf
))
195 int gstfs_getattr(const char *path
, struct stat
*stbuf
)
199 struct gstfs_file_info
*converted
;
201 source_path
= get_source_path(path
);
203 if (stat(source_path
, stbuf
))
205 else if ((converted
= gstfs_lookup(path
)))
206 stbuf
->st_size
= converted
->len
;
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
);
226 memcpy(&info
->buf
[info
->len
], buf
, size
);
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
);
240 pthread_mutex_lock(&info
->mutex
);
243 transcode(mount_info
.pipeline
, info
->src_filename
, read_cb
, info
);
245 if (info
->len
<= offset
)
248 count
= min(info
->len
- offset
, size
);
250 memcpy(buf
, &info
->buf
[offset
], count
);
253 pthread_mutex_unlock(&info
->mutex
);
257 int gstfs_open(const char *path
, struct fuse_file_info
*fi
)
259 struct gstfs_file_info
*info
= gstfs_lookup(path
);
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
;
276 source_path
= get_source_path(path
);
277 dir
= opendir(source_path
);
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);
295 static struct fuse_operations gstfs_opers
= {
296 .readdir
= gstfs_readdir
,
297 .statfs
= gstfs_statfs
,
298 .getattr
= gstfs_getattr
,
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),
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)
320 if (!mount_info
.src_mnt
||
321 !mount_info
.src_ext
||
322 !mount_info
.dst_ext
||
323 !mount_info
.pipeline
)
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
);