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