/*
FUSE: Filesystem in Userspace
Copyright (C) 2001-2005 Miklos Szeredi <miklos@szeredi.hu>
This program can be distributed under the terms of the GNU GPL.
See the file COPYING.
*/
#include <mtpfs.h>
void
free_files(LIBMTP_file_t *filelist)
{
LIBMTP_file_t *file = filelist, *tmp;
while (file) {
tmp = file;
file = file->next;
LIBMTP_destroy_file_t(tmp);
}
}
void
free_playlists(LIBMTP_playlist_t *pl)
{
LIBMTP_playlist_t *playlist = pl, *tmp;
while (playlist) {
tmp = playlist;
playlist = playlist->next;
LIBMTP_destroy_playlist_t(tmp);
}
}
void
check_files ()
{
if (files_changed) {
if (DEBUG) g_debug("Refreshing Filelist");
LIBMTP_file_t *newfiles = NULL;
g_mutex_lock(device_lock);
if (files) free_files(files);
newfiles = LIBMTP_Get_Filelisting_With_Callback(device, NULL, NULL);
files = newfiles;
newfiles = NULL;
files_changed = FALSE;
g_mutex_unlock(device_lock);
}
}
void
check_folders ()
{
if (folders_changed) {
if (DEBUG) g_debug("Refreshing Folderlist");
LIBMTP_folder_t *newfolders = NULL;
g_mutex_lock(device_lock);
if (folders) {
LIBMTP_destroy_folder_t(folders);
}
newfolders = LIBMTP_Get_Folder_List(device);
folders = newfolders;
newfolders = NULL;
folders_changed= FALSE;
g_mutex_unlock(device_lock);
}
}
void
check_playlists ()
{
if (playlists_changed) {
if (DEBUG) g_debug("Refreshing Playlists");
LIBMTP_playlist_t *newplaylists;
g_mutex_lock(device_lock);
if (playlists) free_playlists(playlists);
newplaylists = LIBMTP_Get_Playlist_List(device);
playlists = newplaylists;
playlists_changed = FALSE;
g_mutex_unlock(device_lock);
}
}
int
save_playlist (const char *path, struct fuse_file_info *fi)
{
if (DEBUG) g_debug("save_playlist");
int ret=0;
LIBMTP_playlist_t *playlist;
FILE *file = NULL;
char item_path[1024];
uint32_t item_id=0;
int no_tracks=0;
uint32_t *tracks;
gchar **fields;
GSList *tmplist=NULL;
fields = g_strsplit(path,"/",-1);
gchar *playlist_name;
playlist_name = g_strndup(fields[2],strlen(fields[2])-4);
if (DEBUG) g_debug("Adding:%s",playlist_name);
g_strfreev(fields);
playlist=LIBMTP_new_playlist_t();
playlist->name=g_strdup(playlist_name);
file = fdopen(fi->fh,"r");
while (fgets(item_path,sizeof(item_path)-1,file) != NULL){
g_strchomp(item_path);
item_id = parse_path(item_path);
if (item_id != -1) {
tmplist = g_slist_append(tmplist,GUINT_TO_POINTER(item_id));
g_debug("Adding to tmplist:%d",item_id);
}
}
playlist->no_tracks = g_slist_length(tmplist);
tracks = g_malloc(playlist->no_tracks * sizeof(uint32_t));
int i;
for (i = 0; i < playlist->no_tracks; i++) {
tracks[i]=(uint32_t)GPOINTER_TO_UINT(g_slist_nth_data(tmplist,i));
g_debug("Adding:%d-%d",i,tracks[i]);
}
playlist->tracks = tracks;
g_debug("Total:%d",playlist->no_tracks);
int playlist_id = 0;
LIBMTP_playlist_t *tmp_playlist;
check_playlists();
tmp_playlist=playlists;
while (tmp_playlist != NULL){
if (strcasecmp(tmp_playlist->name,playlist_name) == 0){
playlist_id=playlist->playlist_id;
}
tmp_playlist=tmp_playlist->next;
}
if (playlist_id > 0) {
if(DEBUG) g_debug("Update playlist %d",playlist_id);
playlist->playlist_id=playlist_id;
ret = LIBMTP_Update_Playlist(device,playlist);
} else {
if(DEBUG) g_debug("New playlist");
ret = LIBMTP_Create_New_Playlist(device,playlist,0);
}
playlists_changed=TRUE;
return ret;
}
/* Find the file type based on extension */
static LIBMTP_filetype_t
find_filetype (const gchar * filename)
{
if (DEBUG) g_debug ("find_filetype");
gchar **fields;
fields = g_strsplit (filename, ".", -1);
gchar *ptype;
ptype = g_strdup (fields[g_strv_length (fields) - 1]);
g_strfreev (fields);
LIBMTP_filetype_t filetype;
// This need to be kept constantly updated as new file types arrive.
if (!strcasecmp (ptype, "wav")) {
filetype = LIBMTP_FILETYPE_WAV;
} else if (!strcasecmp (ptype, "mp3")) {
filetype = LIBMTP_FILETYPE_MP3;
} else if (!strcasecmp (ptype, "wma")) {
filetype = LIBMTP_FILETYPE_WMA;
} else if (!strcasecmp (ptype, "ogg")) {
filetype = LIBMTP_FILETYPE_OGG;
} else if (!strcasecmp (ptype, "mp4")) {
filetype = LIBMTP_FILETYPE_MP4;
} else if (!strcasecmp (ptype, "wmv")) {
filetype = LIBMTP_FILETYPE_WMV;
} else if (!strcasecmp (ptype, "avi")) {
filetype = LIBMTP_FILETYPE_AVI;
} else if (!strcasecmp (ptype, "mpeg") || !strcasecmp (ptype, "mpg")) {
filetype = LIBMTP_FILETYPE_MPEG;
} else if (!strcasecmp (ptype, "asf")) {
filetype = LIBMTP_FILETYPE_ASF;
} else if (!strcasecmp (ptype, "qt") || !strcasecmp (ptype, "mov")) {
filetype = LIBMTP_FILETYPE_QT;
} else if (!strcasecmp (ptype, "wma")) {
filetype = LIBMTP_FILETYPE_WMA;
} else if (!strcasecmp (ptype, "jpg") || !strcasecmp (ptype, "jpeg")) {
filetype = LIBMTP_FILETYPE_JPEG;
} else if (!strcasecmp (ptype, "jfif")) {
filetype = LIBMTP_FILETYPE_JFIF;
} else if (!strcasecmp (ptype, "tif") || !strcasecmp (ptype, "tiff")) {
filetype = LIBMTP_FILETYPE_TIFF;
} else if (!strcasecmp (ptype, "bmp")) {
filetype = LIBMTP_FILETYPE_BMP;
} else if (!strcasecmp (ptype, "gif")) {
filetype = LIBMTP_FILETYPE_GIF;
} else if (!strcasecmp (ptype, "pic") || !strcasecmp (ptype, "pict")) {
filetype = LIBMTP_FILETYPE_PICT;
} else if (!strcasecmp (ptype, "png")) {
filetype = LIBMTP_FILETYPE_PNG;
} else if (!strcasecmp (ptype, "wmf")) {
filetype = LIBMTP_FILETYPE_WINDOWSIMAGEFORMAT;
} else if (!strcasecmp (ptype, "ics")) {
filetype = LIBMTP_FILETYPE_VCALENDAR2;
} else if (!strcasecmp (ptype, "exe") || !strcasecmp (ptype, "com") ||
!strcasecmp (ptype, "bat") || !strcasecmp (ptype, "dll") ||
!strcasecmp (ptype, "sys")) {
filetype = LIBMTP_FILETYPE_WINEXEC;
} else {
printf ("Sorry, file type \"%s\" is not yet supported\n", ptype);
printf ("Tagging as unknown file type.\n");
filetype = LIBMTP_FILETYPE_UNKNOWN;
}
return filetype;
}
static int
lookup_folder_id (LIBMTP_folder_t * folderlist, gchar * path, gchar * parent)
{
//if (DEBUG) g_debug("lookup_folder_id");
int ret = -1;
if (folderlist == NULL) {
return -1;
}
check_folders();
gchar *current;
current = g_strconcat(parent, "/", folderlist->name,NULL);
if (strcasecmp (path, current) == 0) {
return folderlist->folder_id;
}
if (strncasecmp (path, current, strlen (current)) == 0) {
ret = lookup_folder_id (folderlist->child, path, current);
}
g_free(current);
if (ret >= 0) {
return ret;
}
ret = lookup_folder_id (folderlist->sibling, path, parent);
return ret;
}
static int
parse_path (const gchar * path)
{
if (DEBUG) g_debug ("parse_path:%s",path);
int item_id = -1;
int i;
// Check cached files first
GSList *item;
item = g_slist_find_custom (myfiles, path, (GCompareFunc) strcmp);
if (item != NULL)
return 0;
// Check Playlists
if (strncmp("/Playlists",path,10) == 0) {
LIBMTP_playlist_t *playlist;
check_playlists();
playlist=playlists;
while (playlist != NULL) {
gchar *tmppath;
tmppath = g_strconcat("/Playlists/",playlist->name,".m3u",NULL);
if (g_strcasecmp(path,tmppath) == 0)
return playlist->playlist_id;
playlist = playlist->next;
}
return -ENOENT;
}
// Check device
LIBMTP_folder_t *folder;
gchar **fields;
gchar *directory;
gchar *file;
directory = (gchar *) g_malloc (strlen (path));
directory = strcpy (directory, "");
fields = g_strsplit (path, "/", -1);
for (i = 0; fields[i] != NULL; i++) {
if (strlen (fields[i]) > 0) {
if (fields[i + 1] != NULL) {
directory = strcat (directory, "/");
directory = strcat (directory, fields[i]);
} else {
check_folders();
folder = folders;
int folder_id = 0;
if (strcmp (directory, "") != 0) {
folder_id = lookup_folder_id (folder, directory, "");
}
if (DEBUG) g_debug ("parent id:%d:%s", folder_id, directory);
LIBMTP_file_t *file, *tmp;
check_files();
file = files;
while (file != NULL) {
if (file->parent_id == folder_id) {
if (strcasecmp (file->filename, fields[i]) == 0) {
if (DEBUG) g_debug ("found:%d:%s", file->item_id,
file->filename);
item_id = file->item_id;
return item_id;
}
}
file = file->next;
}
if (item_id < 0) {
directory = strcat (directory, fields[i]);
item_id = lookup_folder_id (folder, directory, "");
return item_id;
}
}
}
}
return -ENOENT;
}
static int
mtpfs_release (const char *path, struct fuse_file_info *fi)
{
if (DEBUG) g_debug ("release");
// Check cached files first
GSList *item;
item = g_slist_find_custom (myfiles, path, (GCompareFunc) strcmp);
if (item != NULL) {
if (strncmp("/Playlists/",path,11) == 0) {
g_mutex_lock(device_lock);
save_playlist(path,fi);
close (fi->fh);
g_mutex_unlock(device_lock);
return 0;
} else {
//find parent id
gchar *filename;
gchar **fields;
gchar *directory;
directory = (gchar *) g_malloc (strlen (path));
directory = strcpy (directory, "/");
fields = g_strsplit (path, "/", -1);
int i;
uint32_t parent_id = 0;
for (i = 0; fields[i] != NULL; i++) {
if (strlen (fields[i]) > 0) {
if (fields[i + 1] == NULL) {
directory = g_strndup (directory, strlen (directory) - 1);
parent_id = lookup_folder_id (folders, directory, "");
if (parent_id < 0)
parent_id = 0;
filename = g_strdup (fields[i]);
} else {
directory = strcat (directory, fields[i]);
directory = strcat (directory, "/");
}
}
}
if (DEBUG) g_debug ("%s:%s:%d", filename, directory, parent_id);
struct stat st;
uint64_t filesize;
fstat (fi->fh, &st);
filesize = (uint64_t) st.st_size;
// Setup file
LIBMTP_filetype_t filetype;
filetype = find_filetype (filename);
int ret;
if (filetype == LIBMTP_FILETYPE_MP3) {
LIBMTP_track_t *genfile;
genfile = LIBMTP_new_track_t ();
gint songlen;
struct id3_file *id3_fh;
struct id3_tag *tag;
gchar *tracknum;
id3_fh = id3_file_fdopen (fi->fh, ID3_FILE_MODE_READONLY);
tag = id3_file_tag (id3_fh);
genfile->artist = getArtist (tag);
genfile->title = getTitle (tag);
genfile->album = getAlbum (tag);
genfile->genre = getGenre (tag);
genfile->date = getYear (tag);
genfile->usecount = 0;
/* If there is a songlength tag it will take
* precedence over any length calculated from
* the bitrate and filesize */
songlen = getSonglen (tag);
if (songlen > 0) {
genfile->duration = songlen * 1000;
} else {
int fd;
genfile->duration = (uint16_t)calc_length(fi->fh) * 1000;
//genfile->duration = 293000;
}
tracknum = getTracknum (tag);
if (tracknum != NULL) {
genfile->tracknumber = strtoul(tracknum,NULL,10);
} else {
genfile->tracknumber = 0;
}
g_free (tracknum);
// Compensate for missing tag information
if (!genfile->artist)
genfile->artist = g_strdup("<Unknown>");
if (!genfile->title)
genfile->title = g_strdup("<Unknown>");
if (!genfile->album)
genfile->album = g_strdup("<Unknown>");
if (!genfile->genre)
genfile->genre = g_strdup("<Unknown>");
genfile->filesize = filesize;
genfile->filetype = filetype;
genfile->filename = g_strdup (filename);
//title,artist,genre,album,date,tracknumber,duration,samplerate,nochannels,wavecodec,bitrate,bitratetype,rating,usecount
//g_debug("%d:%d:%d",fi->fh,genfile->duration,genfile->filesize);
g_mutex_lock(device_lock);
ret =
LIBMTP_Send_Track_From_File_Descriptor (device, fi->fh,
genfile, NULL, NULL,
parent_id);
g_mutex_unlock(device_lock);
id3_file_close (id3_fh);
LIBMTP_destroy_track_t (genfile);
} else {
LIBMTP_file_t *genfile;
genfile = LIBMTP_new_file_t ();
genfile->filesize = filesize;
genfile->filetype = filetype;
genfile->filename = g_strdup (filename);
genfile->parent_id = parent_id;
g_mutex_lock(device_lock);
ret =
LIBMTP_Send_File_From_File_Descriptor (device, fi->fh,
genfile, NULL, NULL,
parent_id);
g_mutex_unlock(device_lock);
LIBMTP_destroy_file_t (genfile);
}
if (ret == 0)
if (DEBUG) g_debug ("Sent %s",path);
// Cleanup
myfiles = g_slist_remove (myfiles, item->data);
g_strfreev (fields);
close (fi->fh);
// Refresh filelist
files_changed = TRUE;
//files = LIBMTP_Get_Filelisting(device);
return ret;
}
}
close (fi->fh);
return 0;
}
void
mtpfs_destroy ()
{
if (DEBUG) g_debug ("destroy");
if (files) free_files(files);
if (folders) LIBMTP_destroy_folder_t(folders);
if (playlists) free_playlists(playlists);
LIBMTP_Release_Device (device);
}
static int
mtpfs_readdir (const gchar * path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)
{
if (DEBUG) g_debug ("readdir");
LIBMTP_folder_t *folder;
// Add common entries
filler (buf, ".", NULL, 0);
filler (buf, "..", NULL, 0);
// Are we looking at the playlists?
if (strncmp (path, "/Playlists",10) == 0) {
if (DEBUG) g_debug("Checking Playlists");
LIBMTP_playlist_t *playlist;
check_playlists();
playlist=playlists;
while (playlist!= NULL) {
struct stat st;
memset (&st, 0, sizeof (st));
st.st_ino = playlist->playlist_id;
st.st_mode = S_IFREG | 0666;
gchar *name;
name = g_strconcat(playlist->name,".m3u",NULL);
if (DEBUG) g_debug("Playlist:%s",name);
if (filler (buf, name, &st, 0))
break;
playlist=playlist->next;
}
return 0;
}
// Get folder listing.
int folder_id = 0;
if (strcmp (path, "/") != 0) {
check_folders();
folder = folders;
folder_id = lookup_folder_id (folder, (gchar *) path, "");
}
if (folder_id == 0) {
filler (buf, "Playlists", NULL, 0);
check_folders();
folder = folders;
} else {
g_mutex_lock(device_lock);
check_folders();
folder = LIBMTP_Find_Folder (folders, folder_id);
g_mutex_unlock(device_lock);
folder = folder->child;
}
while (folder != NULL) {
if (folder->parent_id == folder_id) {
struct stat st;
memset (&st, 0, sizeof (st));
st.st_ino = folder->folder_id;
st.st_mode = S_IFDIR | 0777;
if (filler (buf, folder->name, &st, 0))
break;
}
folder = folder->sibling;
}
LIBMTP_destroy_folder_t (folder);
// Find files
LIBMTP_file_t *file, *tmp;
check_files();
file = files;
while (file != NULL) {
if (file->parent_id == folder_id) {
struct stat st;
memset (&st, 0, sizeof (st));
st.st_ino = file->item_id;
st.st_mode = S_IFREG | 0444;
if (filler (buf, file->filename, &st, 0))
break;
}
tmp = file;
file = file->next;
}
return 0;
}
static int
mtpfs_getattr (const gchar * path, struct stat *stbuf)
{
if (DEBUG) g_debug ("getattr");
int ret = 0;
memset (stbuf, 0, sizeof (struct stat));
// Set uid/gid of file
struct fuse_context *fc;
fc = fuse_get_context();
stbuf->st_uid = fc->uid;
stbuf->st_gid = fc->gid;
// Check cached files first
GSList *item;
if (myfiles != NULL) {
item = g_slist_find_custom (myfiles, path, (GCompareFunc) strcmp);
if (item != NULL) {
stbuf->st_mode = S_IFREG | 0777;
stbuf->st_size = 0;
stbuf->st_blocks = 2;
return 0;
}
}
// Special case directory 'Playlists'
if (strcasecmp (path, "/Playlists") == 0) {
stbuf->st_mode = S_IFDIR | 0777;
stbuf->st_nlink = 2;
return 0;
}
if (strncasecmp (path, "/Playlists",10) == 0) {
LIBMTP_playlist_t *playlist;
check_playlists();
playlist=playlists;
while (playlist != NULL) {
gchar *tmppath;
tmppath = g_strconcat("/Playlists/",playlist->name,".m3u",NULL);
if (g_strcasecmp(path,tmppath) == 0) {
int filesize = 0;
int i;
for (i=0; i <playlist->no_tracks; i++){
LIBMTP_file_t *file;
LIBMTP_folder_t *folder;
file = LIBMTP_Get_Filemetadata(device,playlist->tracks[i]);
if (file != NULL) {
int parent_id = file->parent_id;
filesize = filesize + strlen(file->filename) + 2;
while (parent_id != 0) {
check_folders();
folder = LIBMTP_Find_Folder(folders,parent_id);
parent_id = folder->parent_id;
filesize = filesize + strlen(folder->name) + 1;
}
}
}
stbuf->st_mode = S_IFREG | 0777;
stbuf->st_size = filesize;
stbuf->st_blocks = 2;
return 0;
}
playlist = playlist->next;
}
return -ENOENT;
}
if (strcmp (path, "/") == 0) {
stbuf->st_mode = S_IFDIR | 0777;
stbuf->st_nlink = 2;
} else {
int item_id = -1;
LIBMTP_folder_t *folder;
check_folders();
folder = folders;
item_id = lookup_folder_id (folder, (gchar *) path, "");
if (item_id >= 0) {
stbuf->st_ino = item_id;
stbuf->st_mode = S_IFDIR | 0777;
stbuf->st_nlink = 2;
} else {
item_id = parse_path (path);
LIBMTP_file_t *file;
if (DEBUG) g_debug ("%d:%s", item_id, path);
check_files();
file = files;
gboolean found = FALSE;
while (file != NULL) {
if (file->item_id == item_id) {
stbuf->st_ino = item_id;
stbuf->st_size = file->filesize;
stbuf->st_blocks = (file->filesize / 512) +
(file->filesize % 512 > 0 ? 1 : 0);
stbuf->st_nlink = 1;
stbuf->st_mode = S_IFREG | 0777;
found = TRUE;
}
file = file->next;
}
if (!found) {
ret = -ENOENT;
}
}
}
return ret;
}
static int
mtpfs_mknod (const gchar * path, mode_t mode, dev_t dev)
{
if (DEBUG) g_debug ("mknod");
int item_id = parse_path (path);
if (item_id > 0)
return -EEXIST;
myfiles = g_slist_append (myfiles, (gpointer) (g_strdup (path)));
if (DEBUG) g_debug ("NEW FILE");
return 0;
}
static int
mtpfs_open (const gchar * path, struct fuse_file_info *fi)
{
if (DEBUG) g_debug ("open");
int item_id = -1;
item_id = parse_path (path);
if (item_id < 0)
return -ENOENT;
if (fi->flags == O_RDONLY) {
if (DEBUG) g_debug("read");
} else if (fi->flags == O_WRONLY) {
if (DEBUG) g_debug("write");
} else if (fi->flags == O_RDWR) {
if (DEBUG) g_debug("rdwrite");
}
FILE *filetmp = tmpfile ();
int tmpfile = fileno (filetmp);
if (tmpfile != -1) {
if (item_id == 0) {
fi->fh = tmpfile;
} else if (strncmp("/Playlists/",path,11) == 0) {
// Is a playlist
gchar **fields;
fields = g_strsplit(path,"/",-1);
gchar *name;
name = g_strndup(fields[2],strlen(fields[2])-4);
g_strfreev(fields);
fi->fh = tmpfile;
LIBMTP_playlist_t *playlist;
check_playlists();
playlist = playlists;
while (playlist != NULL) {
if (strcasecmp(playlist->name,name) == 0 ) {
int playlist_id=playlist->playlist_id;
int i;
for (i=0; i <playlist->no_tracks; i++){
LIBMTP_file_t *file;
LIBMTP_folder_t *folder;
g_mutex_lock(device_lock);
file = LIBMTP_Get_Filemetadata(device,playlist->tracks[i]);
g_mutex_unlock(device_lock);
if (file != NULL) {
gchar *path;
path = (gchar *) g_malloc (1024);
path = strcpy(path,"/");
int parent_id = file->parent_id;
while (parent_id != 0) {
check_folders();
g_mutex_lock(device_lock);
folder = LIBMTP_Find_Folder(folders,parent_id);
path = strcat(path,folder->name);
path = strcat(path,"/");
parent_id = folder->parent_id;
g_mutex_unlock(device_lock);
}
path = strcat (path,file->filename);
fprintf (filetmp,"%s\n",path);
if (DEBUG) g_debug("%s\n",path);
}
}
//LIBMTP_destroy_file_t(file);
fflush(filetmp);
break;
}
playlist=playlist->next;
}
} else {
g_mutex_lock(device_lock);
int ret =
LIBMTP_Get_File_To_File_Descriptor (device, item_id, tmpfile,
NULL, NULL);
g_mutex_unlock(device_lock);
if (ret == 0) {
fi->fh = tmpfile;
} else {
return -ENOENT;
}
}
} else {
return -ENOENT;
}
return 0;
}
static int
mtpfs_read (const gchar * path, gchar * buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
if (DEBUG) g_debug ("read");
int fd;
int ret;
int item_id = -1;
item_id = parse_path (path);
if (item_id < 0)
return -ENOENT;
ret = pread (fi->fh, buf, size, offset);
if (ret == -1)
ret = -errno;
return ret;
}
static int
mtpfs_write (const gchar * path, const gchar * buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
//if (DEBUG) g_debug ("write");
int ret;
if (fi->fh != -1) {
ret = pwrite (fi->fh, buf, size, offset);
} else {
ret = -ENOENT;
}
return ret;
}
static int
mtpfs_unlink (const gchar * path)
{
if (DEBUG) g_debug ("unlink");
int ret = 0;
int item_id = -1;
item_id = parse_path (path);
if (item_id < 0)
return -ENOENT;
g_mutex_lock(device_lock);
ret = LIBMTP_Delete_Object (device, item_id);
if (strncmp (path, "/Playlists",10) == 0) {
playlists_changed = TRUE;
} else {
files_changed = TRUE;
}
g_mutex_unlock(device_lock);
return ret;
}
static int
mtpfs_mkdir (const char *path, mode_t mode)
{
if (DEBUG) g_debug ("mkdir");
int ret = 0;
GSList *item;
item = g_slist_find_custom (myfiles, path, (GCompareFunc) strcmp);
int item_id = parse_path (path);
if ((item == NULL) && (item_id < 0)) {
// Split path and find parent_id
gchar *filename="";
gchar **fields;
gchar *directory;
directory = (gchar *) g_malloc (strlen (path));
directory = strcpy (directory, "/");
fields = g_strsplit (path, "/", -1);
int i;
uint32_t parent_id = 0;
for (i = 0; fields[i] != NULL; i++) {
if (strlen (fields[i]) > 0) {
if (fields[i + 1] == NULL) {
directory = g_strndup (directory, strlen (directory) - 1);
check_folders();
parent_id = lookup_folder_id (folders, directory, "");
if (parent_id < 0)
parent_id = 0;
filename = g_strdup (fields[i]);
} else {
directory = strcat (directory, fields[i]);
directory = strcat (directory, "/");
}
}
}
if (DEBUG) g_debug ("%s:%s:%d", filename, directory, parent_id);
g_mutex_lock(device_lock);
ret = LIBMTP_Create_Folder (device, filename, parent_id);
g_mutex_unlock(device_lock);
g_strfreev (fields);
if (ret == 0) {
ret = -EEXIST;
} else {
folders_changed=TRUE;
ret = 0;
}
} else {
ret = -EEXIST;
}
return ret;
}
static int
mtpfs_rmdir (const char *path)
{
if (DEBUG) g_debug ("rmdir");
int ret = 0;
int folder_id = -1;
if (strcmp (path, "/") != 0) {
folder_id = lookup_folder_id (folders, (gchar *) path, "");
}
if (folder_id < 0)
return -ENOENT;
g_mutex_lock(device_lock);
LIBMTP_Delete_Object(device, folder_id);
g_mutex_unlock(device_lock);
folders_changed=TRUE;
return ret;
}
/* Not working. need some way in libmtp to rename objects
int
mtpfs_rename (const char *oldname, const char *newname)
{
uint32_t old_id = parse_path(oldname);
LIBMTP_track_t *track;
track = LIBMTP_Get_Trackmetadata(device,old_id);
gchar *filename;
gchar **fields;
gchar *directory;
directory = (gchar *) g_malloc (strlen (newname));
directory = strcpy (directory, "/");
fields = g_strsplit (newname, "/", -1);
int i;
uint32_t parent_id = 0;
for (i = 0; fields[i] != NULL; i++) {
if (strlen (fields[i]) > 0) {
if (fields[i + 1] == NULL) {
directory = g_strndup (directory, strlen (directory) - 1);
parent_id = lookup_folder_id (folders, directory, "");
if (parent_id < 0)
parent_id = 0;
filename = g_strdup (fields[i]);
} else {
directory = strcat (directory, fields[i]);
directory = strcat (directory, "/");
}
}
}
if (DEBUG) g_debug ("%s:%s:%d", filename, directory, parent_id);
track->parent_id = parent_id;
track->title = g_strdup(filename);
int ret = LIBMTP_Update_Track_Metadata(device, track);
return ret;
}
*/
static int
mtpfs_statfs (const char *path, struct statvfs *stbuf)
{
if (DEBUG) g_debug ("mtpfs_statfs");
stbuf->f_bsize=1024;
stbuf->f_blocks=device->storage->MaxCapacity/1024;
stbuf->f_bfree=device->storage->FreeSpaceInBytes/1024;
stbuf->f_ffree=device->storage->FreeSpaceInObjects/1024;
stbuf->f_bavail=stbuf->f_bfree;
return 0;
}
void *
mtpfs_init ()
{
if (DEBUG) g_debug ("mtpfs_init");
LIBMTP_Init ();
device = LIBMTP_Get_First_Device ();
if (device == NULL) {
if (DEBUG) g_debug ("No devices.");
exit (1);
}
if (!g_thread_supported ()) g_thread_init(NULL);
device_lock = g_mutex_new ();
files_changed=TRUE;
folders_changed=TRUE;
playlists_changed=TRUE;
//if (DEBUG) g_debug ("Get Folder List");
//folders = LIBMTP_Get_Folder_List (device);
//if (DEBUG) g_debug ("Get File List");
//files = LIBMTP_Get_Filelisting (device);
//if (DEBUG) g_debug ("Get Playlists");
//playlists = LIBMTP_Get_Playlist_List(device);
if (DEBUG) g_debug ("Ready");
}
static struct fuse_operations mtpfs_oper = {
.release = mtpfs_release,
.readdir = mtpfs_readdir,
.getattr = mtpfs_getattr,
.open = mtpfs_open,
.mknod = mtpfs_mknod,
.read = mtpfs_read,
.write = mtpfs_write,
.unlink = mtpfs_unlink,
.destroy = mtpfs_destroy,
.mkdir = mtpfs_mkdir,
.rmdir = mtpfs_rmdir,
/* .rename = mtpfs_rename,*/
.statfs = mtpfs_statfs,
.init = mtpfs_init,
};
int
main (int argc, char *argv[])
{
umask (0);
return fuse_main (argc, argv, &mtpfs_oper);
}
/* Private buffer for passing around with libmad */
typedef struct
{
/* The buffer of raw mpeg data for libmad to decode */
void * buf;
/* Cached data: pointers to the dividing points of frames
in buf, and the playing time at each of those frames */
void **frames;
mad_timer_t *times;
/* fd is the file descriptor if over the network, or -1 if
using mmap()ed files */
int fd;
/* length of the current stream, corrected for id3 tags */
ssize_t length;
/* have we finished fetching this file? (only in non-mmap()'ed case */
int done;
/* total number of frames */
unsigned long num_frames;
/* number of frames to play */
unsigned long max_frames;
/* total duration of the file */
mad_timer_t duration;
/* filename as mpg321 has opened it */
char filename[PATH_MAX];
} buffer;
/* XING parsing is from the MAD winamp input plugin */
struct xing {
int flags;
unsigned long frames;
unsigned long bytes;
unsigned char toc[100];
long scale;
};
enum {
XING_FRAMES = 0x0001,
XING_BYTES = 0x0002,
XING_TOC = 0x0004,
XING_SCALE = 0x0008
};
# define XING_MAGIC (('X' << 24) | ('i' << 16) | ('n' << 8) | 'g')
/* Following two function are adapted from mad_timer, from the
libmad distribution */
static
int parse_xing(struct xing *xing, struct mad_bitptr ptr, unsigned int bitlen)
{
if (bitlen < 64 || mad_bit_read(&ptr, 32) != XING_MAGIC)
goto fail;
xing->flags = mad_bit_read(&ptr, 32);
bitlen -= 64;
if (xing->flags & XING_FRAMES) {
if (bitlen < 32)
goto fail;
xing->frames = mad_bit_read(&ptr, 32);
bitlen -= 32;
}
if (xing->flags & XING_BYTES) {
if (bitlen < 32)
goto fail;
xing->bytes = mad_bit_read(&ptr, 32);
bitlen -= 32;
}
if (xing->flags & XING_TOC) {
int i;
if (bitlen < 800)
goto fail;
for (i = 0; i < 100; ++i)
xing->toc[i] = mad_bit_read(&ptr, 8);
bitlen -= 800;
}
if (xing->flags & XING_SCALE) {
if (bitlen < 32)
goto fail;
xing->scale = mad_bit_read(&ptr, 32);
bitlen -= 32;
}
return 1;
fail:
xing->flags = 0;
return 0;
}
int scan(void const *ptr, ssize_t len)
{
struct mad_stream stream;
struct mad_header header;
struct xing xing;
g_debug("scan: %d",len);
unsigned long bitrate = 0;
int has_xing = 0;
int is_vbr = 0;
int duration = 0;
mad_stream_init(&stream);
mad_header_init(&header);
mad_stream_buffer(&stream, ptr, len);
int num_frames = 0;
/* There are three ways of calculating the length of an mp3:
1) Constant bitrate: One frame can provide the information
needed: # of frames and duration. Just see how long it
is and do the division.
2) Variable bitrate: Xing tag. It provides the number of
frames. Each frame has the same number of samples, so
just use that.
3) All: Count up the frames and duration of each frames
by decoding each one. We do this if we've no other
choice, i.e. if it's a VBR file with no Xing tag.
*/
while (1)
{
if (mad_header_decode(&header, &stream) == -1)
{
if (MAD_RECOVERABLE(stream.error))
continue;
else
break;
}
/* Limit xing testing to the first frame header */
if (!num_frames++)
{
if(parse_xing(&xing, stream.anc_ptr, stream.anc_bitlen))
{
is_vbr = 1;
if (xing.flags & XING_FRAMES)
{
/* We use the Xing tag only for frames. If it doesn't have that
information, it's useless to us and we have to treat it as a
normal VBR file */
has_xing = 1;
num_frames = xing.frames;
break;
}
}
}
/* Test the first n frames to see if this is a VBR file */
if (!is_vbr && !(num_frames > 20))
{
if (bitrate && header.bitrate != bitrate)
{
is_vbr = 1;
}
else
{
bitrate = header.bitrate;
}
}
/* We have to assume it's not a VBR file if it hasn't already been
marked as one and we've checked n frames for different bitrates */
else if (!is_vbr)
{
break;
}
duration = header.duration.seconds;
}
if (!is_vbr)
{
double time = (len * 8.0) / (header.bitrate); /* time in seconds */
double timefrac = (double)time - ((long)(time));
long nsamples = 32 * MAD_NSBSAMPLES(&header); /* samples per frame */
/* samplerate is a constant */
num_frames = (long) (time * header.samplerate / nsamples);
duration = (int)time;
g_debug("d:%d",duration);
}
else if (has_xing)
{
/* modify header.duration since we don't need it anymore */
mad_timer_multiply(&header.duration, num_frames);
duration = header.duration.seconds;
}
else
{
/* the durations have been added up, and the number of frames
counted. We do nothing here. */
}
mad_header_finish(&header);
mad_stream_finish(&stream);
return duration;
}
int calc_length(int f)
{
struct stat filestat;
void *fdm;
char buffer[3];
fstat(f, &filestat);
/* TAG checking is adapted from XMMS */
int length = filestat.st_size;
if (lseek(f, -128, SEEK_END) < 0)
{
/* File must be very short or empty. Forget it. */
return -1;
}
if (read(f, buffer, 3) != 3)
{
return -1;
}
if (!strncmp(buffer, "TAG", 3))
{
length -= 128; /* Correct for id3 tags */
}
fdm = mmap(0, length, PROT_READ, MAP_SHARED, f, 0);
if (fdm == MAP_FAILED)
{
g_error("Map failed");
return -1;
}
/* Scan the file for a XING header, or calculate the length,
or just scan the whole file and add everything up. */
int duration = scan(fdm, length);
if (munmap(fdm, length) == -1)
{
g_error("Unmap failed");
return -1;
}
lseek(f, 0, SEEK_SET);
return duration;
}
syntax highlighted by Code2HTML, v. 0.9.1