1 /* gmpc-mserver (GMPC plugin)
2 * Copyright (C) 2007-2009 Qball Cow <qball@sarine.nl>
3 * Project homepage: http://gmpcwiki.sarine.nl/
4
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include <stdio.h>
21 #include <string.h>
22 #include <config.h>
23 #include <gtk/gtk.h>
24 #include <glib/gi18n-lib.h>
25 #include <glib/gstdio.h>
26 #include <libmpd/debug_printf.h>
27 #include <gmpc/plugin.h>
28 #include <gmpc/gmpc-profiles.h>
29 #include <gmpc/gmpc-mpddata-model.h>
30 #include <gmpc/gmpc-mpddata-treeview.h>
31 #include <gmpc/playlist3-messages.h>
32 #include <libmpd/libmpd-internal.h>
33 #include <sys/types.h>
34 #include <sys/socket.h>
35 #include <sys/stat.h>
36 #include <netinet/in.h>
37 #include <arpa/inet.h>
38 #include <microhttpd.h>
39 #include <config.h>
40
41 #include <tag_c.h>
42
43 #define URLS_CLASS "Music"
44
45 gmpcPlugin plugin;
46
47
48
49 static struct MHD_Daemon * d = NULL;
50
51 static GtkTreeModel *ls = NULL;
52 static GtkWidget *mserver_vbox = NULL;
53 static config_obj *cfg_urls = NULL;
54
55 static GtkTreeRowReference *mserver_ref = NULL;
56 void mserver_browser_activated(GtkWidget *tree,GtkTreePath *path);
57
mserver_browser_add(GtkWidget * cat_tree)58 void mserver_browser_add(GtkWidget *cat_tree)
59 {
60 GtkTreePath *path;
61 GtkTreeIter iter;
62 gint pos = cfg_get_single_value_as_int_with_default(config, "mserver","position",20);
63 GtkListStore *pl3_tree = (GtkListStore *)gtk_tree_view_get_model(GTK_TREE_VIEW(cat_tree));
64
65 playlist3_insert_browser(&iter, pos);
66 gtk_list_store_set(pl3_tree, &iter,
67 PL3_CAT_TYPE, plugin.id,
68 PL3_CAT_TITLE, _("Serve music"),
69 PL3_CAT_INT_ID, "",
70 PL3_CAT_ICON_ID, "gmpc-mserver",
71 -1);
72 /**
73 * Clean up old row reference if it exists
74 */
75 if (mserver_ref)
76 {
77 gtk_tree_row_reference_free(mserver_ref);
78 mserver_ref = NULL;
79 }
80 /**
81 * create row reference
82 */
83 path = gtk_tree_model_get_path(GTK_TREE_MODEL(playlist3_get_category_tree_store()), &iter);
84 if (path)
85 {
86 mserver_ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(playlist3_get_category_tree_store()), path);
87 gtk_tree_path_free(path);
88 }
89
90 }
91
mserver_save_myself(void)92 static void mserver_save_myself(void)
93 {
94 if (mserver_ref)
95 {
96 /**
97 * Store the location in the left pane
98 */
99 GtkTreePath *path = gtk_tree_row_reference_get_path(mserver_ref);
100 if(path)
101 {
102 gint *indices = gtk_tree_path_get_indices(path);
103 debug_printf(DEBUG_INFO,"Saving myself to position: %i\n", indices[0]);
104 cfg_set_single_value_as_int(config, "mserver","position",indices[0]);
105 gtk_tree_path_free(path);
106 }
107 }
108 }
109
110
111
112 /**
113 * Get/Set enabled
114 */
mserver_get_enabled()115 int mserver_get_enabled()
116 {
117 return cfg_get_single_value_as_int_with_default(config, "mserver", "enable", TRUE);
118 }
mserver_set_enabled(int enabled)119 void mserver_set_enabled(int enabled)
120 {
121 cfg_set_single_value_as_int(config, "mserver", "enable", enabled);
122 if (enabled)
123 {
124 if(mserver_ref == NULL)
125 {
126 mserver_browser_add(GTK_WIDGET(playlist3_get_category_tree_view()));
127 }
128 }
129 else if (mserver_ref)
130 {
131 GtkTreePath *path = gtk_tree_row_reference_get_path(mserver_ref);
132 if (path){
133 GtkTreeIter iter;
134
135 mserver_save_myself();
136 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(playlist3_get_category_tree_store()), &iter, path)){
137 gtk_list_store_remove(playlist3_get_category_tree_store(), &iter);
138 }
139 gtk_tree_path_free(path);
140 gtk_tree_row_reference_free(mserver_ref);
141 mserver_ref = NULL;
142 }
143 }
144 pl3_update_go_menu();
145 }
146
147 /**
148 * Web server part
149 **/
150 typedef struct {
151 FILE *fp;
152 size_t size;
153 size_t offset;
154 }str_block;
155
file_reader(str_block * file,size_t pos,char * buf,int max)156 static int file_reader(str_block *file, size_t pos, char * buf, int max)
157 {
158 int retv;
159 /* Set the right position, this is needed because MDH requiries the first block */
160 fseek(file->fp, file->offset+pos, SEEK_SET);
161 retv = fread(buf,sizeof(char), max, file->fp);
162 if(retv == 0)
163 {
164 if(feof(file->fp))
165 {
166 return -1;
167 }
168 if(ferror(file->fp))
169 {
170 printf("Error: %s\n", strerror(ferror(file->fp)));
171 return -1;
172 }
173 }
174 return retv;
175 }
apc_all(void * cls,const struct sockaddr * addr,socklen_t addrlen)176 static int apc_all(void * cls,
177 const struct sockaddr * addr,
178 socklen_t addrlen) {
179
180 return MHD_YES; /* accept connections from anyone */
181 }
file_close(str_block * str)182 static void file_close(str_block *str)
183 {
184 fclose(str->fp);
185 g_free(str);
186 }
ahc_echo(void * nothing,struct MHD_Connection * connection,const char * url,const char * method,const char * version,const char * upload_data,size_t * upload_data_size,void ** con_cls)187 static int ahc_echo(void *nothing,
188 struct MHD_Connection * connection,
189 const char * url,
190 const char * method,
191 const char *version,
192 const char * upload_data,
193 size_t * upload_data_size,
194 void **con_cls)
195 {
196 gchar *me;
197 struct MHD_Response * response;
198 int ret;
199
200 if (0 != strcmp(method, "GET"))
201 return MHD_NO; /* unexpected method */
202
203 /* Catch NULL url or a url that is to short */
204 if ( url == NULL || strlen(url) < 2)
205 return MHD_NO;
206 me = cfg_get_single_value_as_string(cfg_urls, URLS_CLASS, (char *)&url[1]);
207 if(me && g_file_test(me, G_FILE_TEST_EXISTS))
208 {
209 TagLib_File *file = NULL;
210 int tag_found = 0;
211 /* needs error checking */
212 gchar *mime_type = "application/octet-stream";
213 gchar *extention;
214 const char *position;
215 glong pos=0;
216 struct stat stats;
217 str_block *str = g_malloc0(sizeof(str_block));
218 stat(me, &stats);
219 str->size = -1;
220 str->fp=g_fopen(me, "r");
221
222 position = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_RANGE);
223 /* Seek end position */
224 fseek(str->fp, 0, SEEK_END);
225 str->size = (size_t)ftell(str->fp);
226 /*
227 fseek(str->fp, 0, SEEK_SET);
228 */
229 /* if read with offset, get the offset */
230 if(position)
231 {
232 pos = (long)g_ascii_strtoll(&position[6],NULL,10);
233 }
234 str->offset = pos;
235 /* Create response */
236 response = MHD_create_response_from_callback(str->size-pos,
237 4048,
238 (MHD_ContentReaderCallback)&file_reader,
239 str,
240 (MHD_ContentReaderFreeCallback)&file_close);
241
242 /* somehow gcc thinks I am not using this value, stupid gcc */
243 for(extention = &me[strlen(me)]; me != extention && extention[0] != '.';*extention--);
244 if(0 == strncasecmp(extention, ".flac",5)) {
245 mime_type = "audio/x-flac";
246 }else if (0 == strncasecmp(extention, ".mp3", 4))
247 {
248 mime_type = "audio/mpeg";
249 }else if (0 == strncasecmp(extention, ".ogg", 4))
250 {
251 mime_type = "audio/x-vorbis+ogg";
252 } else if (0 == strncasecmp(extention, ".wv", 3))
253 {
254 mime_type = "audio/x-wavpack";
255 }
256 else if (0 == strncasecmp(extention, ".wav", 3))
257 {
258 mime_type = "audio/x-wav";
259 }
260
261 MHD_add_response_header(response,MHD_HTTP_HEADER_CONTENT_TYPE,mime_type);
262 /* Tell mpd we can seek */
263 MHD_add_response_header(response,MHD_HTTP_HEADER_ACCEPT_RANGES,"bytes");
264
265
266 MHD_add_response_header(response,"icy-metaint" , "0");
267 /*
268 * Try to generate icy-data
269 */
270 file = taglib_file_new(me);
271 if(file)
272 {
273 TagLib_Tag * tag;
274 tag = taglib_file_tag(file);
275 if(tag)
276 {
277 gchar *artist, *title,*album;
278 album = taglib_tag_album(tag);
279 artist = taglib_tag_artist(tag);
280 title = taglib_tag_title(tag);
281 if(artist && artist && album) {
282 char *data = g_strdup_printf("%s - %s (%s)", title, artist,album);
283 MHD_add_response_header(response,"x-audiocast-name" , data);
284 g_free(data);
285 tag_found = 1;
286 }else if(artist && title){
287 char *data = g_strdup_printf("%s - %s", title, artist);
288 MHD_add_response_header(response,"x-audiocast-name" , data);
289 g_free(data);
290 tag_found = 1;
291 }
292
293 }
294 taglib_tag_free_strings();
295 taglib_file_free(file);
296 }
297
298 if(!tag_found){
299 extention = g_path_get_basename(me);
300 MHD_add_response_header(response,"x-audiocast-name" , extention);
301 g_free(extention);
302 }
303
304 ret = MHD_queue_response(connection,
305 MHD_HTTP_OK,
306 response);
307 MHD_destroy_response(response);
308 g_free(me);
309 return ret;
310
311 }
312 if(me)g_free(me);
313 return MHD_NO;
314 }
315
mserver_get_active_ip(void)316 static gchar *mserver_get_active_ip(void)
317 {
318 #ifdef HAVE_SOCKADDR_IN6_STRUCT
319 struct sockaddr_in6 name;
320 #else /* HAVE_SOCKADDR_IN6_STRUCT */
321 struct sockaddr_in name;
322 #endif /* HAVE_SOCKADDR_IN6_STRUCT */
323
324
325 socklen_t namelen = sizeof(name);
326 gchar *peername;
327 if(getsockname(connection->connection->sock,(struct sockaddr*)&name, &namelen)<0){
328 peername = g_strdup("localhost");
329 } else{
330 peername = g_strdup(inet_ntoa(name.sin_addr));
331 }
332 return peername;
333 }
334
335
336
mserver_destroy()337 void mserver_destroy()
338 {
339 /* Destroy the build in webserver */
340 if(d) {
341 MHD_stop_daemon(d);
342 d = NULL;
343 }
344 /* Destroy the internal list of songs */
345 if(ls) {
346 g_object_unref(ls);
347 ls = NULL;
348 }
349 /* Destroy the browser */
350 if(mserver_vbox) {
351 gtk_widget_destroy(mserver_vbox);
352 }
353 /* Destroy the config file */
354 if(cfg_urls) {
355 cfg_close(cfg_urls);
356 cfg_urls = NULL;
357 }
358 }
359
360 static int support_http = -1;
361 static int support_file = -1;
mserver_get_full_serve_path(const gchar * uid)362 static gchar *mserver_get_full_serve_path(const gchar *uid)
363 {
364 gchar *retv = NULL;
365 gchar *peername = mserver_get_active_ip();
366 if(support_file) {
367 gchar *temp = cfg_get_single_value_as_string(cfg_urls, URLS_CLASS,uid);
368 retv = g_strdup_printf("file://%s", temp);
369 g_free(temp);
370 }else if(support_http) {
371 retv = g_strdup_printf("http://%s:9876/%s", peername, uid);
372 }
373
374 g_free(peername);
375 return retv;
376 }
377
_add_file(MpdData * data,char * uid,char * filename)378 static MpdData * _add_file(MpdData *data, char *uid, char *filename)
379 {
380 TagLib_File *file;
381 TagLib_Tag *tag;
382 char *a = NULL;
383 mpd_Song *song = mpd_newSong();
384 data = mpd_new_data_struct_append(data);
385 data->type = MPD_DATA_TYPE_SONG;
386 data->song = song;
387 song->file = mserver_get_full_serve_path(uid);
388 song->name = g_strdup(uid);
389
390 file = taglib_file_new(filename);
391 if(file)
392 {
393 tag = taglib_file_tag(file);
394 if(tag)
395 {
396 if((a = taglib_tag_title(tag)) && a[0] != '\0')
397 song->title = g_strdup(a);
398 if((a = taglib_tag_album(tag)) && a[0] != '\0')
399 song->album = g_strdup(a);
400 if((a = taglib_tag_artist(tag)) && a[0] != '\0')
401 song->artist = g_strdup(a);
402 song->track = g_strdup_printf("%i", taglib_tag_track(tag));
403 if((a = taglib_tag_genre(tag)) && a[0] != '\0')
404 song->genre = g_strdup(a);
405 song->date = g_strdup_printf("%i", taglib_tag_year(tag));
406 }
407 taglib_tag_free_strings();
408 taglib_file_free(file);
409 }
410 return data;
411 }
mserver_browser_add_file()412 void mserver_browser_add_file()
413 {
414 GtkWidget *dialog = gtk_file_chooser_dialog_new("Add File", NULL,
415 GTK_FILE_CHOOSER_ACTION_OPEN,
416 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
417 GTK_STOCK_OK, GTK_RESPONSE_OK,
418 NULL);
419
420 GtkFileFilter *filter = gtk_file_filter_new();
421 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), FALSE);
422 gtk_file_filter_set_name(filter, "all");
423 gtk_file_filter_add_pattern(filter,"*.wav");
424 gtk_file_filter_add_pattern(filter,"*.ogg");
425 gtk_file_filter_add_pattern(filter,"*.mp3");
426 gtk_file_filter_add_pattern(filter,"*.flac");
427 gtk_file_filter_add_pattern(filter,"*.wv");
428 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
429 filter = gtk_file_filter_new();
430 gtk_file_filter_set_name(filter, "wav");
431 gtk_file_filter_add_pattern(filter,"*.wav");
432 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
433
434 filter = gtk_file_filter_new();
435 gtk_file_filter_set_name(filter, "ogg");
436 gtk_file_filter_add_pattern(filter,"*.ogg");
437 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
438 filter = gtk_file_filter_new();
439 gtk_file_filter_set_name(filter, "mp3");
440 gtk_file_filter_add_pattern(filter,"*.mp3");
441 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
442 filter = gtk_file_filter_new();
443 gtk_file_filter_set_name(filter, "flac");
444 gtk_file_filter_add_pattern(filter,"*.flac");
445 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
446 filter = gtk_file_filter_new();
447 gtk_file_filter_set_name(filter, "wavpack");
448 gtk_file_filter_add_pattern(filter,"*.wv");
449 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
450
451 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog),TRUE);
452
453
454 gtk_widget_set_size_request(GTK_WIDGET(dialog), 300,400);
455 gtk_widget_show(dialog);
456 switch(gtk_dialog_run(GTK_DIALOG(dialog)))
457 {
458 case GTK_RESPONSE_OK:
459 {
460 GSList *filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
461 if(filenames)
462 {
463 MpdData *data = gmpc_mpddata_model_steal_mpd_data(GMPC_MPDDATA_MODEL(ls));
464 GSList *iter = filenames;
465 if(data)
466 while(!mpd_data_is_last(data)) data = mpd_data_get_next(data);
467 for(;iter;iter= g_slist_next(iter))
468 {
469 gchar *filename = iter->data;
470 guint id = g_random_int();
471 gchar *name = g_strdup_printf("%u", id);
472 data = _add_file(data,name,filename);
473
474 cfg_set_single_value_as_string(cfg_urls,URLS_CLASS, name, filename);
475 g_free(name);
476 }
477 g_slist_foreach(filenames, (GFunc)g_free, NULL);
478 g_slist_free(filenames);
479 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(ls), mpd_data_get_first(data));
480 }
481 }
482 default:
483 break;
484 }
485 gtk_widget_destroy(dialog);
486 }
mserver_browser_activated(GtkWidget * tree,GtkTreePath * path)487 void mserver_browser_activated(GtkWidget *tree, GtkTreePath *path)
488 {
489 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree));
490 GtkTreeIter iter;
491 if(gtk_tree_model_get_iter(model, &iter,path))
492 {
493 gchar *uid = NULL;
494 gchar *url = NULL;
495 gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, MPDDATA_MODEL_COL_SONG_NAME, &uid, -1);
496 if(uid)
497 {
498 url = mserver_get_full_serve_path(uid);
499 mpd_playlist_add(connection, url);
500 g_free(url);
501 g_free(uid);
502 }
503 }
504 }
mserver_browser_remove_files(GtkButton * button,GtkTreeView * tree)505 static void mserver_browser_remove_files(GtkButton *button, GtkTreeView *tree)
506 {
507 MpdData *data;
508 MpdData_real *miter = NULL;
509 GtkTreeModel *model = gtk_tree_view_get_model(tree);
510 GtkTreeSelection *sel = gtk_tree_view_get_selection(tree);
511 GList *del_items = NULL;
512 GList *iter, *list = gtk_tree_selection_get_selected_rows(sel, &model);
513 int not_deleted = 0;
514
515 /**
516 * If there is no row selected.
517 * Make a list of all rows
518 */
519 if(list == NULL) {
520 GtkTreeIter iter;
521 if(gtk_tree_model_get_iter_first(model, &iter))
522 {
523 do{
524 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
525 list = g_list_append(list, path);
526 }while(gtk_tree_model_iter_next(model, &iter));
527 }
528 list = g_list_first(list);
529 }
530 /**
531 * Remove rows
532 */
533 for(iter = list; iter;iter = g_list_next(iter))
534 {
535 GtkTreeIter titer;
536 if(gtk_tree_model_get_iter(model, &titer, iter->data))
537 {
538 gchar *uids=NULL;
539 gchar *path=NULL;
540 gtk_tree_model_get(GTK_TREE_MODEL(model), &titer,
541 MPDDATA_MODEL_COL_SONG_NAME, &uids,
542 MPDDATA_MODEL_COL_PATH, &path,
543 -1);
544 if(path)
545 {
546 MpdData *data = NULL;
547 /* Check if the song that is going to be deleted is in the playlist */
548 mpd_playlist_search_start(connection, TRUE);
549 mpd_playlist_search_add_constraint(connection, MPD_TAG_ITEM_FILENAME, path);
550 data = mpd_playlist_search_commit(connection);
551 /* Delete the songs that are in the playlist from he playlist */
552 if (data){
553 /* Make sure it is not deleted */
554 g_free(uids);
555 uids = NULL;
556 not_deleted++;
557 mpd_data_free(data);
558 }
559 g_free(path);
560 }
561 if(uids)
562 {
563 /* Delete the item from the config file */
564 cfg_del_single_value(cfg_urls, URLS_CLASS, uids);
565 del_items = g_list_append(del_items,uids);
566 }
567
568 }
569 }
570 g_list_foreach (list, (GFunc)gtk_tree_path_free, NULL);
571 g_list_free(list);
572
573 /* Remove the items from the tree view */
574 data = gmpc_mpddata_model_steal_mpd_data(GMPC_MPDDATA_MODEL(ls));
575 if(data) miter =(MpdData_real *)mpd_data_get_first(data);
576 iter = g_list_first(del_items);
577 for(;iter;iter= g_list_next(iter))
578 {
579 gchar *uid = iter->data;
580 while(strcmp(miter->song->name, uid) != 0 && miter) miter = miter->next;
581 if(miter){
582 miter = (MpdData_real *)mpd_data_delete_item((MpdData *)miter);
583 }
584 }
585 data = (MpdData *)miter;
586 g_list_foreach(del_items, (GFunc) g_free, NULL);
587 g_list_free(del_items);
588 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(ls), mpd_data_get_first(data));
589
590 /**
591 * Tell the user that files where not deleted
592 */
593 if(not_deleted > 0 )
594 {
595 gchar *mes = g_markup_printf_escaped("%i %s %s.",
596 not_deleted,
597 (not_deleted > 1)?_("songs where"):_("song was"),
598 _("not removed because it still exists in the play queue"));
599 playlist3_message_show(pl3_messages, mes, ERROR_WARNING);
600 g_free(mes);
601 }
602 }
603
mserver_browser_add_files_to_playlist(GtkButton * button,GtkTreeView * tree)604 static void mserver_browser_add_files_to_playlist(GtkButton *button, GtkTreeView *tree)
605 {
606 GtkTreeModel *model = gtk_tree_view_get_model(tree);
607 GtkTreeSelection *sel = gtk_tree_view_get_selection(tree);
608 GList *iter, *list = gtk_tree_selection_get_selected_rows(sel, &model);
609 int found = 0;
610 for(iter = list; iter;iter = g_list_next(iter))
611 {
612 GtkTreeIter titer;
613 if(gtk_tree_model_get_iter(model, &titer, iter->data))
614 {
615 char *uid = NULL;
616 gchar *url = NULL;
617 gtk_tree_model_get(GTK_TREE_MODEL(model), &titer, MPDDATA_MODEL_COL_SONG_NAME, &uid, -1);
618 if(uid)
619 {
620 url = mserver_get_full_serve_path(uid);
621 mpd_playlist_queue_add(connection, url);
622 g_free(url);
623 g_free(uid);
624 found = 1;
625 }
626 }
627 }
628 if(found) mpd_playlist_queue_commit(connection);
629 g_list_foreach (list, (GFunc)gtk_tree_path_free, NULL);
630 g_list_free(list);
631 }
632
mserver_browser_replace_files_to_playlist(GtkButton * button,GtkTreeView * tree)633 static void mserver_browser_replace_files_to_playlist(GtkButton *button, GtkTreeView *tree)
634 {
635 mpd_playlist_clear(connection);
636 mserver_browser_add_files_to_playlist(button, tree);
637 mpd_player_play(connection);
638 }
639 /* Drag and drop Target table */
640 static GtkTargetEntry target_table[] =
641 {
642 { (gchar*)"x-url/http", 0, 0 },
643 { (gchar*)"_NETSCAPE_URL", 0, 1},
644 { (gchar*)"text/uri-list", 0, 2},
645 { (gchar*)"audio/*",0,3},
646 { (gchar*)"audio/x-scpls", 0,4}
647 };
648
649
add_url(MpdData * data,char * uri)650 MpdData *add_url(MpdData *data, char *uri)
651 {
652 gchar *filename = g_filename_from_uri(uri, NULL, NULL);
653 if(filename)
654 {
655 if(g_file_test(filename, G_FILE_TEST_IS_REGULAR))
656 {
657 if(g_regex_match_simple(".*\\.(flac|mp3|ogg|wv|wav)$", filename, G_REGEX_CASELESS, 0))
658 {
659 guint id = g_random_int();
660 gchar *name = g_strdup_printf("%u", id);
661 data = _add_file(data,name,filename);
662 cfg_set_single_value_as_string(cfg_urls,URLS_CLASS, name, filename);
663 g_free(name);
664 }
665 }else if (g_file_test(filename, G_FILE_TEST_IS_DIR)) {
666 GDir *dir = g_dir_open(filename, 0, NULL);
667 if(dir) {
668 const char *filename;
669 while((filename = g_dir_read_name(dir))){
670 gchar *ffile = g_build_filename(uri, filename,NULL);
671 data = add_url(data, ffile);
672 g_free(ffile);
673 }
674 g_dir_close(dir);
675 }
676 }
677 g_free(filename);
678 }
679 return data;
680 }
681
mserver_drag_data_recieved(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * data,guint info,guint time_recieved)682 static void mserver_drag_data_recieved (GtkWidget *widget,
683 GdkDragContext *context,
684 gint x,
685 gint y,
686 GtkSelectionData *data,
687 guint info,
688 guint time_recieved)
689 {
690 const gchar *url_data = (gchar *)data->data;
691 gchar **url = g_uri_list_extract_uris(url_data);
692 if(url) {
693 int i;
694 MpdData *data = gmpc_mpddata_model_steal_mpd_data(GMPC_MPDDATA_MODEL(ls));
695 if(data)
696 while(!mpd_data_is_last(data)) data = mpd_data_get_next(data);
697 for(i=0;url[i];i++)
698 {
699 data = add_url(data,url[i]);
700 }
701 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(ls), mpd_data_get_first(data));
702 g_strfreev(url);
703 }
704 }
mserver_right_mouse_menu(GtkWidget * tree,GdkEventButton * event)705 static gboolean mserver_right_mouse_menu(GtkWidget *tree, GdkEventButton *event)
706 {
707 if(event->button == 3)
708 {
709 GtkWidget *menu = NULL,*item;
710 menu = gtk_menu_new();
711
712 /* add the delete widget */
713 item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ADD,NULL);
714 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
715 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(mserver_browser_add_files_to_playlist), tree);
716
717 /* replace the replace widget */
718 item = gtk_image_menu_item_new_with_label(_("Replace"));
719 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
720 gtk_image_new_from_stock(GTK_STOCK_REDO, GTK_ICON_SIZE_MENU));
721 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
722 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(mserver_browser_replace_files_to_playlist), tree);
723
724 gmpc_mpddata_treeview_right_mouse_intergration(GMPC_MPDDATA_TREEVIEW(tree), GTK_MENU(menu));
725 gtk_widget_show_all(menu);
726 gtk_menu_popup(GTK_MENU(menu), NULL, NULL,NULL, NULL, 0, event->time);
727 return TRUE;
728 }
729 return FALSE;
730 }
731
732 GtkWidget* error_label = NULL;
733
mserver_init()734 void mserver_init() {
735 GtkWidget *sw, *tree,*button,*bbox;
736 gchar *fs, *path, *url;
737
738 bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
739 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
740 /**
741 * Add icon
742 */
743 path = gmpc_plugin_get_data_path(&plugin);
744 url = g_build_path(G_DIR_SEPARATOR_S,path, "gmpc-mserver", NULL);
745 gtk_icon_theme_append_search_path(gtk_icon_theme_get_default (),url);
746 g_free(url);
747 g_free(path);
748
749 /* config file */
750 fs = gmpc_get_user_path("server_urls.txt");
751 cfg_urls = cfg_open(fs);
752 g_free(fs);
753
754 d = MHD_start_daemon(
755 MHD_USE_THREAD_PER_CONNECTION,
756 9876,
757 &apc_all,
758 NULL,
759 &ahc_echo,
760 NULL,
761 MHD_OPTION_END);
762
763 ls = (GtkTreeModel *) gmpc_mpddata_model_new();
764
765 mserver_vbox = gtk_vbox_new(FALSE,6);
766 /* Scrolled window */
767 sw = gtk_scrolled_window_new(NULL, NULL);
768 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC,GTK_POLICY_AUTOMATIC);
769 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
770 /* TreeView */
771 tree = gmpc_mpddata_treeview_new("mserver-plugin", TRUE, GTK_TREE_MODEL(ls));
772 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(tree)), GTK_SELECTION_MULTIPLE);
773 g_signal_connect(G_OBJECT(tree), "button-press-event", G_CALLBACK(mserver_right_mouse_menu), NULL);
774
775
776
777 g_signal_connect(G_OBJECT(tree), "row-activated", G_CALLBACK(mserver_browser_activated), NULL);
778 gtk_container_add(GTK_CONTAINER(sw),tree);
779 gtk_box_pack_start(GTK_BOX(mserver_vbox), sw, TRUE, TRUE, 0);
780
781 bbox = gtk_hbutton_box_new();
782
783 button = gtk_button_new_with_label(_("Add files"));
784 gtk_button_set_image(GTK_BUTTON(button), gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON));
785 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(mserver_browser_add_file), NULL);
786 gtk_box_pack_start(GTK_BOX(bbox), button,FALSE,FALSE,0);
787
788 button = gtk_button_new_with_label(_("Add to playlist"));
789 gtk_button_set_image(GTK_BUTTON(button), gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON));
790 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(mserver_browser_add_files_to_playlist), tree);
791 gtk_box_pack_start(GTK_BOX(bbox), button,FALSE,FALSE,0);
792
793 button = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
794 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(mserver_browser_remove_files),tree);
795 gtk_box_pack_start(GTK_BOX(bbox), button,FALSE,FALSE,0);
796 gtk_box_pack_start(GTK_BOX(mserver_vbox), bbox,FALSE,FALSE,0);
797
798
799 /**
800 * Set as drag destination
801 */
802 gtk_drag_dest_set(mserver_vbox,
803 GTK_DEST_DEFAULT_ALL,
804 target_table, 5,
805 GDK_ACTION_COPY|GDK_ACTION_LINK|GDK_ACTION_DEFAULT|GDK_ACTION_MOVE);
806 g_signal_connect (G_OBJECT (mserver_vbox),"drag_data_received",
807 GTK_SIGNAL_FUNC (mserver_drag_data_recieved),
808 NULL);
809
810 g_object_ref(mserver_vbox);
811 gtk_widget_show_all(mserver_vbox);
812
813 error_label = gtk_label_new(_("The connected MPD does not support reading music from the 'Serve Music' plugin"));
814 fs = g_markup_printf_escaped("<span size='xx-large' weight='bold'>%s</span>",
815 _("The connected MPD does not support reading music from the 'Serve Music' plugin"));
816 gtk_label_set_markup(GTK_LABEL(error_label), fs);
817 g_free(fs);
818
819 gtk_box_pack_start(GTK_BOX(mserver_vbox), error_label,FALSE,FALSE,0);
820 }
821
822
mserver_browser_selected(GtkWidget * container)823 void mserver_browser_selected(GtkWidget *container)
824 {
825 gtk_container_add(GTK_CONTAINER(container), mserver_vbox);
826 }
827
mserver_browser_unselected(GtkWidget * container)828 void mserver_browser_unselected(GtkWidget *container)
829 {
830 gtk_container_remove(GTK_CONTAINER(container), mserver_vbox);
831 }
832
mserver_connection_changed(MpdObj * mi,int connect,gpointer data)833 void mserver_connection_changed(MpdObj *mi, int connect, gpointer data)
834 {
835 /* reset */
836 support_http = -1;
837 support_file = -1;
838 if(connect)
839 {
840 if(support_http < 0 || support_file < 0){
841 gchar **url_handlers = mpd_server_get_url_handlers(connection);
842 /* set to not supported */
843 support_http = support_file = 0;
844 if(url_handlers){
845 int i;
846 for(i=0;url_handlers[i];i++){
847 if(strcasecmp(url_handlers[i], "http://") == 0) support_http = 1;
848 else if(strcasecmp(url_handlers[i], "file://") == 0) support_file = 1;
849 }
850 g_strfreev(url_handlers);
851 }
852 }
853 }
854 if(mserver_vbox && connect) {
855 if(support_http == 0 && support_file == 0){
856 gtk_widget_set_sensitive(mserver_vbox,FALSE);
857 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(ls),NULL);
858 gtk_widget_show(error_label);
859 }else {
860
861 gtk_widget_hide(error_label);
862 gtk_widget_set_sensitive(mserver_vbox,TRUE);
863 /**
864 * Reload
865 */
866 conf_mult_obj *cmo,*cmo_iter;
867 cmo= cfg_get_key_list(cfg_urls, URLS_CLASS);
868 if(cmo) {
869 MpdData *data = NULL;
870 for( cmo_iter = cmo ;cmo_iter;cmo_iter = cmo_iter->next ) {
871 data = _add_file(data,cmo_iter->key,cmo_iter->value);
872 }
873 cfg_free_multiple(cmo);
874 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(ls), mpd_data_get_first(data));
875 }
876 }
877 }
878 }
879
mserver_get_translation_domain(void)880 static const gchar *mserver_get_translation_domain(void)
881 {
882 return GETTEXT_PACKAGE;
883 }
884
885 gmpcPlBrowserPlugin mserver_browser_plugin ={
886 .add = mserver_browser_add,
887 .selected = mserver_browser_selected,
888 .unselected = mserver_browser_unselected,
889
890 };
891
892 int plugin_api_version = PLUGIN_API_VERSION;
893 gmpcPlugin plugin = {
894 .name = N_("File Serve plugin"),
895 .version = {PLUGIN_MAJOR_VERSION,PLUGIN_MINOR_VERSION,PLUGIN_MICRO_VERSION},
896 .plugin_type = GMPC_PLUGIN_PL_BROWSER,
897 .init = mserver_init,
898 .destroy = mserver_destroy,
899 .save_yourself = mserver_save_myself,
900 .get_enabled = mserver_get_enabled,
901 .set_enabled = mserver_set_enabled,
902 .browser = &mserver_browser_plugin,
903 .mpd_connection_changed = mserver_connection_changed,
904 .get_translation_domain = mserver_get_translation_domain
905 };
906