/* FUSE: Filesystem in Userspace Copyright (C) 2001-2005 Miklos Szeredi This program can be distributed under the terms of the GNU GPL. See the file COPYING. */ #include 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(""); if (!genfile->title) genfile->title = g_strdup(""); if (!genfile->album) genfile->album = g_strdup(""); if (!genfile->genre) genfile->genre = g_strdup(""); 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 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 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; }