1 /* By Laurent Coustet (c) 2005 */
2
3 #include <stdlib.h>
4 #include <stdio.h>
5 #include <errno.h>
6
7 #ifndef WIN32
8 #include <arpa/inet.h>
9 #endif
10
11 #include <string.h>
12 #include <unistd.h>
13 #include <fcntl.h>
14 #include <dirent.h>
15 #include <ctype.h>
16 #include <sys/stat.h>
17
18 #ifndef WIN32
19 #include <regex.h>
20 #include <locale.h>
21 #endif
22
23 #include "cpige.h"
24 #include "icy.h"
25 #include "id3.h"
26 #include "pool.h"
27 #include "tool.h"
28 #include "mytime.h"
29 #include "debug.h"
30
31 #ifndef NOCONFIG
32 #include "configlib.h"
33 char *configFile = NULL;
34 #endif
35
36 /* Windows needs file to be opened in byte mode */
37 #if WIN32
38 #define WAITALL 0
39 #define WRITE "wb+"
40 #define sleep(msec) _sleep(msec)
41 #else
42 #define WAITALL 0x100
43 #define WRITE "w+"
44 #endif
45
46 #define TENTATIVES -1
47 #define CONNECTION_TIMEOUT 5 /* The connection must be established within 5 seconds */
48 #define RECONNECT_TIME 5 /* We are waiting 5 seconds before trying to reconnect. */
49
50 /* Struct storing command line arguments */
51 commandLine *cmdLine;
52
53 /* Uptime */
54 time_t start_time;
55 time_t current_time;
56 /* for select() */
57 fd_set rfds;
58 /* socket timeout */
59 struct timeval timeout;
60
61 /* Statistics */
62 stats *cPigeStats;
63
64 /* Servers pool */
65 urlPool_t *serversPool = NULL;
66 int poolPosition = 0;
67
68 int server_socket = -1;
69
70 #ifdef SOLARIS
71 int daemon(int nochdir, int noclose);
72 #endif
73
74 /* Main Program */
main(int argc,char ** argv)75 int main (int argc, char **argv)
76 {
77 char *buffer = NULL;
78 char *titre = NULL;
79 char *oldtitre = NULL;
80 char *nexttitre = NULL;
81 char *filename = NULL;
82 char *extension = "mp3";
83 char *meta;
84 char *statusline;
85 char *dir;
86
87 long long unsigned int uptime = 0;
88 long long unsigned int metacounter = 0;
89
90 /* When operates in quiet mode, display stats in logfile
91 * evry countdown seconds
92 */
93 time_t countdown = 3600; /* Evry 1 hour */
94 time_t temptime;
95
96 /* Integers ... */
97 int size;
98 int retval;
99 int i;
100 int tempsize = 0;
101 int nextsize = 0;
102 int len1 = 0;
103 int len2 = 0;
104 int maxlen = 0;
105 int songs = 0;
106 int buflen = 0;
107 int maxlinelength = 80;
108
109 /* Directory & File manipulations */
110 DIR *pige_dir;
111 FILE *output_file = NULL;
112 FILE *logfile = NULL;
113
114 /* Custom typedef */
115 currentSong *curSong;
116 icyHeaders *icy_headers = NULL;
117 lastCut last_cut;
118
119 /*
120 * last_cut.hour = getHour();
121 * last_cut.min = getMinute();
122 */
123
124 last_cut.hour = -1;
125 last_cut.min = -1;
126 last_cut.sec = -1;
127
128 /* Global dynamic configuration */
129 cmdLine = parseCommandLine(argc, argv);
130 testCommandLine();
131
132 /* Logfile */
133 if ((logfile = fopen(cmdLine->logFile, "a+")) == NULL)
134 {
135 _ERROR("Unable to openlogfile: %s Setting log to stdout\n", cmdLine->logFile);
136 logfile = stdout;
137 } else {
138 VERBOSE("Successfully opened %s\n", cmdLine->logFile);
139 }
140
141 /* We setup debug to logfile instead of stderr */
142 SetDebugFile(logfile);
143
144 if (cmdLine->background)
145 {
146 #ifndef WIN32
147 int pid;
148 int fd;
149 FILE *pidfile;
150
151 if (daemon(1, 1) == -1)
152 {
153 _ERROR("Error daemonizing. %s\n", strerror(errno));
154 _exit(-1);
155 }
156
157 pid = (int)getpid();
158
159 if (cmdLine->pidFile != NULL)
160 {
161 pidfile = fopen(cmdLine->pidFile, "w");
162 if (pidfile == NULL)
163 _ERROR("Unable to open pidfile %s\n", cmdLine->pidFile);
164 else
165 {
166 fprintf(pidfile, "%d", pid);
167 fclose(pidfile);
168 }
169 }
170
171 fprintf(stdout, "cPige launched in background. pid: %d\n", pid);
172 fd = open("/dev/null", O_RDWR);
173
174 if (fd == -1)
175 {
176 _ERROR("Error opening /dev/null: %s\n", strerror(errno));
177 _exit(0);
178 }
179
180 for (i = 0; i < 3; i++)
181 dup2(fd, i);
182
183 close(fd);
184 #else
185 printf("Fork not available under WIN32.\n");
186 #endif
187 }
188
189 cPigeStats = (stats *)memory_allocation(sizeof(stats));
190 cPigeStats->reconnections = 0;
191 cPigeStats->songs = 0;
192
193 /* Create output dir if does not exists! */
194 if (( pige_dir = opendir(cmdLine->dir)) == NULL)
195 {
196 _ERROR("Unable to open %s for writing\n", cmdLine->dir);
197 #ifdef WIN32
198 if (mkdir(cmdLine->dir) != 0) {
199 #else
200 if (mkdir(cmdLine->dir, 0755) != 0) {
201 #endif
202 _ERROR("Failed trying to create %s. Verify rights.\n", cmdLine->dir);
203 _exit(-1);
204 } else {
205 VERBOSE("Created %s\n", cmdLine->dir);
206 }
207 } else {
208 VERBOSE("Sucessfully opened %s\n", cmdLine->dir);
209 closedir(pige_dir);
210 }
211
212 /* If we are keeping One week of stream, check if directories
213 * exists, and check disk space
214 */
215
216 checkWeekBackup();
217
218 /* Start time, for uptime calculation */
219 start_time = time((time_t *)NULL);
220 temptime = time((time_t *)NULL) + countdown;
221
222 /* We try connecting and receiving good headers infinitely */
223 do
224 {
225 server_socket = reconnect(RECONNECT_TIME, TENTATIVES, 0, NULL);
226 if (icy_headers != NULL)
227 {
228 free_icy_headers(icy_headers);
229 free(icy_headers);
230 icy_headers = NULL;
231 }
232
233 icy_headers = readicyheaders(getHeaders(server_socket));
234 } while (icy_headers == NULL);
235
236 if (icy_headers->metaint != buflen)
237 {
238 buflen = icy_headers->metaint;
239 if (buffer != NULL)
240 free(buffer);
241 buffer = (char *)memory_allocation(buflen);
242 }
243
244 if (strncmp(icy_headers->content_type, "audio/mpeg", 10) == 0)
245 extension = "mp3";
246 else if (strncmp(icy_headers->content_type, "audio/aacp", 10) == 0)
247 extension = "aac";
248 else if (strncmp(icy_headers->content_type, "application/ogg", 15) == 0)
249 extension = "ogg";
250
251 VERBOSE("Using extension: %s\n", extension);
252
253 /* Only usefull for debug, not for production ! */
254 /* print_icyheaders(icy_headers); */
255
256 #ifndef WIN32
257 if(cmdLine->useNumbers == 1)
258 songs = getSongs(cmdLine->dir);
259 #endif
260
261 nextsize = 512;
262 oldtitre = strdup("please.delete");
263
264 while (1)
265 {
266 /* For select() it's a global struct. */
267 timeout.tv_sec = SOCKET_TIMEOUT;
268 timeout.tv_usec = 0;
269 retval = select(server_socket+1, &rfds, NULL, NULL, &timeout);
270 if (retval <= 0)
271 {
272 _ERROR("Connection Error.\n");
273 server_close(server_socket);
274 server_socket = reconnect(RECONNECT_TIME, TENTATIVES, 1, icy_headers);
275 if (icy_headers->metaint != buflen)
276 {
277 buflen = icy_headers->metaint;
278 free(buffer);
279 buffer = (char *)memory_allocation(buflen);
280 }
281
282 tempsize = 0;
283 nextsize = 512;
284 continue;
285 }
286
287 size = recv(server_socket, buffer, nextsize, 0);
288 if ((size == -1) || ((size == 0) && nextsize != 0))
289 {
290 _ERROR("Connection error in recv main() size: %d nextsize: %d\n", size, nextsize);
291 server_socket = reconnect(RECONNECT_TIME, TENTATIVES, 1, icy_headers);
292 if (icy_headers->metaint != buflen)
293 {
294 buflen = icy_headers->metaint;
295 free(buffer);
296 buffer = (char *)memory_allocation(buflen);
297 }
298
299 tempsize = 0;
300 nextsize = 512;
301 continue;
302 }
303
304 if (output_file)
305 {
306 if (fwrite(buffer, sizeof(char), size, output_file) != size*sizeof(char))
307 {
308 _ERROR("Error writing output. Disk full ?\n");
309 break;
310 }
311 /* Many thanks to the #hurdfr team! */
312 fflush(output_file);
313 }
314
315 if ( tempsize == icy_headers->metaint )
316 {
317 if (cmdLine->pige)
318 {
319 /* Pige Mode */
320
321 if (mustCut(&last_cut) == 1)
322 {
323 if ((cmdLine->usePigeMeta == 1) && (output_file != NULL) && (strncmp(icy_headers->content_type, "audio/mpeg", 10) == 0))
324 {
325 char buffer1[30];
326 char buffer2[30];
327 char *buffer3;
328
329 switch (cmdLine->intervalType)
330 {
331 case IVAL_HOUR:
332 snprintf(buffer1, 30, "pige %.2dh -> %.2dh", last_cut.hour, getCloserInterval(getHour(), cmdLine->interval));
333 break;
334 case IVAL_MIN:
335 snprintf(buffer1, 30, "pige %.2dh%.2d -> %.2dh%.2d", last_cut.hour, last_cut.min, getCloserInterval(getHour(), 1), getCloserInterval(getMinute(), cmdLine->interval));
336 break;
337 }
338 strncpy(buffer2, icy_headers->name, 30);
339
340 buffer3 = GetId3v1(buffer2, buffer1, icy_headers->name);
341 fwrite(buffer3, sizeof(char), 128, output_file);
342 (void)free(buffer3);
343 }
344
345 if (output_file)
346 fclose(output_file);
347
348 if (cmdLine->weekBackup)
349 {
350 char *dirname = getDayName(time((time_t *)NULL));
351 int dirlen = strlen(cmdLine->dir);
352 int totlen = dirlen+strlen(dirname)+1+1+1;
353
354 dir = (char *)memory_allocation(totlen);
355 if (cmdLine->dir[dirlen-1] == '/')
356 snprintf(dir, totlen, "%s%s/", cmdLine->dir, dirname);
357 else
358 snprintf(dir, totlen, "%s/%s/", cmdLine->dir, dirname);
359
360 (void)free(dirname);
361 } else {
362 dir = strdup(cmdLine->dir);
363 }
364
365 switch (cmdLine->intervalType)
366 {
367 case IVAL_HOUR:
368 last_cut.hour = getCloserInterval(getHour(), cmdLine->interval);
369 last_cut.min = getCloserInterval(getMinute(), 60);
370
371 len1 = strlen(dir) + 1 + strlen(extension) + 2 + 1;
372 filename = (char *)memory_allocation(len1);
373 snprintf(filename, len1, "%s%.2d.%s", dir, last_cut.hour, extension);
374 break;
375
376 case IVAL_MIN:
377 last_cut.min = getCloserInterval(getMinute(), cmdLine->interval);
378 last_cut.hour = getCloserInterval(getHour(), 1);
379
380 len1 = strlen(dir) + 1 + strlen(extension) + 2 + 2 + 1 + 1;
381 filename = memory_allocation(len1);
382 snprintf(filename, len1, "%s%.2d-%.2d.%s", dir, getHour(), last_cut.min, extension);
383 break;
384
385 default:
386 fprintf(stderr, "Internal Error: unknown interval type.\n");
387 break;
388 }
389 last_cut.sec = time(NULL);
390
391 (void)free(dir);
392
393 if ((output_file = fopen(filename, "r")) != NULL)
394 {
395 VERBOSE("Erasing %s\n", filename);
396 unlink(filename);
397 fclose(output_file);
398 }
399 VERBOSE("Opening: %s\n", filename);
400 output_file = fopen(filename, WRITE);
401 (void)free(filename);
402
403 }
404 } else if (cmdLine->live) {
405 printf("Not yet implemented.\n");
406 _exit(1);
407 }
408
409 metacounter++;
410 current_time = time((time_t *)NULL);
411 uptime = (long long unsigned int) difftime(current_time, start_time);
412
413 /* Classical stdout status line */
414 if (!cmdLine->quiet)
415 {
416 fflush(stdout);
417 fprintf(stdout, "\r");
418
419 /* 100% pur porc */
420 for(i = 0; i < maxlinelength; i++)
421 fprintf(stdout, " ");
422
423 if (strncmp(icy_headers->content_type, "application/ogg", 15) == 0)
424 statusline = statusLine(uptime, metacounter, icy_headers->metaint, NULL, NULL);
425 else
426 statusline = statusLine(uptime, metacounter, icy_headers->metaint, oldtitre, nexttitre);
427
428 fprintf(stdout, "\r%s", statusline);
429
430 if ( strlen(statusline) > maxlinelength)
431 maxlinelength = strlen(statusline);
432
433 if (statusline != NULL)
434 (void)free(statusline);
435 }
436
437 if ((cmdLine->useGUI) && (cmdLine->quiet))
438 {
439 char *mybuf;
440
441 /* GUI Communication protocol */
442 /* Protocol:
443 * CPIGEGUI:title:nexttitle:timeleft\n
444 */
445 fprintf(stdout, "CPIGEGUI:");
446
447 if (oldtitre)
448 {
449 mybuf = (char *)memory_allocation(strlen(oldtitre)+1);
450 stripcolon(oldtitre, mybuf);
451 fprintf(stdout, "%s", mybuf);
452 free(mybuf);
453 }
454
455 fprintf(stdout, ":");
456
457 if (nexttitre)
458 {
459 mybuf = (char *)memory_allocation(strlen(nexttitre)+1);
460
461 if ((cmdLine->Next) && (strlen(nexttitre) > strlen(cmdLine->Next)))
462 stripcolon(nexttitre + strlen(cmdLine->Next), mybuf);
463 else
464 stripcolon(nexttitre, mybuf);
465
466 fprintf(stdout, "%s", mybuf);
467 free(mybuf);
468 } else
469 fprintf(stdout, " ");
470
471 fprintf(stdout, ":");
472
473 if (cmdLine->pige == 1)
474 {
475 int timeleft, percentage;
476 int stop_s, nowTmp;
477 int tmp1;
478
479 nowTmp = time((time_t *)NULL);
480
481 /* How many time left ? */
482 switch (cmdLine->intervalType)
483 {
484 case IVAL_HOUR:
485 /* Il me faut l'heure d'arriv�e th�orique, en seconde, du stream */
486 tmp1 = (getCloserInterval(getHour(), cmdLine->interval) + cmdLine->interval);
487 stop_s = ((tmp1 * 3600) + (nowTmp - ((getHour()*3600) - getMinute() * 60) - getSec()));
488 percentage = ((float)(1.0 - (((float)stop_s - (float)nowTmp) / ((float)cmdLine->interval*3600.0))) * 100.0);
489 break;
490 case IVAL_MIN:
491 tmp1 = (getCloserInterval(getMinute(), cmdLine->interval) + cmdLine->interval);
492 stop_s = ((tmp1 * 60) + (nowTmp - (getMinute() * 60) - getSec()));
493 percentage = ((float)(1.0 - (((float)stop_s - (float)nowTmp) / ((float)cmdLine->interval*60.0))) * 100.0);
494 //percentage = ((float)(1.0 - (((float)tmp1 - (float)getMinute()) / (float)getMinute())) * 100.0);
495 break;
496 }
497
498 timeleft = stop_s - nowTmp;
499
500 fprintf(stdout, "%d:%d", timeleft, percentage);
501 }
502
503 fprintf(stdout, "\n");
504 fflush(stdout);
505 }
506
507 /* Stats evry countdown seconds */
508 if ((countdown != 0) && (temptime <= current_time))
509 {
510 statusline = getStats(uptime, metacounter, icy_headers->metaint);
511 if (statusline != NULL)
512 {
513 if (fwrite(statusline, sizeof(char), strlen(statusline), logfile) != strlen(statusline))
514 {
515 _ERROR("Fwrite error.\n");
516 break;
517 }
518 /* Many thanks to the #hurdfr@freenode team! */
519 fflush(logfile);
520 (void)free(statusline);
521 } else {
522 VERBOSE("getStats returned NULL values...\n");
523 }
524 temptime = current_time + countdown;
525 }
526
527 if ((strncmp(icy_headers->content_type, "audio/mpeg", 10) == 0) ||
528 (strncmp(icy_headers->content_type, "audio/aacp", 10) == 0))
529 {
530 meta = readMeta(server_socket);
531
532 if ((meta == NULL) && (server_socket == 0))
533 {
534 server_socket = reconnect(RECONNECT_TIME, TENTATIVES, 0, icy_headers);
535 if (icy_headers->metaint != buflen)
536 {
537 buflen = icy_headers->metaint;
538 free(buffer);
539 buffer = (char *)memory_allocation(buflen);
540 }
541
542 tempsize = 0;
543 nextsize = 512;
544 continue;
545 }
546
547 if (titre != NULL)
548 free(titre);
549
550 titre = getTitle(meta);
551
552 if (meta != NULL)
553 (void)free(meta);
554 }
555
556 if (titre != NULL)
557 {
558 if (strlen(titre) > 0)
559 {
560 if (strstr(titre, cmdLine->Next) == NULL) /* If title isn't ASUIVRE */
561 {
562 if (oldtitre != NULL)
563 {
564 len1 = strlen(oldtitre);
565 len2 = strlen(titre);
566
567 /* We are determining the maximal comp len */
568 if (len1 >= len2); else maxlen = len1;
569
570 if (strncmp(titre, oldtitre, maxlen) != 0)
571 {
572 if (! cmdLine->pige)
573 {
574 if (cmdLine->skipSongs > 0)
575 {
576 cmdLine->skipSongs--;
577 VERBOSE("Skipping song `%s' (left %d songs to skip)\n", titre, cmdLine->skipSongs);
578 } else {
579 cPigeStats->songs++;
580 songs++;
581
582 if (output_file != NULL)
583 {
584 curSong = getCurrentSong(oldtitre);
585 if (curSong != NULL)
586 {
587 if (strncmp(icy_headers->content_type, "audio/mpeg", 10) == 0)
588 {
589 char *buffer1;
590 buffer1 = GetId3v1(curSong->title, curSong->artist, icy_headers->name);
591 fwrite(buffer1, sizeof(char), 128, output_file);
592 (void)free(buffer1);
593 }
594 if (curSong->artist != NULL)
595 free(curSong->artist);
596 if (curSong->title != NULL)
597 free(curSong->title);
598 free(curSong);
599 }
600
601 fclose(output_file);
602 }
603
604 if (cmdLine->useNumbers == 0)
605 {
606 len1 = strlen(titre) + strlen(cmdLine->dir)+ 1 + strlen(extension) + 1 + 1;
607 filename = (char *)memory_allocation(len1);
608 snprintf(filename, len1, "%s%s.%s", cmdLine->dir, titre, extension);
609 } else {
610 len1 = 5+strlen(titre) + strlen(cmdLine->dir)+ 1 + strlen(extension) + 1 + 1;
611 filename = (char *)memory_allocation(len1);
612 snprintf(filename, len1, "%s%.4d_%s.%s", cmdLine->dir, songs, titre, extension);
613 }
614
615 if ((output_file = fopen(filename, "r")) == NULL)
616 { /* Anti doublons */
617 VERBOSE("New file opened: %s\n", filename);
618 output_file = fopen(filename, WRITE);
619 } else {
620 VERBOSE("File already exists %s.\n", filename);
621 fclose(output_file);
622 output_file = NULL;
623 }
624 (void)free(filename);
625 } /* Song skip */
626 }
627 } /* Title are differents */
628 } /* Oldtitre != NULL */
629 if (oldtitre != NULL)
630 (void)free(oldtitre);
631
632 oldtitre = strdup(titre);
633
634 } else {
635 /* Title is "ASUIVRE" */
636 if (nexttitre != NULL)
637 (void)free(nexttitre);
638
639 nexttitre = strdup(titre);
640 }
641 if (titre != NULL)
642 {
643 (void)free(titre);
644 titre = NULL;
645 }
646
647 } /* Strlen(titre > 0) */
648 }
649 tempsize = 0; /* tempsize = 0: chunk received successfully. Awaiting next chunk */
650 } else if (tempsize > icy_headers->metaint) {
651 _ERROR("Error tempsize > metaint\n");
652 break;
653 } else
654 tempsize = tempsize + size;
655
656 if ((tempsize + 512) >= icy_headers->metaint)
657 nextsize = icy_headers->metaint - tempsize;
658 else
659 nextsize = 512;
660
661 /* Check timeToStop */
662 if ((cmdLine->timeToStop != -1) && (uptime >= cmdLine->timeToStop))
663 {
664 VERBOSE("TimeToStop reached. Stopping cPige.\n");
665 goto cleanup;
666 }
667 } /* infinite loop */
668
669 cleanup:
670 printf("\n");
671 fflush(stdout);
672
673 server_close(server_socket);
674
675 /* cleanup */
676 if (icy_headers != NULL)
677 (void)free(icy_headers);
678 if (output_file != NULL)
679 fclose(output_file);
680 if (logfile != stdout)
681 fclose(logfile);
682
683 (void)free(cmdLine);
684 (void)free(cPigeStats);
685
686 return 0;
687 }
688
689 cPigeUptime *getUptime(long long unsigned int uptime)
690 {
691 cPigeUptime *cu;
692
693 cu = (cPigeUptime *)memory_allocation(sizeof(cPigeUptime));
694
695 /* Calculs bourrins :) */
696 cu->day = (unsigned int) (uptime / (60 * 60 * 24));
697 cu->hour = (unsigned int) (uptime / (60 * 60)) - (cu->day * 24);
698 cu->min = (unsigned int) (uptime / (60)) - ((cu->hour * 60) + (cu->day * 24 * 60));
699 cu->sec = (unsigned int) (uptime) - ((cu->min * 60) + (cu->hour * 60 * 60) + (cu->day * 24 * 60 * 60));
700
701 return cu;
702 }
703
704 char *getStats(long long unsigned int uptime, long long unsigned int metacounter, int metaint)
705 {
706 char *line;
707 cPigeUptime *cu;
708
709 cu = getUptime(uptime);
710 line = (char *)memory_allocation(300); /* Exessif. */
711
712 snprintf(line, 299, "Uptime: %d days, %d hours, %d min, %d seconds\nDownloaded: %lldKo\nSongs played: %ld\nReconnections: %ld\n", cu->day, cu->hour, cu->min, cu->sec, (long long unsigned int)((metacounter*metaint)/1024), cPigeStats->songs, cPigeStats->reconnections);
713
714 free(cu);
715 return line;
716 }
717
718 char *statusLine(long long unsigned int uptime, long long unsigned int metacounter, int metaint, char *titre, char *nexttitre)
719 {
720 char *line;
721 int len;
722 cPigeUptime *cu;
723
724 cu = getUptime(uptime);
725
726 /* Pas terrible... */
727 line = memory_allocation(300);
728
729 len = snprintf(line, 299, "[%dj %dh:%dm:%ds][%lldKo] ", cu->day, cu->hour, cu->min, cu->sec, (long long unsigned int)((metacounter * metaint) / 1024));
730
731 if (cmdLine->pige)
732 len += snprintf(line + len, 299-len, "%dh -> %dh ", getHour(), getHour()+1);
733
734 if (titre != NULL)
735 len += snprintf(line+len, 299-len, "%s", titre);
736
737 if (nexttitre != NULL)
738 if (strstr(nexttitre, titre) == NULL)
739 snprintf(line+len, 299-len, " -> %s", nexttitre);
740
741 free(cu);
742 return line;
743 }
744
745
746 char *readMeta(int serversocket)
747 {
748 char *meta;
749 char c;
750 int retval;
751 int size = 0;
752 int readsize = 0;
753 char *buffer;
754
755 /* For select() it's a global struct. */
756 timeout.tv_sec = SOCKET_TIMEOUT;
757 timeout.tv_usec = 0;
758
759 retval = select(server_socket+1, &rfds, NULL, NULL, &timeout);
760 if (retval <= 0)
761 {
762 _ERROR("Erreur de connexion!\n");
763 server_close(server_socket);
764 return NULL;
765 } else if (recv(server_socket, &c, 1, 0) != 1) {
766 _ERROR("Error reading from server socket: \n", strerror(errno));
767 server_close(server_socket);
768 return NULL;
769 }
770
771 if (c > 0)
772 {
773 meta = (char *)memory_allocation((c*16)+2);
774 buffer = (char *)memory_allocation((c*16)+1);
775
776 /* For select() it's a global struct. */
777 timeout.tv_sec = SOCKET_TIMEOUT;
778 timeout.tv_usec = 0;
779 retval = select(server_socket+1, &rfds, NULL, NULL, &timeout);
780 if (retval <= 0)
781 {
782 _ERROR("Connection error in select. (readmeta)\n");
783 (void)free(buffer);
784 (void)free(meta);
785 server_close(server_socket);
786 return NULL;
787
788 } else {
789 readsize = 0;
790 while(readsize < c*16)
791 {
792 /* For select() it's a global struct. */
793 timeout.tv_sec = SOCKET_TIMEOUT;
794 timeout.tv_usec = 0;
795
796 retval = select(server_socket+1, &rfds, NULL, NULL, &timeout);
797 if (retval <= 0)
798 {
799 _ERROR("Erreur de connexion!\n");
800 server_close(server_socket);
801 return NULL;
802 } else {
803 size = recv(server_socket, buffer, (c*16 - readsize), 0);
804 if (size <= 0)
805 {
806 VERBOSE("Megaproblem here.\n");
807 server_close(server_socket);
808 }
809 readsize += size;
810 strncat(meta, buffer, size);
811 }
812 }
813 (void)free(buffer);
814 }
815 } else {
816 /* Title was not sent. */
817 return NULL;
818 }
819 return meta;
820 }
821
822
823 /* Lorsque le serveur envoie: "StreamTitle='TITREZIK';"
824 * On ne r�cup�re avec cette fonction que TITREZIK.
825 */
826
827 char *getTitle(char *titleString)
828 {
829 int i;
830 char *result;
831 int count = 0;
832 int j = 0;
833 int first = 0;
834
835 if ((titleString == NULL) || (strlen(titleString) == 0))
836 return NULL;
837
838 if (strstr(titleString, "StreamTitle") == NULL)
839 {
840 /* We have there an invalid strea title. Can't be
841 * parsed.
842 *
843 * Aborting, will try another server!
844 */
845 _ERROR("Received a stream with metadata enabled, but at metaint interval, we only received binary crap. Please fix your stream.");
846 return NULL;
847 }
848
849 result = (char *)memory_allocation(strlen(titleString) + 1);
850
851 for (i=0; i < strlen(titleString); i++)
852 {
853 if (count == 1)
854 {
855 if (titleString[(i+1)] != ';')
856 {
857 if (first == 0)
858 {
859 if ((isalnum((int) titleString[i])) && (titleString[i] != ' '))
860 {
861 result[j++] = titleString[i];
862 first++;
863 }
864 } else {
865 result[j++] = titleString[i];
866 }
867 }
868 }
869
870 if (titleString[i] == '\'')
871 {
872 if (count == 0)
873 count++;
874 else if (titleString[(i+1)] == ';')
875 break;
876 }
877 } /* for */
878
879 result[j] = '\0';
880
881 stripBadChar(result, result);
882
883 return result;
884 }
885
886 currentSong *getCurrentSong(char *title)
887 {
888 currentSong *cursong;
889 char c;
890 int i;
891 int j = 0;
892 int flag = 0;
893
894 if (title == NULL) return NULL;
895
896 cursong = (currentSong *)malloc(sizeof(currentSong));
897
898 cursong->title = (char *)memory_allocation(30 + 1); /* id3v1 == 30 chars max */
899 cursong->artist = (char *)memory_allocation(30 + 1); /* id3v1 == 30 chars max */
900
901 for (i = 0; i < strlen(title); i++)
902 {
903 c = title[i];
904 if (!flag) /* ! Flag */
905 {
906 if ((c == '-') && ((j-1) > 0))/* Avant le - => artiste */
907 {
908 if (cursong->artist[(j-1)] == ' ')
909 cursong->artist[(j-1)] = '\0';
910
911 flag = 1;
912 j = 0; /* Pour l'espace */
913 } else {
914 if (j < 30) /* "l'espace!" */
915 cursong->artist[j++] = c;
916 }
917 } else { /* Flag */
918 if (j < 30)
919 {
920 if (j == 0 && c == ' ') continue;
921 cursong->title[j++] = c;
922 }
923 }
924 } /* boucle :) */
925
926 if (strlen(cursong->title) == 0)
927 {
928 (void)free(cursong->title);
929 cursong->title = NULL;
930 }
931
932 if (strlen(cursong->artist) == 0)
933 {
934 (void)free(cursong->artist);
935 cursong->artist = NULL;
936 }
937
938 /* Last byte of artist & title in the structure
939 * are 0 as they have been initialized with calloc.
940 */
941 return cursong;
942 }
943
944 void print_credits()
945 {
946 printf ("cPige %s by Laurent Coustet (c) 2005\n", VERSION);
947 return;
948 }
949
950 void print_help()
951 {
952 fprintf (stderr,
953 "cPige help. cPige is a Laurent Coustet product.\n"
954 "For more informations about me and my software,\n"
955 "please visit http://ed.zehome.com/\n\n"
956 #ifdef NOCONFIG
957 "Usage: ./cpige -h http://stream-hautdebit.frequence3.net:8000/ -d /home/ed/Pige -l logfile.log\n\n"
958 " -h host to connect to.\n"
959 " -V show cpige Version.\n"
960 " -d directory save stream to this directory.\n"
961 " -P Pige mode (takes no argument), save stream by hour.\n"
962 " -M Use pige Meta: will write id3v1 tag (only usefull with pige mode).\n"
963 " -q Quiet mode, does not output stream status.\n"
964 " -b Background mode (UNIX only) use cPige as a daemon.\n"
965 " -l Path to logfile.\n"
966 " -I [h|m] pige mode will cut on a hour by hour basis or min by min basis.\n"
967 " -i nb how many \"nb\" hour(s) or minute(s) we should wait before cutting.\n"
968 #ifndef WIN32
969 " -n cPige will append xxxx to file in 'non pige mode', where xxxx is a number.\n");
970 #else
971 );
972 #endif
973 #else
974 "Usage: ./cpige -c path/to/cpige.conf\n"
975 " -c path/to/cpige.conf.\n");
976 #endif
977 fflush(stderr);
978 }
979
980 commandLine *parseCommandLine(int argc, char **argv)
981 {
982 commandLine *cmdLine;
983 int i;
984 char *c;
985
986 #ifndef NOCONFIG
987 char *buffer;
988 config_t *conf, *workconf;
989 #endif
990
991 if (argc < 2)
992 {
993 print_help();
994 _exit(-1);
995 }
996
997 cmdLine = (commandLine *)malloc(sizeof(commandLine));
998
999 /* default is not pige mode */
1000 cmdLine->pige = 0;
1001 cmdLine->quiet = 0;
1002 cmdLine->live = 0;
1003 cmdLine->background = 0;
1004 cmdLine->interval = 1;
1005 cmdLine->intervalType = IVAL_HOUR;
1006 cmdLine->usePigeMeta = 0;
1007 cmdLine->useNumbers = 0;
1008 cmdLine->skipSongs = 0;
1009 cmdLine->useGUI = 0;
1010
1011 cmdLine->logFile = "cpige.log";
1012 cmdLine->dir = NULL;
1013 cmdLine->Next = "A suivre";
1014
1015 for (i = 1; i < argc; i++)
1016 {
1017 c = strchr("c", argv[i][1]);
1018 if (c != NULL)
1019 {
1020 /* from streamripper */
1021 if ((i == (argc-1)) || (argv[i+1][0] == '-'))
1022 {
1023 (void)print_help();
1024 fprintf(stderr, "option %s requires an argument\n", argv[i]);
1025 _exit(1);
1026 }
1027 }
1028
1029 switch (argv[i][1])
1030 {
1031 case 'V':
1032 printf("%s\n", USER_AGENT);
1033 _exit(0);
1034 break;
1035
1036 #ifndef NOCONFIG
1037 case 'c':
1038 i++;
1039 configFile = strdup(argv[i]);
1040 break;
1041 case 'g':
1042 cmdLine->useGUI = 1;
1043 cmdLine->quiet = 1;
1044 break;
1045 #else
1046 case 'h':
1047 i++;
1048
1049 if (serversPool == NULL)
1050 serversPool = addPoolUrl(NULL, argv[i]);
1051 else
1052 addPoolUrl(serversPool, argv[i]);
1053
1054 break;
1055
1056 case 'd':
1057 i++;
1058 {
1059 int len = strlen(argv[i]);
1060 cmdLine->dir = (char *)memory_allocation(len+2);
1061 strncpy(cmdLine->dir, argv[i], len);
1062
1063 /* TODO: Windows compatibility ? */
1064 if (cmdLine->dir[len-1] != '/')
1065 {
1066 cmdLine->dir[len] = '/';
1067 cmdLine->dir[len+1] = 0;
1068 }
1069 }
1070 break;
1071 #ifndef WIN32
1072 case 'n':
1073 cmdLine->useNumbers = 1;
1074 break;
1075 #endif
1076 case 'l':
1077 i++;
1078 cmdLine->logFile = strdup(argv[i]);
1079 break;
1080
1081 case 'P':
1082 if (cmdLine->live == 1)
1083 {
1084 printf("You can't use Live Mode and Pige mode simultaneously.\n");
1085 _exit(-1);
1086 }
1087 if (cmdLine->pige == 1)
1088 break;
1089
1090 cmdLine->pige = 1;
1091 printf("Pige Mode activated.\n");
1092 break;
1093
1094 case 'q':
1095 cmdLine->quiet = 1;
1096 break;
1097
1098 case 'b':
1099 cmdLine->quiet = 1;
1100 cmdLine->background = 1;
1101 break;
1102
1103 case 'L':
1104 if (cmdLine->pige == 1)
1105 {
1106 printf("You can't use Live Mode and Pige mode simultaneously.\n");
1107 _exit(-1);
1108 }
1109 cmdLine->live = 1;
1110 printf("Live Mode activated.\n");
1111 break;
1112
1113 case 'M':
1114 if (cmdLine->pige == 1)
1115 cmdLine->usePigeMeta = 1;
1116
1117 break;
1118
1119 case 'I':
1120 i++;
1121
1122 if (cmdLine->pige != 1)
1123 {
1124 cmdLine->pige = 1;
1125 printf("Pige Mode activated.\n");
1126 }
1127
1128 if ( *argv[i] == 'h' || *argv[i] == 'H' )
1129 cmdLine->intervalType = IVAL_HOUR;
1130 else if ( *argv[i] == 'm' || *argv[i] == 'M' )
1131 {
1132 cmdLine->intervalType = IVAL_MIN;
1133 if (cmdLine->interval == 1)
1134 cmdLine->interval = 30;
1135 }
1136 else
1137 {
1138 fprintf(stderr, "Unknown interval type.\n");
1139 _exit(1);
1140 }
1141 break;
1142
1143 case 'i':
1144 i++;
1145
1146 if (cmdLine->pige != 1)
1147 {
1148 cmdLine->pige = 1;
1149 printf("Pige Mode activated.\n");
1150 }
1151
1152 cmdLine->interval = atoi(argv[i]);
1153 if (cmdLine->interval == 0)
1154 {
1155 fprintf(stderr, "Invalid interval 0.\n");
1156 _exit(1);
1157 }
1158
1159 break;
1160
1161 #endif /* NOCONFIG */
1162
1163 default:
1164 fprintf(stderr, "Unknown switch: `%s'\n", argv[i]);
1165 break;
1166 } /* switch */
1167 } /* for */
1168
1169 #ifndef NOCONFIG
1170 /* Use the config file parser :) */
1171
1172 if (configFile == NULL)
1173 configFile = strdup("./cpige.conf");
1174
1175 /* Configfile Readin */
1176 printf("Reading config file: %s\n", configFile);
1177 if ( (conf = parseConfig( configFile )) == NULL)
1178 {
1179 fprintf(stderr, "Could not read config from: %s.\n", configFile);
1180 _exit(0);
1181 }
1182
1183 /* Setting up url pool */
1184 workconf = conf;
1185 while (workconf != NULL)
1186 {
1187 workconf = _conf_getValue(workconf, "url", &buffer);
1188 if (buffer == NULL)
1189 break;
1190 else
1191 {
1192 VERBOSE("Adding %s to the pool.", buffer);
1193 if (serversPool == NULL)
1194 serversPool = addPoolUrl(NULL, buffer);
1195 else
1196 addPoolUrl(serversPool, buffer);
1197 free(buffer);
1198 }
1199 }
1200
1201 /* Setting up cpige common parameters */
1202 set_str_from_conf(conf, "savedirectory", &buffer, NULL, "savedirectory not found in config file.\n", 1);
1203
1204 {
1205 int len = strlen(buffer);
1206 cmdLine->dir = (char *)memory_allocation(len+2);
1207 strncpy(cmdLine->dir, buffer, len);
1208 free(buffer);
1209
1210 /* TODO: Windows compatibility ? */
1211 if (cmdLine->dir[len-1] != '/')
1212 {
1213 cmdLine->dir[len] = '/';
1214 cmdLine->dir[len+1] = 0;
1215 }
1216 }
1217
1218 /* String values */
1219 #ifndef WIN32
1220 set_str_from_conf(conf, "pidfile", &(cmdLine->pidFile), "/var/run/cpige.pid", "Warning: no pid file defined. Using /var/run/cpige.pid\n", 0);
1221 #endif
1222 set_str_from_conf(conf, "logfile", &(cmdLine->logFile), "./cpige.log", NULL, 0);
1223 set_str_from_conf(conf, "nexttitle", &(cmdLine->Next), "A suivre", NULL, 0);
1224
1225 #ifndef WIN32
1226 set_str_from_conf(conf, "locale", &buffer, "C", NULL, 0);
1227
1228 if (setlocale(LC_TIME, buffer) == NULL)
1229 _ERROR("Error setting up locale: `%s'.\n", buffer);
1230 free(buffer);
1231 #endif
1232
1233 /* Int values */
1234 set_int_from_conf(conf, "cutdelay", &(cmdLine->interval), 30, NULL, 0);
1235 set_int_from_conf(conf, "skipsongs", &(cmdLine->skipSongs), 0, NULL, 0);
1236 set_str_from_conf(conf, "cuttype", &buffer, "h", NULL, 0);
1237 if ( (*buffer == 'h') || (*buffer == 'H') )
1238 cmdLine->intervalType = IVAL_HOUR;
1239 else if ( (*buffer == 'm') || (*buffer == 'M') )
1240 {
1241 cmdLine->intervalType = IVAL_MIN;
1242 if (cmdLine->interval == 1)
1243 cmdLine->interval = 30;
1244 } else {
1245 fprintf(stderr, "Unknown interval type: `%s'. Should be 'm' or 'h'\n", buffer);
1246 _exit(1);
1247 }
1248 free(buffer);
1249
1250 set_str_from_conf(conf, "timetostop", &buffer, NULL, NULL, 0);
1251 if (buffer != NULL)
1252 {
1253 /* Feature activated */
1254 for (i = 0; i < strlen(buffer); i++)
1255 {
1256 if (! isdigit(buffer[i]))
1257 {
1258 /* We have a letter */
1259 char c = buffer[i];
1260 buffer[i] = 0;
1261
1262 if (c == 'h')
1263 {
1264 cmdLine->timeToStop = atoi(buffer) * 3600;
1265 } else if (c == 'm') {
1266 cmdLine->timeToStop = atoi(buffer) * 60;
1267 } else if (c == 's') {
1268 cmdLine->timeToStop = atoi(buffer);
1269 } else {
1270 fprintf(stderr, "Unknown interval for timetostop (%c)\n", c);
1271 }
1272
1273 fprintf(stdout, "Setting timetostop: %lld\n", cmdLine->timeToStop);
1274 break;
1275 }
1276 }
1277 free(buffer);
1278 } else {
1279 cmdLine->timeToStop = -1;
1280 }
1281
1282 /* Boolean values */
1283 set_bool_from_conf(conf, "weekbackup", &(cmdLine->weekBackup), 0, NULL, 0);
1284 set_bool_from_conf(conf, "pigemode", &(cmdLine->pige), 0, NULL, 0);
1285 set_bool_from_conf(conf, "pigemeta", &(cmdLine->usePigeMeta), 1, NULL, 0);
1286 if (cmdLine->useGUI != 1)
1287 {
1288 set_bool_from_conf(conf, "quietmode", &(cmdLine->quiet), 0, NULL, 0);
1289 set_bool_from_conf(conf, "background", &(cmdLine->background), 0, NULL, 0);
1290 }
1291 set_bool_from_conf(conf, "usenumbers", &(cmdLine->useNumbers), 0, NULL, 0);
1292
1293 /* TODO: I know there's a little memleak there,
1294 * as we are not freeing the conf chained list,
1295 * and not freeing var(s) / val(s)
1296 */
1297
1298 if (cmdLine->background)
1299 cmdLine->quiet = 1;
1300
1301 #endif /* Using config file */
1302
1303 if (cmdLine->dir == NULL)
1304 cmdLine->dir = strdup("./");
1305
1306 _conf_freeConfig(conf);
1307 /* Don't forget to free it on exit ! */
1308 return cmdLine;
1309 }
1310
1311 int getCloserInterval(int now, int interval)
1312 {
1313 int tmp;
1314 tmp = (now % interval);
1315 return (now - tmp);
1316 }
1317
1318 int mustCut(lastCut *cut)
1319 {
1320 int ret = 0;
1321 int closer;
1322
1323 if (cmdLine->intervalType == IVAL_HOUR)
1324 {
1325 closer = getCloserInterval(getHour(), cmdLine->interval);
1326 if (closer != cut->hour)
1327 ret = 1;
1328 } else {
1329 closer = getCloserInterval(getMinute(), cmdLine->interval);
1330 if (closer != cut->min)
1331 ret = 1;
1332 }
1333
1334 /*
1335 * if (ret)
1336 * printf("must cut will return true: itype: %d ival: %d closer: %d hour: %d min: %d\n", cmdLine->intervalType, cmdLine->interval, closer, cut->hour, cut->min);
1337 */
1338 return ret;
1339 }
1340
1341 /* Returns what song number we lastly saved
1342 * the stream to
1343 */
1344
1345 #ifndef WIN32
1346 int getSongs(char *dir)
1347 {
1348 DIR *dirp;
1349 char *filename;
1350 static regex_t *match = NULL;
1351 int songs = 0;
1352 int current = 0;
1353 struct dirent *cur_dir;
1354
1355 if (match == NULL)
1356 {
1357 match = (regex_t *)malloc(sizeof(regex_t));
1358 if (regcomp(match, "^[0-9]+.*\\.(mp3|aac)$", REG_EXTENDED) != 0)
1359 {
1360 _ERROR("Regex compilation error... Please contact developper!\n");
1361 return 0;
1362 }
1363 }
1364
1365 if ( (dirp = opendir(dir)) == NULL)
1366 {
1367 _ERROR("Unable to open directory: %s\n", strerror(errno));
1368 return 0;
1369 }
1370
1371 while ((cur_dir = readdir(dirp)) != NULL)
1372 {
1373 filename = cur_dir->d_name;
1374
1375 if ((*filename == '.') || (strcmp(filename, "..") == 0))
1376 continue;
1377
1378 if (regexec(match, filename, 0, 0, 0) == 0)
1379 {
1380 /* Match */
1381 current = atoi(filename); /* A bit uggly.. */
1382 if (current > songs)
1383 songs = current;
1384 }
1385 }
1386
1387 closedir(dirp);
1388
1389 return songs;
1390 }
1391 #endif
1392
1393 int checkInterval()
1394 {
1395 int ret = 0;
1396 if (cmdLine->intervalType == IVAL_HOUR)
1397 {
1398 if ((cmdLine->interval <= 0) || (cmdLine->interval > 12))
1399 ret = 1;
1400
1401 } else if (cmdLine->intervalType == IVAL_MIN) {
1402 if ((cmdLine->interval <= 0 || cmdLine->interval > 30))
1403 ret = 1;
1404 } else {
1405 ret = 1;
1406 fprintf(stderr, "Intenal Error: intervalType unknown!\n");
1407 }
1408
1409 return ret;
1410 }
1411
1412 void testCommandLine()
1413 {
1414 if (cmdLine == NULL)
1415 {
1416 (void) print_help();
1417 _exit(-1);
1418 }
1419
1420 if (serversPool == NULL)
1421 {
1422 (void) print_help();
1423 _exit(-1);
1424 }
1425
1426 if (checkInterval() != 0)
1427 {
1428 fprintf(stderr, "Incorrect interval specified. Exiting...\n");
1429 _exit(1);
1430 }
1431 }
1432
1433 /* Tries to create directories for week backup */
1434 void checkWeekBackup()
1435 {
1436 int i;
1437 char *dayName;
1438 char *dirName;
1439 int dirNameLen;
1440 time_t when;
1441 DIR *weekdir;
1442
1443 if ((! cmdLine->weekBackup) || (cmdLine->pige == 0))
1444 return;
1445
1446 when = time((time_t *) NULL);
1447 /* One week => 7 days ! */
1448 for (i = 0; i < 7; i++)
1449 {
1450 dayName = getDayName((time_t)(when+(i*86400)));
1451
1452 dirNameLen = strlen(cmdLine->dir) + strlen(dayName) + 2;
1453 dirName = (char *) memory_allocation(dirNameLen);
1454 snprintf(dirName, dirNameLen, "%s%s/", cmdLine->dir, dayName);
1455 (void)free(dayName);
1456
1457 /* Create output dir if does not exists! */
1458 if (( weekdir = opendir(dirName)) == NULL)
1459 {
1460 _ERROR("Unable to open %s for writing\n", dirName);
1461 #ifdef WIN32
1462 if (mkdir(dirName) != 0) {
1463 #else
1464 if (mkdir(dirName, 0755) != 0) {
1465 #endif
1466 _ERROR("Failed trying to create %s. Verify rights.\n", dirName);
1467 fprintf(stderr, "Failed trying to create %s. Verify rights.\n", dirName);
1468 _exit(-1);
1469 } else {
1470 VERBOSE("Created %s\n", dirName);
1471 /* pige_dir = opendir(cmdLine->dir); */
1472 }
1473 } else {
1474 VERBOSE("Sucessfully opened %s\n", dirName);
1475 closedir(weekdir);
1476 }
1477
1478 (void)free(dirName);
1479 }
1480 }
1481
1482 #ifdef SOLARIS
1483 int daemon(int nochdir, int noclose)
1484 {
1485 /* Dummy function, do not use out of cPige ! */
1486 int pid;
1487
1488 pid = fork();
1489 if (pid == 0)
1490 {
1491 _exit(0);
1492 }
1493 return 0;
1494 }
1495 #endif
1496