hythmbox-2.98/plugins/mtpdevice/rb-mtp-thread.c

No issues found

Incomplete coverage

Tool Failure ID Location Function Message Data
clang-analyzer no-output-found rb-mtp-thread.c Message(text='Unable to locate XML output from invoke-clang-analyzer') None
Failure running clang-analyzer ('no-output-found')
Message
Unable to locate XML output from invoke-clang-analyzer
  1 /*
  2  *  Copyright (C) 2009 Jonathan Matthew  <jonathan@d14n.org>
  3  *
  4  *  This program is free software; you can redistribute it and/or modify
  5  *  it under the terms of the GNU General Public License as published by
  6  *  the Free Software Foundation; either version 2 of the License, or
  7  *  (at your option) any later version.
  8  *
  9  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
 10  *  GStreamer plugins to be used and distributed together with GStreamer
 11  *  and Rhythmbox. This permission is above and beyond the permissions granted
 12  *  by the GPL license by which Rhythmbox is covered. If you modify this code
 13  *  you may extend this exception to your version of the code, but you are not
 14  *  obligated to do so. If you do not wish to do so, delete this exception
 15  *  statement from your version.
 16  *
 17  *  This program is distributed in the hope that it will be useful,
 18  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 19  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 20  *  GNU General Public License for more details.
 21  *
 22  *  You should have received a copy of the GNU General Public License
 23  *  along with this program; if not, write to the Free Software
 24  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 25  *
 26  */
 27 
 28 #include <config.h>
 29 
 30 #include <string.h>
 31 #include <stdlib.h>
 32 
 33 #include <glib.h>
 34 #include <glib/gi18n.h>
 35 #include <gtk/gtk.h>
 36 
 37 #include "rb-mtp-thread.h"
 38 #include "rb-file-helpers.h"
 39 #include "rb-dialog.h"
 40 #include "rb-debug.h"
 41 
 42 G_DEFINE_DYNAMIC_TYPE(RBMtpThread, rb_mtp_thread, G_TYPE_OBJECT)
 43 
 44 
 45 typedef struct {
 46 	enum {
 47 		OPEN_DEVICE = 1,
 48 		CLOSE_DEVICE,
 49 		SET_DEVICE_NAME,
 50 		THREAD_CALLBACK,
 51 
 52 		CREATE_FOLDER,
 53 
 54 		ADD_TO_ALBUM,
 55 		REMOVE_FROM_ALBUM,
 56 		SET_ALBUM_IMAGE,
 57 
 58 		GET_TRACK_LIST,
 59 		DELETE_TRACK,
 60 		UPLOAD_TRACK,
 61 		DOWNLOAD_TRACK
 62 	} task;
 63 
 64 	LIBMTP_raw_device_t *raw_device;
 65 	LIBMTP_track_t *track;
 66 	uint32_t track_id;
 67 	uint32_t folder_id;
 68 	uint32_t storage_id;
 69 	char *album;
 70 	char *filename;
 71 	GdkPixbuf *image;
 72 	char *name;
 73 	char **path;
 74 
 75 	gpointer callback;
 76 	gpointer user_data;
 77 	GDestroyNotify destroy_data;
 78 } RBMtpThreadTask;
 79 
 80 static char *
 81 task_name (RBMtpThreadTask *task)
 82 {
 83 	switch (task->task) {
 84 	case OPEN_DEVICE:	return g_strdup ("open device");
 85 	case CLOSE_DEVICE:	return g_strdup ("close device");
 86 	case SET_DEVICE_NAME:	return g_strdup_printf ("set device name to %s", task->name);
 87 	case THREAD_CALLBACK:	return g_strdup ("thread callback");
 88 
 89 	case CREATE_FOLDER:	return g_strdup_printf ("create folder %s", task->path[g_strv_length (task->path)-1]);
 90 
 91 	case ADD_TO_ALBUM:	return g_strdup_printf ("add track %u to album %s", task->track_id, task->album);
 92 	case REMOVE_FROM_ALBUM:	return g_strdup_printf ("remove track %u from album %s", task->track_id, task->album);
 93 	case SET_ALBUM_IMAGE:	return g_strdup_printf ("set image for album %s", task->album);
 94 
 95 	case GET_TRACK_LIST:	return g_strdup ("get track list");
 96 	case DELETE_TRACK:	return g_strdup_printf ("delete track %u", task->track_id);
 97 	case UPLOAD_TRACK:	return g_strdup_printf ("upload track from %s", task->filename);
 98 	case DOWNLOAD_TRACK:	return g_strdup_printf ("download track %u to %s",
 99 							task->track_id,
100 							task->filename[0] ? task->filename : "<temporary>");
101 	default:		return g_strdup_printf ("unknown task type %d", task->task);
102 	}
103 }
104 
105 static RBMtpThreadTask *
106 create_task (int tasktype)
107 {
108 	RBMtpThreadTask *task = g_slice_new0 (RBMtpThreadTask);
109 	task->task = tasktype;
110 	return task;
111 }
112 
113 static void
114 destroy_task (RBMtpThreadTask *task)
115 {
116 	/* don't think we ever own the track structure here;
117 	 * we only have it for uploads, and then we pass it back
118 	 * to the callback.
119 	 */
120 
121 	g_free (task->album);
122 	g_free (task->filename);
123 	g_free (task->name);
124 	g_strfreev (task->path);
125 
126 	if (task->image) {
127 		g_object_unref (task->image);
128 	}
129 
130 	if (task->destroy_data) {
131 		task->destroy_data (task->user_data);
132 	}
133 
134 	g_slice_free (RBMtpThreadTask, task);
135 }
136 
137 
138 static void
139 queue_task (RBMtpThread *thread, RBMtpThreadTask *task)
140 {
141 	char *name = task_name (task);
142 	rb_debug ("queueing task: %s", name);
143 	g_free (name);
144 
145 	g_async_queue_push (thread->queue, task);
146 }
147 
148 static void
149 open_device (RBMtpThread *thread, RBMtpThreadTask *task)
150 {
151 	RBMtpOpenCallback cb = task->callback;
152 	int retry;
153 
154 	/* open the device */
155 	rb_debug ("attempting to open device");
156 	for (retry = 0; retry < 5; retry++) {
157 		if (retry > 0) {
158 			/* sleep a while before trying again */
159 			g_usleep (G_USEC_PER_SEC);
160 		}
161 
162 		thread->device = LIBMTP_Open_Raw_Device (task->raw_device);
163 		if (thread->device != NULL) {
164 			break;
165 		}
166 
167 		rb_debug ("attempt %d failed..", retry+1);
168 	}
169 
170 	cb (thread->device, task->user_data);
171 }
172 
173 static void
174 create_folder (RBMtpThread *thread, RBMtpThreadTask *task)
175 {
176 	RBMtpCreateFolderCallback cb = task->callback;
177 	LIBMTP_folder_t *folders;
178 	LIBMTP_folder_t *f;
179 	LIBMTP_folder_t *target = NULL;
180 	uint32_t folder_id;
181 	uint32_t storage_id;
182 	int i;
183 
184 	folders = LIBMTP_Get_Folder_List (thread->device);
185 	if (folders == NULL) {
186 		rb_debug ("unable to get folder list");
187 		rb_mtp_thread_report_errors (thread, FALSE);
188 		cb (0, task->user_data);
189 		return;
190 	}
191 
192 	/* first find the default music folder */
193 	f = LIBMTP_Find_Folder (folders, thread->device->default_music_folder);
194 	if (f == NULL) {
195 		rb_debug ("unable to find default music folder");
196 		cb (0, task->user_data);
197 		LIBMTP_destroy_folder_t (folders);
198 		return;
199 	}
200 	storage_id = f->storage_id;
201 	folder_id = f->folder_id;
202 
203 	/* descend through the folder tree, following the path */
204 	i = 0;
205 	while (task->path[i] != NULL) {
206 
207 		/* look for a folder at this level with the same name as the
208 		 * next path component
209 		 */
210 		target = f->child;
211 		while (target != NULL) {
212 			if (g_strcmp0 (target->name, task->path[i]) == 0) {
213 				rb_debug ("found path element %d: %s", i, target->name);
214 				break;
215 			}
216 			target = target->sibling;
217 		}
218 
219 		if (target == NULL) {
220 			rb_debug ("path element %d (%s) not found", i, task->path[i]);
221 			break;
222 		}
223 		f = target;
224 		folder_id = f->folder_id;
225 		i++;
226 	}
227 
228 	/* now create any path elements that don't already exist */
229 	while (task->path[i] != NULL) {
230 		folder_id = LIBMTP_Create_Folder (thread->device, task->path[i], folder_id, storage_id);
231 		if (folder_id == 0) {
232 			rb_debug ("couldn't create path element %d: %s", i, task->path[i]);
233 			rb_mtp_thread_report_errors (thread, FALSE);
234 			break;
235 		}
236 		rb_debug ("created path element %d: %s with folder ID %u", i, task->path[i], folder_id);
237 		i++;
238 	}
239 
240 	cb (folder_id, task->user_data);
241 	LIBMTP_destroy_folder_t (folders);
242 }
243 
244 static LIBMTP_album_t *
245 add_track_to_album (RBMtpThread *thread, const char *album_name, uint32_t track_id, uint32_t folder_id, uint32_t storage_id, gboolean *new_album)
246 {
247 	LIBMTP_album_t *album;
248 
249 	album = g_hash_table_lookup (thread->albums, album_name);
250 	if (album != NULL) {
251 		/* add track to album */
252 		album->tracks = realloc (album->tracks, sizeof(uint32_t) * (album->no_tracks+1));
253 		album->tracks[album->no_tracks] = track_id;
254 		album->no_tracks++;
255 		rb_debug ("adding track ID %d to album ID %d; now has %d tracks",
256 			  track_id,
257 			  album->album_id,
258 			  album->no_tracks);
259 
260 		if (new_album != NULL) {
261 			*new_album = FALSE;
262 		}
263 	} else {
264 		/* add new album */
265 		album = LIBMTP_new_album_t ();
266 		album->name = strdup (album_name);
267 		album->no_tracks = 1;
268 		album->tracks = malloc (sizeof(uint32_t));
269 		album->tracks[0] = track_id;
270 		album->parent_id = folder_id;
271 		album->storage_id = storage_id;
272 
273 		rb_debug ("creating new album (%s) for track ID %d", album->name, track_id);
274 
275 		g_hash_table_insert (thread->albums, album->name, album);
276 		if (new_album != NULL) {
277 			*new_album = TRUE;
278 		}
279 	}
280 
281 	return album;
282 }
283 
284 static void
285 write_album_to_device (RBMtpThread *thread, LIBMTP_album_t *album, gboolean new_album)
286 {
287 	if (new_album) {
288 		if (LIBMTP_Create_New_Album (thread->device, album) != 0) {
289 			rb_debug ("LIBMTP_Create_New_Album failed..");
290 			rb_mtp_thread_report_errors (thread, FALSE);
291 		}
292 	} else {
293 		if (LIBMTP_Update_Album (thread->device, album) != 0) {
294 			rb_debug ("LIBMTP_Update_Album failed..");
295 			rb_mtp_thread_report_errors (thread, FALSE);
296 		}
297 	}
298 }
299 
300 static void
301 add_track_to_album_and_update (RBMtpThread *thread, RBMtpThreadTask *task)
302 {
303 	LIBMTP_album_t *album;
304 	gboolean new_album = FALSE;
305 
306 	album = add_track_to_album (thread, task->album, task->track_id, task->folder_id, task->storage_id, &new_album);
307 	write_album_to_device (thread, album, new_album);
308 }
309 
310 static void
311 remove_track_from_album (RBMtpThread *thread, RBMtpThreadTask *task)
312 {
313 	LIBMTP_album_t *album;
314 	int i;
315 
316 	album = g_hash_table_lookup (thread->albums, task->album);
317 	if (album == NULL) {
318 		rb_debug ("Couldn't find an album for %s", task->album);
319 		return;
320 	}
321 
322 	for (i = 0; i < album->no_tracks; i++) {
323 		if (album->tracks[i] == task->track_id) {
324 			break;
325 		}
326 	}
327 
328 	if (i == album->no_tracks) {
329 		rb_debug ("Couldn't find track %d in album %d", task->track_id, album->album_id);
330 		return;
331 	}
332 
333 	memmove (album->tracks + i, album->tracks + i + 1, album->no_tracks - (i+1));
334 	album->no_tracks--;
335 
336 	if (album->no_tracks == 0) {
337 		rb_debug ("deleting empty album %d", album->album_id);
338 		if (LIBMTP_Delete_Object (thread->device, album->album_id) != 0) {
339 			rb_mtp_thread_report_errors (thread, FALSE);
340 		}
341 		g_hash_table_remove (thread->albums, task->album);
342 	} else {
343 		rb_debug ("updating album %d: %d tracks remaining", album->album_id, album->no_tracks);
344 		if (LIBMTP_Update_Album (thread->device, album) != 0) {
345 			rb_mtp_thread_report_errors (thread, FALSE);
346 		}
347 	}
348 }
349 
350 static void
351 set_album_image (RBMtpThread *thread, RBMtpThreadTask *task)
352 {
353 	LIBMTP_filesampledata_t *albumart;
354 	LIBMTP_album_t *album;
355 	GError *error = NULL;
356 	char *image_data;
357 	gsize image_size;
358 	int ret;
359 	
360 	album = g_hash_table_lookup (thread->albums, task->album);
361 	if (album == NULL) {
362 		rb_debug ("Couldn't find an album for %s", task->album);
363 		return;
364 	}
365 	
366 	/* probably should scale the image down, since some devices have a size limit and they all have
367 	 * tiny displays anyway.
368 	 */
369 
370 	if (gdk_pixbuf_save_to_buffer (task->image, &image_data, &image_size, "jpeg", &error, NULL) == FALSE) {
371 		rb_debug ("unable to convert album art image to a JPEG buffer: %s", error->message);
372 		g_error_free (error);
373 		return;
374 	}
375 
376 	albumart = LIBMTP_new_filesampledata_t ();
377 	albumart->filetype = LIBMTP_FILETYPE_JPEG;
378 	albumart->data = image_data;
379 	albumart->size = image_size;
380 
381 	ret = LIBMTP_Send_Representative_Sample (thread->device, album->album_id, albumart);
382 	if (ret != 0) {
383 		rb_mtp_thread_report_errors (thread, TRUE);
384 	} else {
385 		rb_debug ("successfully set album art for %s (%" G_GSIZE_FORMAT " bytes)", task->album, image_size);
386 	}
387 
388 	/* libmtp will try to free this if we don't clear the pointer */
389 	albumart->data = NULL;
390 	LIBMTP_destroy_filesampledata_t (albumart);
391 }
392 
393 static void
394 get_track_list (RBMtpThread *thread, RBMtpThreadTask *task)
395 {
396 	RBMtpTrackListCallback cb = task->callback;
397 	gboolean device_forgets_albums = TRUE;
398 	GHashTable *update_albums = NULL;
399 	LIBMTP_track_t *tracks = NULL;
400 	LIBMTP_album_t *albums;
401 	LIBMTP_album_t *album;
402 
403 	/* get all the albums */
404 	albums = LIBMTP_Get_Album_List (thread->device);
405 	rb_mtp_thread_report_errors (thread, FALSE);
406 	if (albums != NULL) {
407 		LIBMTP_album_t *album;
408 
409 		for (album = albums; album != NULL; album = album->next) {
410 			if (album->name == NULL)
411 				continue;
412 
413 			rb_debug ("album: %s, %d tracks", album->name, album->no_tracks);
414 			g_hash_table_insert (thread->albums, album->name, album);
415 			if (album->no_tracks != 0) {
416 				device_forgets_albums = FALSE;
417 			}
418 		}
419 
420 		if (device_forgets_albums) {
421 			rb_debug ("stupid mtp device detected.  will rebuild all albums.");
422 		}
423 	} else {
424 		rb_debug ("No albums");
425 		device_forgets_albums = FALSE;
426 	}
427 
428 	tracks = LIBMTP_Get_Tracklisting_With_Callback (thread->device, NULL, NULL);
429 	rb_mtp_thread_report_errors (thread, FALSE);
430 	if (tracks == NULL) {
431 		rb_debug ("no tracks on the device");
432 	} else if (device_forgets_albums) {
433 		LIBMTP_track_t *track;
434 	       
435 		rb_debug ("rebuilding albums");
436 		update_albums = g_hash_table_new (g_direct_hash, g_direct_equal);
437 		for (track = tracks; track != NULL; track = track->next) {
438 			if (track->album != NULL) {
439 				gboolean new_album = FALSE;
440 				album = add_track_to_album (thread, track->album, track->item_id, track->parent_id, track->storage_id, &new_album);
441 				g_hash_table_insert (update_albums, album, GINT_TO_POINTER (new_album));
442 			}
443 		}
444 		rb_debug ("finished rebuilding albums");
445 	}
446 
447 	cb (tracks, task->user_data);
448 	/* the callback owns the tracklist */
449 
450 	if (device_forgets_albums) {
451 		GHashTableIter iter;
452 		gpointer album_ptr;
453 		gpointer new_album_ptr;
454 
455 		rb_debug ("writing rebuilt albums back to the device");
456 		g_hash_table_iter_init (&iter, update_albums);
457 		while (g_hash_table_iter_next (&iter, &album_ptr, &new_album_ptr)) {
458 			album = album_ptr;
459 			rb_debug ("writing album \"%s\"", album->name);
460 			write_album_to_device (thread, album, GPOINTER_TO_INT (new_album_ptr));
461 		}
462 		g_hash_table_destroy (update_albums);
463 
464 		rb_debug ("removing remaining empty albums");
465 		g_hash_table_iter_init (&iter, thread->albums);
466 		while (g_hash_table_iter_next (&iter, NULL, &album_ptr)) {
467 			int ret;
468 
469 			album = album_ptr;
470 			if (album->no_tracks == 0) {
471 				rb_debug ("pruning empty album \"%s\"", album->name); 
472 				ret = LIBMTP_Delete_Object (thread->device, album->album_id);
473 				if (ret != 0) {
474 					rb_mtp_thread_report_errors (thread, FALSE);
475 				}
476 				g_hash_table_iter_remove (&iter);
477 			}
478 		}
479 
480 		rb_debug ("finished updating albums on the device");
481 	}
482 }
483 
484 static void
485 download_track (RBMtpThread *thread, RBMtpThreadTask *task)
486 {
487 	LIBMTP_file_t *fileinfo;
488 	LIBMTP_error_t *stack;
489 	GError *error = NULL;
490 	GFile *dir;
491 	RBMtpDownloadCallback cb = (RBMtpDownloadCallback) task->callback;
492 
493 	/* first, check there's enough space to copy it */
494 	fileinfo = LIBMTP_Get_Filemetadata (thread->device, task->track_id);
495 	if (fileinfo == NULL) {
496 		stack = LIBMTP_Get_Errorstack (thread->device);
497 		rb_debug ("unable to get track metadata for %u: %s", task->track_id, stack->error_text);
498 		error = g_error_new (RB_MTP_THREAD_ERROR,
499 				     RB_MTP_THREAD_ERROR_GET_TRACK,
500 				     _("Unable to copy file from MTP device: %s"),
501 				     stack->error_text);
502 		LIBMTP_Clear_Errorstack (thread->device);
503 
504 		cb (task->track_id, NULL, error, task->user_data);
505 		g_error_free (error);
506 		return;
507 	}
508 
509 	if (task->filename[0] == '\0') {
510 		dir = g_file_new_for_path (g_get_tmp_dir ());
511 	} else {
512 		GFile *file = g_file_new_for_path (task->filename);
513 		dir = g_file_get_parent (file);
514 		g_object_unref (file);
515 	}
516 	rb_debug ("checking for %" G_GINT64_FORMAT " bytes available", fileinfo->filesize);
517 	if (rb_check_dir_has_space (dir, fileinfo->filesize) == FALSE) {
518 		char *dpath = g_file_get_path (dir);
519 		rb_debug ("not enough space in %s", dpath);
520 		error = g_error_new (RB_MTP_THREAD_ERROR, RB_MTP_THREAD_ERROR_NO_SPACE,
521 				     _("Not enough space in %s"), dpath);
522 		g_free (dpath);
523 	}
524 	LIBMTP_destroy_file_t (fileinfo);
525 	g_object_unref (dir);
526 
527 	if (error != NULL) {
528 		rb_debug ("bailing out due to error: %s", error->message);
529 		cb (task->track_id, NULL, error, task->user_data);
530 		g_error_free (error);
531 		return;
532 	}
533 
534 	if (task->filename[0] == '\0') {
535 		/* download to a temporary file */
536 		int fd;
537 		GError *tmperror = NULL;
538 
539 		g_free (task->filename);
540 		fd = g_file_open_tmp ("rb-mtp-temp-XXXXXX", &task->filename, &tmperror);
541 		if (fd == -1) {
542 			rb_debug ("unable to open temporary file: %s", tmperror->message);
543 			error = g_error_new (RB_MTP_THREAD_ERROR,
544 					     RB_MTP_THREAD_ERROR_TEMPFILE,
545 					     _("Unable to open temporary file: %s"),
546 					     tmperror->message);
547 			g_error_free (tmperror);
548 
549 			cb (task->track_id, NULL, error, task->user_data);
550 			g_error_free (error);
551 			return;
552 		} else {
553 			rb_debug ("downloading track %u to file descriptor %d", task->track_id, fd);
554 			if (LIBMTP_Get_Track_To_File_Descriptor (thread->device, task->track_id, fd, NULL, NULL)) {
555 				stack = LIBMTP_Get_Errorstack (thread->device);
556 				rb_debug ("unable to retrieve track %u: %s", task->track_id, stack->error_text);
557 				error = g_error_new (RB_MTP_THREAD_ERROR, RB_MTP_THREAD_ERROR_GET_TRACK,
558 						     _("Unable to copy file from MTP device: %s"),
559 						     stack->error_text);
560 				LIBMTP_Clear_Errorstack (thread->device);
561 
562 				cb (task->track_id, NULL, error, task->user_data);
563 				g_error_free (error);
564 				close (fd);
565 				remove (task->filename);
566 				return;
567 			}
568 			rb_debug ("done downloading track");
569 
570 			close (fd);
571 		}
572 	} else {
573 		if (LIBMTP_Get_Track_To_File (thread->device, task->track_id, task->filename, NULL, NULL)) {
574 			stack = LIBMTP_Get_Errorstack (thread->device);
575 			error = g_error_new (RB_MTP_THREAD_ERROR, RB_MTP_THREAD_ERROR_GET_TRACK,
576 					     _("Unable to copy file from MTP device: %s"),
577 					     stack->error_text);
578 			LIBMTP_Clear_Errorstack (thread->device);
579 
580 			cb (task->track_id, NULL, error, task->user_data);
581 			g_error_free (error);
582 			return;
583 		}
584 	}
585 
586 	cb (task->track_id, task->filename, NULL, task->user_data);
587 }
588 
589 static void
590 upload_track (RBMtpThread *thread, RBMtpThreadTask *task)
591 {
592 	RBMtpUploadCallback cb = (RBMtpUploadCallback) task->callback;
593 	LIBMTP_error_t *stack;
594 	GError *error = NULL;
595 
596 	if (LIBMTP_Send_Track_From_File (thread->device, task->filename, task->track, NULL, NULL)) {
597 		stack = LIBMTP_Get_Errorstack (thread->device);
598 		rb_debug ("unable to send track: %s", stack->error_text);
599 
600 		if (stack->errornumber == LIBMTP_ERROR_STORAGE_FULL) {
601 			error = g_error_new (RB_MTP_THREAD_ERROR, RB_MTP_THREAD_ERROR_NO_SPACE,
602 					     _("No space left on MTP device"));
603 		} else {
604 			error = g_error_new (RB_MTP_THREAD_ERROR, RB_MTP_THREAD_ERROR_SEND_TRACK,
605 					     _("Unable to send file to MTP device: %s"),
606 					     stack->error_text);
607 		}
608 		LIBMTP_Clear_Errorstack (thread->device);
609 		task->track->item_id = 0;		/* is this actually an invalid item ID? */
610 	}
611 	cb (task->track, error, task->user_data);
612 	g_clear_error (&error);
613 }
614 
615 static gboolean 
616 run_task (RBMtpThread *thread, RBMtpThreadTask *task)
617 {
618 	char *name = task_name (task);
619 	rb_debug ("running task: %s", name);
620 	g_free (name);
621 
622 	switch (task->task) {
623 	case OPEN_DEVICE:
624 		open_device (thread, task);
625 		break;
626 
627 	case CLOSE_DEVICE:
628 		return TRUE;
629 
630 	case SET_DEVICE_NAME:
631 		if (LIBMTP_Set_Friendlyname (thread->device, task->name)) {
632 			rb_mtp_thread_report_errors (thread, TRUE);
633 		}
634 		break;
635 
636 	case THREAD_CALLBACK:
637 		{
638 			RBMtpThreadCallback cb = (RBMtpThreadCallback)task->callback;
639 			cb (thread->device, task->user_data);
640 		}
641 		break;
642 
643 	case CREATE_FOLDER:
644 		create_folder (thread, task);
645 		break;
646 
647 	case ADD_TO_ALBUM:
648 		add_track_to_album_and_update (thread, task);
649 		break;
650 
651 	case REMOVE_FROM_ALBUM:
652 		remove_track_from_album (thread, task);
653 		break;
654 
655 	case SET_ALBUM_IMAGE:
656 		set_album_image (thread, task);
657 		break;
658 
659 	case GET_TRACK_LIST:
660 		get_track_list (thread, task);
661 		break;
662 
663 	case DELETE_TRACK:
664 		if (LIBMTP_Delete_Object (thread->device, task->track_id)) {
665 			rb_mtp_thread_report_errors (thread, TRUE);
666 		}
667 		break;
668 
669 	case UPLOAD_TRACK:
670 		upload_track (thread, task);
671 		break;
672 
673 	case DOWNLOAD_TRACK:
674 		download_track (thread, task);
675 		break;
676 
677 	default:
678 		g_assert_not_reached ();
679 	}
680 
681 	return FALSE;
682 }
683 
684 static gpointer
685 task_thread (RBMtpThread *thread)
686 {
687 	RBMtpThreadTask *task;
688 	gboolean quit = FALSE;
689 	GAsyncQueue *queue = g_async_queue_ref (thread->queue);
690 
691 	rb_debug ("MTP device worker thread starting");
692 	while (quit == FALSE) {
693 
694 		task = g_async_queue_pop (queue);
695 		quit = run_task (thread, task);
696 		destroy_task (task);
697 	}
698 
699 	rb_debug ("MTP device worker thread exiting");
700 	
701 	/* clean up any queued tasks */
702 	while ((task = g_async_queue_try_pop (queue)) != NULL)
703 		destroy_task (task);
704 
705 	g_async_queue_unref (queue);
706 	return NULL;
707 }
708 
709 /* callable interface */
710 
711 void
712 rb_mtp_thread_open_device (RBMtpThread *thread,
713 			   LIBMTP_raw_device_t *raw_device,
714 			   RBMtpOpenCallback callback,
715 			   gpointer data,
716 			   GDestroyNotify destroy_data)
717 {
718 	RBMtpThreadTask *task = create_task (OPEN_DEVICE);
719 	task->raw_device = raw_device;
720 	task->callback = callback;
721 	task->user_data = data;
722 	task->destroy_data = destroy_data;
723 	queue_task (thread, task);
724 }
725 
726 void
727 rb_mtp_thread_set_device_name (RBMtpThread *thread, const char *name)
728 {
729 	RBMtpThreadTask *task = create_task (SET_DEVICE_NAME);
730 	task->name = g_strdup (name);
731 	queue_task (thread, task);
732 }
733 
734 void
735 rb_mtp_thread_create_folder (RBMtpThread *thread,
736 			     const char **path,
737 			     RBMtpCreateFolderCallback func,
738 			     gpointer data,
739 			     GDestroyNotify destroy_data)
740 {
741 	RBMtpThreadTask *task = create_task (CREATE_FOLDER);
742 	task->path = g_strdupv ((char **)path);
743 	task->callback = func;
744 	task->user_data = data;
745 	task->destroy_data = destroy_data;
746 	queue_task (thread, task);
747 }
748 
749 void
750 rb_mtp_thread_add_to_album (RBMtpThread *thread, LIBMTP_track_t *track, const char *album)
751 {
752 	RBMtpThreadTask *task = create_task (ADD_TO_ALBUM);
753 	task->track_id = track->item_id;
754 	task->folder_id = track->parent_id;
755 	task->storage_id = track->storage_id;
756 	task->album = g_strdup (album);
757 	queue_task (thread, task);
758 }
759 
760 void
761 rb_mtp_thread_remove_from_album (RBMtpThread *thread, LIBMTP_track_t *track, const char *album)
762 {
763 	RBMtpThreadTask *task = create_task (REMOVE_FROM_ALBUM);
764 	task->track_id = track->item_id;
765 	task->storage_id = track->storage_id;
766 	task->album = g_strdup (album);
767 	queue_task (thread, task);
768 }
769 
770 void
771 rb_mtp_thread_set_album_image (RBMtpThread *thread, const char *album, GdkPixbuf *image)
772 {
773 	RBMtpThreadTask *task = create_task (SET_ALBUM_IMAGE);
774 	task->album = g_strdup (album);
775 	task->image = g_object_ref (image);
776 	queue_task (thread, task);
777 }
778 
779 void
780 rb_mtp_thread_get_track_list (RBMtpThread *thread,
781 			      RBMtpTrackListCallback callback,
782 			      gpointer data,
783 			      GDestroyNotify destroy_data)
784 {
785 	RBMtpThreadTask *task = create_task (GET_TRACK_LIST);
786 	task->callback = callback;
787 	task->user_data = data;
788 	task->destroy_data = destroy_data;
789 	queue_task (thread, task);
790 }
791 
792 void
793 rb_mtp_thread_delete_track (RBMtpThread *thread, LIBMTP_track_t *track)
794 {
795 	RBMtpThreadTask *task = create_task (DELETE_TRACK);
796 	task->track_id = track->item_id;
797 	task->storage_id = track->storage_id;
798 	queue_task (thread, task);
799 }
800 
801 void
802 rb_mtp_thread_upload_track (RBMtpThread *thread,
803 			    LIBMTP_track_t *track,
804 			    const char *filename,
805 			    RBMtpUploadCallback func,
806 			    gpointer data,
807 			    GDestroyNotify destroy_data)
808 {
809 	RBMtpThreadTask *task = create_task (UPLOAD_TRACK);
810 	task->track = track;
811 	task->filename = g_strdup (filename);
812 	task->callback = func;
813 	task->user_data = data;
814 	task->destroy_data = destroy_data;
815 	queue_task (thread, task);
816 }
817 
818 void
819 rb_mtp_thread_download_track (RBMtpThread *thread,
820 			      uint32_t track_id,
821 			      const char *filename,
822 			      RBMtpDownloadCallback func,
823 			      gpointer data,
824 			      GDestroyNotify destroy_data)
825 {
826 	RBMtpThreadTask *task = create_task (DOWNLOAD_TRACK);
827 	task->track_id = track_id;
828 	task->filename = g_strdup (filename);
829 	task->callback = func;
830 	task->user_data = data;
831 	task->destroy_data = destroy_data;
832 	queue_task (thread, task);
833 }
834 
835 void
836 rb_mtp_thread_queue_callback (RBMtpThread *thread,
837 			      RBMtpThreadCallback func,
838 			      gpointer data,
839 			      GDestroyNotify destroy_data)
840 {
841 	RBMtpThreadTask *task = create_task (THREAD_CALLBACK);
842 	task->callback = func;
843 	task->user_data = data;
844 	task->destroy_data = destroy_data;
845 	queue_task (thread, task);
846 }
847 
848 void
849 rb_mtp_thread_report_errors (RBMtpThread *thread, gboolean use_dialog)
850 {
851 	LIBMTP_error_t *stack;
852 
853 	for (stack = LIBMTP_Get_Errorstack (thread->device); stack != NULL; stack = stack->next) {
854 		if (use_dialog) {
855 			GDK_THREADS_ENTER ();
856 			rb_error_dialog (NULL, _("Media player device error"), "%s", stack->error_text);
857 			GDK_THREADS_LEAVE ();
858 
859 			/* only display one dialog box per error */
860 			use_dialog = FALSE;
861 		} else {
862 			g_warning ("libmtp error: %s", stack->error_text);
863 		}
864 	}
865 
866 	LIBMTP_Clear_Errorstack (thread->device);
867 }
868 
869 /* GObject things */
870 
871 static void
872 impl_finalize (GObject *object)
873 {
874 	RBMtpThread *thread = RB_MTP_THREAD (object);
875 	RBMtpThreadTask *task;
876 
877 	rb_debug ("killing MTP worker thread");
878 	task = create_task (CLOSE_DEVICE);
879 	queue_task (thread, task);
880 	if (thread->thread != g_thread_self ()) {
881 		g_thread_join (thread->thread);
882 		rb_debug ("MTP worker thread exited");
883 	} else {
884 		rb_debug ("we're on the MTP worker thread..");
885 	}
886 
887 	g_async_queue_unref (thread->queue);
888 
889 	g_hash_table_destroy (thread->albums);
890 
891 	if (thread->device != NULL) {
892 		LIBMTP_Release_Device (thread->device);
893 	}
894 
895 	G_OBJECT_CLASS (rb_mtp_thread_parent_class)->finalize (object);
896 }
897 
898 static void
899 rb_mtp_thread_init (RBMtpThread *thread)
900 {
901 	thread->queue = g_async_queue_new ();
902 	
903 	thread->albums = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) LIBMTP_destroy_album_t);
904 
905 	thread->thread = g_thread_new ("mtp", (GThreadFunc) task_thread, thread);
906 }
907 
908 static void
909 rb_mtp_thread_class_init (RBMtpThreadClass *klass)
910 {
911 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
912 
913 	object_class->finalize = impl_finalize;
914 }
915 
916 static void
917 rb_mtp_thread_class_finalize (RBMtpThreadClass *klass)
918 {
919 }
920 
921 RBMtpThread *
922 rb_mtp_thread_new (void)
923 {
924 	return RB_MTP_THREAD (g_object_new (RB_TYPE_MTP_THREAD, NULL));
925 }
926 
927 GQuark
928 rb_mtp_thread_error_quark (void)
929 {
930 	static GQuark quark = 0;
931 	if (!quark)
932 		quark = g_quark_from_static_string ("rb_mtp_thread_error");
933 
934 	return quark;
935 }
936 
937 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
938 
939 GType
940 rb_mtp_thread_error_get_type (void)
941 {
942 	static GType etype = 0;
943 
944 	if (etype == 0)	{
945 		static const GEnumValue values[] = {
946 			ENUM_ENTRY (RB_MTP_THREAD_ERROR_NO_SPACE, "no-space"),
947 			ENUM_ENTRY (RB_MTP_THREAD_ERROR_TEMPFILE, "tempfile-failed"),
948 			ENUM_ENTRY (RB_MTP_THREAD_ERROR_GET_TRACK, "track-get-failed"),
949 			{ 0, 0, 0 }
950 		};
951 
952 		etype = g_enum_register_static ("RBMTPThreadError", values);
953 	}
954 
955 	return etype;
956 }
957 
958 void
959 _rb_mtp_thread_register_type (GTypeModule *module)
960 {
961 	rb_mtp_thread_register_type (module);
962 }