2 * gstfs - a gstreamer filesystem
18 #define min(a,b) ((a)<(b)?(a):(b))
19 #define max(a,b) ((a)>(b)?(a):(b))
21 #define GSTFS_OPT_KEY(templ, elem, key) \
22 { templ, offsetof(struct gstfs_mount_info, elem), key }
24 /* per-mount options and data structures */
25 struct gstfs_mount_info
27 pthread_mutex_t cache_mutex
; /* protects file_cache, cache_lru accesses */
28 GHashTable
*file_cache
; /* cache of transcoded audio */
29 GQueue
*cache_lru
; /* queue of items in LRU order */
30 int max_cache_entries
; /* max # of entries in the cache */
31 char *src_mnt
; /* directory we are mirroring */
32 char *src_ext
; /* extension of files we transcode */
33 char *dst_ext
; /* extension of target files */
34 char *pipeline
; /* gstreamer pipeline */
37 /* This stuff is stored into file_cache by filename */
38 struct gstfs_file_info
40 char *filename
; /* hash key */
41 char *src_filename
; /* filename in other mount */
42 pthread_mutex_t mutex
; /* protects this file info */
43 size_t len
; /* size of file */
44 size_t alloc_len
; /* allocated size of buf */
45 char *buf
; /* completely converted file */
46 GList
*list_node
; /* pointer for cache_lru */
48 static char *get_source_path(const char *filename
);
50 static struct gstfs_mount_info mount_info
;
52 void usage(const char *prog
)
54 printf("Usage: %s -o [options] mount_point\n\n"
55 "where options can be:\n"
56 " src=[source directory] (required)\n"
57 " src_ext=[mp3|ogg|...] (required)\n"
58 " dst_ext=[mp3|ogg|...] (required)\n"
59 " pipeline=[gst pipeline] (required)\n"
60 " ncache=[0-9]* (optional)\n",
65 * Create a new gstfs_file_info object using the specified destination file.
67 struct gstfs_file_info
*get_file_info(const char *filename
)
69 struct gstfs_file_info
*fi
;
71 fi
= calloc(1, sizeof(struct gstfs_file_info
));
72 fi
->filename
= g_strdup(filename
);
73 fi
->src_filename
= get_source_path(filename
);
74 pthread_mutex_init(&fi
->mutex
, NULL
);
79 * Release a previously allocated gstfs_file_info object.
81 void put_file_info(struct gstfs_file_info
*fi
)
84 g_free(fi
->src_filename
);
89 * Given a filename with extension "search", return a possibly reallocated
90 * string with "replace" on the end.
92 char *replace_ext(char *filename
, char *search
, char *replace
)
94 char *ext
= strrchr(filename
, '.');
95 if (ext
&& strcmp(ext
+1, search
) == 0)
98 filename
= g_strconcat(filename
, replace
, NULL
);
104 * Return true if filename has extension dst_ext.
106 int is_target_type(const char *filename
)
108 char *ext
= strrchr(filename
, '.');
109 return (ext
&& strcmp(ext
+1, mount_info
.dst_ext
) == 0);
113 * Remove items from the file cache until below the maximum.
114 * This is relatively quick since we can find elements by looking at the
115 * head of the lru list and then do a single hash lookup to remove from
118 * Called with cache_mutex held.
120 static void expire_cache()
122 struct gstfs_file_info
*fi
;
124 while (g_queue_get_length(mount_info
.cache_lru
) >
125 mount_info
.max_cache_entries
)
127 fi
= (struct gstfs_file_info
*) g_queue_pop_head(mount_info
.cache_lru
);
128 g_hash_table_remove(mount_info
.file_cache
, fi
);
134 * If the path represents a file in the mirror filesystem, then
135 * look for it in the cache. If not, create a new file info.
137 * If it isn't a mirror file, return NULL.
139 static struct gstfs_file_info
*gstfs_lookup(const char *path
)
141 struct gstfs_file_info
*ret
;
143 if (!is_target_type(path
))
146 pthread_mutex_lock(&mount_info
.cache_mutex
);
147 ret
= g_hash_table_lookup(mount_info
.file_cache
, path
);
150 ret
= get_file_info(path
);
154 g_hash_table_replace(mount_info
.file_cache
, ret
->filename
, ret
);
157 // move to end of LRU
159 g_queue_unlink(mount_info
.cache_lru
, ret
->list_node
);
161 g_queue_push_tail(mount_info
.cache_lru
, ret
);
162 ret
->list_node
= mount_info
.cache_lru
->tail
;
167 pthread_mutex_unlock(&mount_info
.cache_mutex
);
172 * Given a filename from the fuse mount, return the corresponding filename
175 static char *get_source_path(const char *filename
)
179 source
= g_strdup_printf("%s%s", mount_info
.src_mnt
, filename
);
180 source
= replace_ext(source
, mount_info
.dst_ext
, mount_info
.src_ext
);
184 static char *canonize(const char *cwd
, const char *filename
)
186 if (filename
[0] == '/')
187 return g_strdup(filename
);
189 return g_strdup_printf("%s/%s", cwd
, filename
);
192 int gstfs_statfs(const char *path
, struct statvfs
*buf
)
196 source_path
= get_source_path(path
);
197 if (statvfs(source_path
, buf
))
204 int gstfs_getattr(const char *path
, struct stat
*stbuf
)
208 struct gstfs_file_info
*converted
;
210 source_path
= get_source_path(path
);
212 if (stat(source_path
, stbuf
))
214 else if ((converted
= gstfs_lookup(path
)))
215 stbuf
->st_size
= converted
->len
;
221 static int read_cb(char *buf
, size_t size
, void *data
)
223 struct gstfs_file_info
*info
= (struct gstfs_file_info
*) data
;
225 size_t newsz
= info
->len
+ size
;
227 if (info
->alloc_len
< newsz
)
229 info
->alloc_len
= max(info
->alloc_len
* 2, newsz
);
230 info
->buf
= realloc(info
->buf
, info
->alloc_len
);
235 memcpy(&info
->buf
[info
->len
], buf
, size
);
240 int gstfs_read(const char *path
, char *buf
, size_t size
, off_t offset
,
241 struct fuse_file_info
*fi
)
243 struct gstfs_file_info
*info
= gstfs_lookup(path
);
249 pthread_mutex_lock(&info
->mutex
);
252 transcode(mount_info
.pipeline
, info
->src_filename
, read_cb
, info
);
254 if (info
->len
<= offset
)
257 count
= min(info
->len
- offset
, size
);
259 memcpy(buf
, &info
->buf
[offset
], count
);
262 pthread_mutex_unlock(&info
->mutex
);
266 int gstfs_open(const char *path
, struct fuse_file_info
*fi
)
268 struct gstfs_file_info
*info
= gstfs_lookup(path
);
276 * Copy all entries from source mount, replacing extensions along the way.
278 int gstfs_readdir(const char *path
, void *buf
, fuse_fill_dir_t filler
,
279 off_t offset
, struct fuse_file_info
*fi
)
281 struct dirent
*dirent
;
285 source_path
= get_source_path(path
);
286 dir
= opendir(source_path
);
291 while ((dirent
= readdir(dir
)))
293 char *s
= g_strdup(dirent
->d_name
);
294 s
= replace_ext(s
, mount_info
.src_ext
, mount_info
.dst_ext
);
295 filler(buf
, s
, NULL
, 0);
304 static struct fuse_operations gstfs_opers
= {
305 .readdir
= gstfs_readdir
,
306 .statfs
= gstfs_statfs
,
307 .getattr
= gstfs_getattr
,
312 static struct fuse_opt gstfs_opts
[] = {
313 GSTFS_OPT_KEY("src=%s", src_mnt
, 0),
314 GSTFS_OPT_KEY("src_ext=%s", src_ext
, 0),
315 GSTFS_OPT_KEY("dst_ext=%s", dst_ext
, 0),
316 GSTFS_OPT_KEY("ncache=%d", max_cache_entries
, 0),
317 GSTFS_OPT_KEY("pipeline=%s", pipeline
, 0),
322 int main(int argc
, char *argv
[])
325 struct fuse_args args
= FUSE_ARGS_INIT(argc
, argv
);
327 if (fuse_opt_parse(&args
, &mount_info
, gstfs_opts
, NULL
) == -1)
330 if (!mount_info
.src_mnt
||
331 !mount_info
.src_ext
||
332 !mount_info
.dst_ext
||
333 !mount_info
.pipeline
)
339 if (!getcwd(pwd
, sizeof(pwd
)))
345 mount_info
.src_mnt
= canonize(pwd
, mount_info
.src_mnt
);
347 if (mount_info
.max_cache_entries
== 0)
348 mount_info
.max_cache_entries
= 50;
350 pthread_mutex_init(&mount_info
.cache_mutex
, NULL
);
351 mount_info
.file_cache
= g_hash_table_new(g_str_hash
, g_str_equal
);
352 mount_info
.cache_lru
= g_queue_new();
354 gst_init(&argc
, &argv
);
355 return fuse_main(args
.argc
, args
.argv
, &gstfs_opers
, NULL
);