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);
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);
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);
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);
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);
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);
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);
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 	LIBMTP_track_t *tracks = NULL;
398 	LIBMTP_album_t *albums;
399 
400 	/* get all the albums */
401 	albums = LIBMTP_Get_Album_List (thread->device);
402 	rb_mtp_thread_report_errors (thread);
403 	if (albums != NULL) {
404 		LIBMTP_album_t *album;
405 
406 		for (album = albums; album != NULL; album = album->next) {
407 			if (album->name == NULL)
408 				continue;
409 
410 			rb_debug ("album: %s, %d tracks", album->name, album->no_tracks);
411 			g_hash_table_insert (thread->albums, album->name, album);
412 		}
413 	} else {
414 		rb_debug ("No albums");
415 	}
416 
417 	tracks = LIBMTP_Get_Tracklisting_With_Callback (thread->device, NULL, NULL);
418 	rb_mtp_thread_report_errors (thread);
419 	if (tracks == NULL) {
420 		rb_debug ("no tracks on the device");
421 	}
422 
423 	cb (tracks, task->user_data);
424 	/* the callback owns the tracklist */
425 }
426 
427 static void
428 download_track (RBMtpThread *thread, RBMtpThreadTask *task)
429 {
430 	LIBMTP_file_t *fileinfo;
431 	LIBMTP_error_t *stack;
432 	GError *error = NULL;
433 	GFile *dir;
434 	RBMtpDownloadCallback cb = (RBMtpDownloadCallback) task->callback;
435 
436 	/* first, check there's enough space to copy it */
437 	fileinfo = LIBMTP_Get_Filemetadata (thread->device, task->track_id);
438 	if (fileinfo == NULL) {
439 		stack = LIBMTP_Get_Errorstack (thread->device);
440 		rb_debug ("unable to get track metadata for %u: %s", task->track_id, stack->error_text);
441 		error = g_error_new (RB_MTP_THREAD_ERROR,
442 				     RB_MTP_THREAD_ERROR_GET_TRACK,
443 				     _("Unable to copy file from MTP device: %s"),
444 				     stack->error_text);
445 		LIBMTP_Clear_Errorstack (thread->device);
446 
447 		cb (task->track_id, NULL, error, task->user_data);
448 		g_error_free (error);
449 		return;
450 	}
451 
452 	if (task->filename[0] == '\0') {
453 		dir = g_file_new_for_path (g_get_tmp_dir ());
454 	} else {
455 		GFile *file = g_file_new_for_path (task->filename);
456 		dir = g_file_get_parent (file);
457 		g_object_unref (file);
458 	}
459 	rb_debug ("checking for %" G_GINT64_FORMAT " bytes available", fileinfo->filesize);
460 	if (rb_check_dir_has_space (dir, fileinfo->filesize) == FALSE) {
461 		char *dpath = g_file_get_path (dir);
462 		rb_debug ("not enough space in %s", dpath);
463 		error = g_error_new (RB_MTP_THREAD_ERROR, RB_MTP_THREAD_ERROR_NO_SPACE,
464 				     _("Not enough space in %s"), dpath);
465 		g_free (dpath);
466 	}
467 	LIBMTP_destroy_file_t (fileinfo);
468 	g_object_unref (dir);
469 
470 	if (error != NULL) {
471 		rb_debug ("bailing out due to error: %s", error->message);
472 		cb (task->track_id, NULL, error, task->user_data);
473 		g_error_free (error);
474 		return;
475 	}
476 
477 	if (task->filename[0] == '\0') {
478 		/* download to a temporary file */
479 		int fd;
480 		GError *tmperror = NULL;
481 
482 		g_free (task->filename);
483 		fd = g_file_open_tmp ("rb-mtp-temp-XXXXXX", &task->filename, &tmperror);
484 		if (fd == -1) {
485 			rb_debug ("unable to open temporary file: %s", tmperror->message);
486 			error = g_error_new (RB_MTP_THREAD_ERROR,
487 					     RB_MTP_THREAD_ERROR_TEMPFILE,
488 					     _("Unable to open temporary file: %s"),
489 					     tmperror->message);
490 			g_error_free (tmperror);
491 
492 			cb (task->track_id, NULL, error, task->user_data);
493 			g_error_free (error);
494 			return;
495 		} else {
496 			rb_debug ("downloading track %u to file descriptor %d", task->track_id, fd);
497 			if (LIBMTP_Get_Track_To_File_Descriptor (thread->device, task->track_id, fd, NULL, NULL)) {
498 				stack = LIBMTP_Get_Errorstack (thread->device);
499 				rb_debug ("unable to retrieve track %u: %s", task->track_id, stack->error_text);
500 				error = g_error_new (RB_MTP_THREAD_ERROR, RB_MTP_THREAD_ERROR_GET_TRACK,
501 						     _("Unable to copy file from MTP device: %s"),
502 						     stack->error_text);
503 				LIBMTP_Clear_Errorstack (thread->device);
504 
505 				cb (task->track_id, NULL, error, task->user_data);
506 				g_error_free (error);
507 				close (fd);
508 				remove (task->filename);
509 				return;
510 			}
511 			rb_debug ("done downloading track");
512 
513 			close (fd);
514 		}
515 	} else {
516 		if (LIBMTP_Get_Track_To_File (thread->device, task->track_id, task->filename, NULL, NULL)) {
517 			stack = LIBMTP_Get_Errorstack (thread->device);
518 			error = g_error_new (RB_MTP_THREAD_ERROR, RB_MTP_THREAD_ERROR_GET_TRACK,
519 					     _("Unable to copy file from MTP device: %s"),
520 					     stack->error_text);
521 			LIBMTP_Clear_Errorstack (thread->device);
522 
523 			cb (task->track_id, NULL, error, task->user_data);
524 			g_error_free (error);
525 			return;
526 		}
527 	}
528 
529 	cb (task->track_id, task->filename, NULL, task->user_data);
530 }
531 
532 static int
533 upload_progress (const uint64_t sent, const uint64_t total, const void * const data)
534 {
535 	rb_debug ("upload: %lu of %lu", sent, total);
536 	return 0;
537 }
538 
539 static void
540 upload_track (RBMtpThread *thread, RBMtpThreadTask *task)
541 {
542 	RBMtpUploadCallback cb = (RBMtpUploadCallback) task->callback;
543 	LIBMTP_error_t *stack;
544 	GError *error = NULL;
545 
546 	if (LIBMTP_Send_Track_From_File (thread->device, task->filename, task->track, upload_progress, NULL)) {
547 		stack = LIBMTP_Get_Errorstack (thread->device);
548 		rb_debug ("unable to send track: %s", stack->error_text);
549 
550 		if (stack->errornumber == LIBMTP_ERROR_STORAGE_FULL) {
551 			error = g_error_new (RB_MTP_THREAD_ERROR, RB_MTP_THREAD_ERROR_NO_SPACE,
552 					     _("No space left on MTP device"));
553 		} else {
554 			error = g_error_new (RB_MTP_THREAD_ERROR, RB_MTP_THREAD_ERROR_SEND_TRACK,
555 					     _("Unable to send file to MTP device: %s"),
556 					     stack->error_text);
557 		}
558 		LIBMTP_Clear_Errorstack (thread->device);
559 		task->track->item_id = 0;		/* is this actually an invalid item ID? */
560 	}
561 	cb (task->track, error, task->user_data);
562 	g_clear_error (&error);
563 }
564 
565 static gboolean
566 run_task (RBMtpThread *thread, RBMtpThreadTask *task)
567 {
568 	char *name = task_name (task);
569 	rb_debug ("running task: %s", name);
570 	g_free (name);
571 
572 	switch (task->task) {
573 	case OPEN_DEVICE:
574 		open_device (thread, task);
575 		break;
576 
577 	case CLOSE_DEVICE:
578 		return TRUE;
579 
580 	case SET_DEVICE_NAME:
581 		if (LIBMTP_Set_Friendlyname (thread->device, task->name)) {
582 			rb_mtp_thread_report_errors (thread);
583 		}
584 		break;
585 
586 	case THREAD_CALLBACK:
587 		{
588 			RBMtpThreadCallback cb = (RBMtpThreadCallback)task->callback;
589 			cb (thread->device, task->user_data);
590 		}
591 		break;
592 
593 	case CREATE_FOLDER:
594 		create_folder (thread, task);
595 		break;
596 
597 	case ADD_TO_ALBUM:
598 		add_track_to_album_and_update (thread, task);
599 		break;
600 
601 	case REMOVE_FROM_ALBUM:
602 		remove_track_from_album (thread, task);
603 		break;
604 
605 	case SET_ALBUM_IMAGE:
606 		set_album_image (thread, task);
607 		break;
608 
609 	case GET_TRACK_LIST:
610 		get_track_list (thread, task);
611 		break;
612 
613 	case DELETE_TRACK:
614 		if (LIBMTP_Delete_Object (thread->device, task->track_id)) {
615 			rb_mtp_thread_report_errors (thread);
616 		}
617 		break;
618 
619 	case UPLOAD_TRACK:
620 		upload_track (thread, task);
621 		break;
622 
623 	case DOWNLOAD_TRACK:
624 		download_track (thread, task);
625 		break;
626 
627 	default:
628 		g_assert_not_reached ();
629 	}
630 
631 	return FALSE;
632 }
633 
634 static gpointer
635 task_thread (RBMtpThread *thread)
636 {
637 	RBMtpThreadTask *task;
638 	gboolean quit = FALSE;
639 	GAsyncQueue *queue = g_async_queue_ref (thread->queue);
640 
641 	rb_debug ("MTP device worker thread starting");
642 	while (quit == FALSE) {
643 
644 		task = g_async_queue_pop (queue);
645 		quit = run_task (thread, task);
646 		destroy_task (task);
647 	}
648 
649 	rb_debug ("MTP device worker thread exiting");
650 
651 	/* clean up any queued tasks */
652 	while ((task = g_async_queue_try_pop (queue)) != NULL)
653 		destroy_task (task);
654 
655 	g_async_queue_unref (queue);
656 	return NULL;
657 }
658 
659 /* callable interface */
660 
661 void
662 rb_mtp_thread_open_device (RBMtpThread *thread,
663 			   LIBMTP_raw_device_t *raw_device,
664 			   RBMtpOpenCallback callback,
665 			   gpointer data,
666 			   GDestroyNotify destroy_data)
667 {
668 	RBMtpThreadTask *task = create_task (OPEN_DEVICE);
669 	task->raw_device = raw_device;
670 	task->callback = callback;
671 	task->user_data = data;
672 	task->destroy_data = destroy_data;
673 	queue_task (thread, task);
674 }
675 
676 void
677 rb_mtp_thread_set_device_name (RBMtpThread *thread, const char *name)
678 {
679 	RBMtpThreadTask *task = create_task (SET_DEVICE_NAME);
680 	task->name = g_strdup (name);
681 	queue_task (thread, task);
682 }
683 
684 void
685 rb_mtp_thread_create_folder (RBMtpThread *thread,
686 			     const char **path,
687 			     RBMtpCreateFolderCallback func,
688 			     gpointer data,
689 			     GDestroyNotify destroy_data)
690 {
691 	RBMtpThreadTask *task = create_task (CREATE_FOLDER);
692 	task->path = g_strdupv ((char **)path);
693 	task->callback = func;
694 	task->user_data = data;
695 	task->destroy_data = destroy_data;
696 	queue_task (thread, task);
697 }
698 
699 void
700 rb_mtp_thread_add_to_album (RBMtpThread *thread, LIBMTP_track_t *track, const char *album)
701 {
702 	RBMtpThreadTask *task = create_task (ADD_TO_ALBUM);
703 	task->track_id = track->item_id;
704 	task->folder_id = track->parent_id;
705 	task->storage_id = track->storage_id;
706 	task->album = g_strdup (album);
707 	queue_task (thread, task);
708 }
709 
710 void
711 rb_mtp_thread_remove_from_album (RBMtpThread *thread, LIBMTP_track_t *track, const char *album)
712 {
713 	RBMtpThreadTask *task = create_task (REMOVE_FROM_ALBUM);
714 	task->track_id = track->item_id;
715 	task->storage_id = track->storage_id;
716 	task->album = g_strdup (album);
717 	queue_task (thread, task);
718 }
719 
720 void
721 rb_mtp_thread_set_album_image (RBMtpThread *thread, const char *album, GdkPixbuf *image)
722 {
723 	RBMtpThreadTask *task = create_task (SET_ALBUM_IMAGE);
724 	task->album = g_strdup (album);
725 	task->image = g_object_ref (image);
726 	queue_task (thread, task);
727 }
728 
729 void
730 rb_mtp_thread_get_track_list (RBMtpThread *thread,
731 			      RBMtpTrackListCallback callback,
732 			      gpointer data,
733 			      GDestroyNotify destroy_data)
734 {
735 	RBMtpThreadTask *task = create_task (GET_TRACK_LIST);
736 	task->callback = callback;
737 	task->user_data = data;
738 	task->destroy_data = destroy_data;
739 	queue_task (thread, task);
740 }
741 
742 void
743 rb_mtp_thread_delete_track (RBMtpThread *thread, LIBMTP_track_t *track)
744 {
745 	RBMtpThreadTask *task = create_task (DELETE_TRACK);
746 	task->track_id = track->item_id;
747 	task->storage_id = track->storage_id;
748 	queue_task (thread, task);
749 }
750 
751 void
752 rb_mtp_thread_upload_track (RBMtpThread *thread,
753 			    LIBMTP_track_t *track,
754 			    const char *filename,
755 			    RBMtpUploadCallback func,
756 			    gpointer data,
757 			    GDestroyNotify destroy_data)
758 {
759 	RBMtpThreadTask *task = create_task (UPLOAD_TRACK);
760 	task->track = track;
761 	task->filename = g_strdup (filename);
762 	task->callback = func;
763 	task->user_data = data;
764 	task->destroy_data = destroy_data;
765 	queue_task (thread, task);
766 }
767 
768 void
769 rb_mtp_thread_download_track (RBMtpThread *thread,
770 			      uint32_t track_id,
771 			      const char *filename,
772 			      RBMtpDownloadCallback func,
773 			      gpointer data,
774 			      GDestroyNotify destroy_data)
775 {
776 	RBMtpThreadTask *task = create_task (DOWNLOAD_TRACK);
777 	task->track_id = track_id;
778 	task->filename = g_strdup (filename);
779 	task->callback = func;
780 	task->user_data = data;
781 	task->destroy_data = destroy_data;
782 	queue_task (thread, task);
783 }
784 
785 void
786 rb_mtp_thread_queue_callback (RBMtpThread *thread,
787 			      RBMtpThreadCallback func,
788 			      gpointer data,
789 			      GDestroyNotify destroy_data)
790 {
791 	RBMtpThreadTask *task = create_task (THREAD_CALLBACK);
792 	task->callback = func;
793 	task->user_data = data;
794 	task->destroy_data = destroy_data;
795 	queue_task (thread, task);
796 }
797 
798 void
799 rb_mtp_thread_report_errors (RBMtpThread *thread)
800 {
801 	LIBMTP_error_t *stack;
802 
803 	for (stack = LIBMTP_Get_Errorstack (thread->device); stack != NULL; stack = stack->next) {
804 		g_warning ("libmtp error: %s", stack->error_text);
805 	}
806 
807 	LIBMTP_Clear_Errorstack (thread->device);
808 }
809 
810 /* GObject things */
811 
812 static void
813 impl_finalize (GObject *object)
814 {
815 	RBMtpThread *thread = RB_MTP_THREAD (object);
816 	RBMtpThreadTask *task;
817 
818 	rb_debug ("killing MTP worker thread");
819 	task = create_task (CLOSE_DEVICE);
820 	queue_task (thread, task);
821 	if (thread->thread != g_thread_self ()) {
822 		g_thread_join (thread->thread);
823 		rb_debug ("MTP worker thread exited");
824 	} else {
825 		rb_debug ("we're on the MTP worker thread..");
826 	}
827 
828 	g_async_queue_unref (thread->queue);
829 
830 	g_hash_table_destroy (thread->albums);
831 
832 	if (thread->device != NULL) {
833 		LIBMTP_Release_Device (thread->device);
834 	}
835 
836 	G_OBJECT_CLASS (rb_mtp_thread_parent_class)->finalize (object);
837 }
838 
839 static void
840 rb_mtp_thread_init (RBMtpThread *thread)
841 {
842 	thread->queue = g_async_queue_new ();
843 
844 	thread->albums = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) LIBMTP_destroy_album_t);
845 
846 	thread->thread = g_thread_new ("mtp", (GThreadFunc) task_thread, thread);
847 }
848 
849 static void
850 rb_mtp_thread_class_init (RBMtpThreadClass *klass)
851 {
852 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
853 
854 	object_class->finalize = impl_finalize;
855 }
856 
857 static void
858 rb_mtp_thread_class_finalize (RBMtpThreadClass *klass)
859 {
860 }
861 
862 RBMtpThread *
863 rb_mtp_thread_new (void)
864 {
865 	return RB_MTP_THREAD (g_object_new (RB_TYPE_MTP_THREAD, NULL));
866 }
867 
868 GQuark
869 rb_mtp_thread_error_quark (void)
870 {
871 	static GQuark quark = 0;
872 	if (!quark)
873 		quark = g_quark_from_static_string ("rb_mtp_thread_error");
874 
875 	return quark;
876 }
877 
878 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
879 
880 GType
881 rb_mtp_thread_error_get_type (void)
882 {
883 	static GType etype = 0;
884 
885 	if (etype == 0)	{
886 		static const GEnumValue values[] = {
887 			ENUM_ENTRY (RB_MTP_THREAD_ERROR_NO_SPACE, "no-space"),
888 			ENUM_ENTRY (RB_MTP_THREAD_ERROR_TEMPFILE, "tempfile-failed"),
889 			ENUM_ENTRY (RB_MTP_THREAD_ERROR_GET_TRACK, "track-get-failed"),
890 			{ 0, 0, 0 }
891 		};
892 
893 		etype = g_enum_register_static ("RBMTPThreadError", values);
894 	}
895 
896 	return etype;
897 }
898 
899 void
900 _rb_mtp_thread_register_type (GTypeModule *module)
901 {
902 	rb_mtp_thread_register_type (module);
903 }
904