Merge branch 'master' of git://github.com/jomat/shell-fm
[shell-fm.git] / source / sckif.c
blobe4aeb15186e5bf6a40f0246e9ca60482a57d0c25
1 /*
2 Copyright (C) 2006-2010 by Jonas Kramer
3 Published under the terms of the GNU General Public License (GPL).
4 */
6 #define _GNU_SOURCE
9 #include <stdio.h>
10 #include <unistd.h>
11 #include <string.h>
12 #include <errno.h>
13 #include <sys/types.h>
14 #include <sys/socket.h>
15 #include <sys/stat.h>
16 #include <stdlib.h>
17 #include <signal.h>
18 #include <time.h>
20 #include <netinet/in.h>
21 #include <arpa/inet.h>
22 #include <netdb.h>
24 #include <sys/select.h>
25 #include <sys/un.h>
27 #include "sckif.h"
28 #include "http.h"
29 #include "service.h"
30 #include "interface.h"
31 #include "hash.h"
32 #include "submit.h"
33 #include "getln.h"
34 #include "tag.h"
35 #include "select.h"
36 #include "util.h"
38 #include "globals.h"
40 #include "split.h"
42 struct hash track;
44 static int stcpsck = -1, sunixsck = -1;
46 #define BUFSIZE 1024
48 void parse_volume(const char *);
51 int tcpsock(const char * ip, unsigned short port) {
52 static const int one = 1;
53 struct sockaddr_in host;
54 struct hostent * hostent;
56 if(!ip || !port)
57 return 0;
59 if(-1 == (stcpsck = socket(AF_INET, SOCK_STREAM, PF_UNSPEC))) {
60 fputs("Failed to create socket.\n", stderr);
61 return 0;
64 if(!(hostent = gethostbyname(ip))) {
65 fprintf(stderr, "Failed to lookup host. %s.\n", hstrerror(h_errno));
66 return 0;
69 host.sin_family = PF_INET;
70 host.sin_port = htons(port);
71 host.sin_addr.s_addr = * (unsigned *) hostent->h_addr;
73 if(-1 == setsockopt(stcpsck, SOL_SOCKET, SO_REUSEADDR, & one, sizeof one))
74 fprintf(stderr, "Couldn't make socket re-usable. %s\n", strerror(errno));
76 if(bind(stcpsck, (struct sockaddr *) & host, sizeof(struct sockaddr_in))) {
77 fprintf(stderr, "Failed to bind socket. %s.\n", strerror(errno));
78 return 0;
81 listen(stcpsck, 2);
83 register_listen_socket(stcpsck);
85 return !0;
89 int unixsock(const char * path) {
90 struct sockaddr_un host;
92 if(path == NULL)
93 return 0;
96 if(!access(path, F_OK)) {
97 fprintf(stderr, "%s already existing. UNIX socket not created.\n", path);
98 return 0;
102 if(-1 == (sunixsck = socket(AF_UNIX, SOCK_STREAM, PF_UNSPEC))) {
103 fputs("Failed to create socket.\n", stderr);
104 return 0;
108 memset(& host, 0, sizeof(struct sockaddr_un));
109 strncpy(host.sun_path, path, sizeof(host.sun_path) - 1);
110 host.sun_family = AF_UNIX;
113 if(bind(sunixsck, (struct sockaddr *) & host, sizeof(struct sockaddr_un))) {
114 fprintf(stderr, "Failed to bind socket. %s.\n", strerror(errno));
115 return 0;
118 listen(sunixsck, 2);
120 register_listen_socket(sunixsck);
122 return !0;
126 void rmsckif(void) {
127 if(stcpsck > 0) {
128 close(stcpsck);
129 stcpsck = -1;
132 if(sunixsck > 0) {
133 close(sunixsck);
134 sunixsck = -1;
139 void accept_client(int listen_socket) {
140 struct sockaddr client;
141 socklen_t client_size = sizeof(struct sockaddr);
143 int client_socket = accept(listen_socket, & client, & client_size);
145 if(-1 != client_socket)
146 register_client_socket(client_socket);
150 void handle_client(int client_socket) {
151 static FILE * fd = NULL;
152 int disconnect = 0;
154 if (!fd)
155 fd = fdopen(client_socket, "rw");
157 signal(SIGPIPE, SIG_IGN);
159 if(fd != NULL && !feof(fd)) {
160 debug("client socket is ok and readable: %i\n", client_socket);
161 char line[BUFSIZE];
163 if(fgets(line, sizeof(line), fd) == NULL) {
164 disconnect = 1;
166 else {
167 if(!ferror(fd)) {
168 unsigned chunks, i;
169 char ** lines = split(line, "\n", & chunks);
171 for(i = 0; i < chunks && !disconnect; ++i) {
172 char reply[BUFSIZE] = { 0, };
173 debug("client message: <%s>\n", lines[i]);
175 disconnect = execcmd(lines[i], reply);
177 if(strlen(reply)) {
178 strncat(reply, "\n", BUFSIZE - strlen(reply));
179 write(client_socket, reply, strlen(reply));
184 else {
185 debug("fd error: %i\n", ferror(fd));
186 disconnect = 1;
192 if(disconnect) {
193 debug("removing client\n");
194 shutdown(SHUT_RDWR, client_socket);
195 close(client_socket);
196 fclose(fd);
198 remove_handle(client_socket);
203 int execcmd(const char * cmd, char * reply) {
204 char arg[1024], * ptr;
205 unsigned ncmd;
206 const char * known [] = {
207 "play",
208 "love",
209 "ban",
210 "skip",
211 "quit",
212 "info",
213 "pause",
214 "discovery",
215 "tag-artist",
216 "tag-album",
217 "tag-track",
218 "artist-tags",
219 "album-tags",
220 "track-tags",
221 "stop",
222 "volume-up",
223 "volume-down",
224 "volume",
225 "rtp",
226 "status",
227 "detach"
230 memset(arg, 0, sizeof(arg));
231 memset(reply, 0, BUFSIZE);
233 for(ncmd = 0; ncmd < (sizeof(known) / sizeof(char *)); ++ncmd) {
234 if(!strncmp(known[ncmd], cmd, strlen(known[ncmd])))
235 break;
238 switch(ncmd) {
239 case (sizeof(known) / sizeof(char *)):
240 strncpy(reply, "ERROR", BUFSIZE);
241 break;
243 /* "play lastfm://station" */
244 case 0:
245 if(sscanf(cmd, "play %128[a-zA-Z0-9:/_ %,*.-]", arg) == 1) {
246 char * url;
247 decode(arg, & url);
248 station(url);
249 free(url);
251 break;
253 /* Love currently played track. */
254 case 1:
255 rate("L");
256 break;
258 /* Ban currently played track. */
259 case 2:
260 rate("B");
261 break;
263 /* Skip track. */
264 case 3:
265 rate("S");
266 break;
268 /* Kill Shell.FM. */
269 case 4:
270 quit();
272 /* "info FORMAT" - returns the format string with the meta data filled in. */
273 case 5:
274 if(* (cmd + 5))
275 strncpy(reply, meta(cmd + 5, 0, & track), BUFSIZE);
276 else if(haskey(& rc, "np-file-format"))
277 strncpy(
278 reply,
279 meta(value(& rc, "np-file-format"), 0, & track),
280 BUFSIZE
283 break;
285 /* Pause playback. */
286 case 6:
287 if(playfork) {
288 if(pausetime) {
289 kill(playfork, SIGCONT);
291 else {
292 time(& pausetime);
293 kill(playfork, SIGSTOP);
296 break;
298 /* Toggle discovery mode. Returns "DISCOVERY <ON|OFF>" */
299 case 7:
300 toggle(DISCOVERY);
301 snprintf(
302 reply,
303 BUFSIZE,
304 "DISCOVERY %s",
305 enabled(DISCOVERY) ? "ON" : "OFF"
307 break;
309 /* "tag-artist tag1,tag2,..." - tag the artist of the current track. */
310 case 8:
311 if(sscanf(cmd, "tag-artist %128s", arg) == 1)
312 sendtag('a', arg, track);
313 break;
315 /* "tag-album tag1,tag2,..." - tag the album of the current track. */
316 case 9:
317 if(sscanf(cmd, "tag-album %128s", arg) == 1)
318 sendtag('l', arg, track);
319 break;
321 /* "tag-track tag1,tag2,..." - tag the current track. */
322 case 10:
323 if(sscanf(cmd, "tag-track %128s", arg) == 1)
324 sendtag('t', arg, track);
325 break;
327 /* Return comma-separated list of the current artists tags. */
328 case 11:
329 if((ptr = oldtags('a', track)) != NULL) {
330 strncpy(reply, ptr, BUFSIZE);
331 free(ptr);
332 ptr = NULL;
334 break;
336 /* Return comma-separated list of the current albums tags. */
337 case 12:
338 if((ptr = oldtags('l', track)) != NULL) {
339 strncpy(reply, ptr, BUFSIZE);
340 free(ptr);
341 ptr = NULL;
343 break;
345 /* Return comma-separated list of the current tracks tags. */
346 case 13:
347 if((ptr = oldtags('t', track)) != NULL) {
348 strncpy(reply, ptr, BUFSIZE);
349 free(ptr);
350 ptr = NULL;
352 break;
355 /* Stop playback. */
356 case 14:
357 if(playfork) {
358 enable(STOPPED);
359 kill(playfork, SIGUSR1);
361 break;
363 /* Increase absolute volume (0-64) by 1. */
364 case 15:
365 volume_up();
366 break;
368 /* Decrease absolute volume (0-64) by 1. */
369 case 16:
370 volume_down();
371 break;
374 Set volume.
375 "volume 32" - set absolute volume (0-64) to 32 (50%).
376 "volume %50" - same, but using percentual volume.
377 "volume +1" - same as "volume_up".
378 "volume -1" - same as "volume_down".
380 Returns absolute volume ("VOLUME 32").
382 case 17:
383 parse_volume(cmd);
384 snprintf(reply, BUFSIZE, "VOLUME %d", volume);
385 break;
387 /* Toggle RTP (report to profile, "scrobbling"). Returns "RTP <ON|OFF>". */
388 case 18:
389 /* RTP on/off */
390 toggle(RTP);
391 snprintf(reply, BUFSIZE, "RTP %s", enabled(RTP) ? "ON" : "OFF");
392 break;
394 /* Get current status. Returns on of "PAUSE", "PLAYING" and "STOPPED". */
395 case 19:
396 strncpy(reply, playfork ? (pausetime ? "PAUSED" : "PLAYING") : "STOPPED", BUFSIZE);
397 break;
399 /* Detach from network interface. */
400 case 20:
401 return 1;
404 return 0;
408 void parse_volume(const char * cmd) {
409 char sign = 0;
410 int new_volume;
412 if(sscanf(cmd, "volume %1[+-]%d", & sign, & new_volume) == 2) {
413 if(sign == '-')
414 set_volume(volume - new_volume);
415 else if(sign == '+')
416 set_volume(volume + new_volume);
419 /* Allow percentual volume (1-100%). */
420 else if(sscanf(cmd, "volume %%%d", & new_volume)) {
421 set_volume(new_volume * 0.64);
424 /* Allow absolute volume (0-64). */
425 else if(sscanf(cmd, "volume %d", & new_volume) == 1) {
426 set_volume(new_volume);