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