1 /*
2 * Copyright (C) 2002-2003 Stefan Holst
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA.
17 *
18 * parsing mediamarks
19 */
20
21 #include "config.h"
22
23 #include <stdio.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <ctype.h>
27 #include <dirent.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #include <string.h>
31 #include <libgen.h>
32
33 #include "common.h"
34
35 #include "oxine.h"
36 #include "mediamarks.h"
37 #include "playlist.h"
38 #include "otk.h"
39 #include "xine/xmlparser.h"
40 #include "utils.h"
41
42
43 #define TYPE_NONE (0)
44 #define TYPE_DIR (1)
45 #define TYPE_REG (2)
46 #define TYPE_RDIR (3)
47 #define TYPE_RREG (4)
48 #define TYPE_UP (5)
49 #define TYPE_MULTIPLE (6)
50 #define TYPE_M3U (7)
51
52 /* sort methods 0 is default*/
53 #define PLAYITEM_SORT_FILES (1)
54 #define PLAYITEM_SORT_DIRFILES (0)
55
56 #define MM_ACTION_PLAY (1)
57 #define MM_ACTION_ADD (2)
58
59 static const char *mm_action_strings[] = { "Play", "Add to PL" };
60
61 typedef struct playitem_s playitem_t;
62
63 struct playitem_s {
64
65 int type;
66 char *title;
67 char *mrl;
68 list_t *sub;
69 };
70
71 typedef struct mm_session_s mm_session_t;
72
73 struct mm_session_s {
74
75 oxine_t *oxine;
76 otk_widget_t *list;
77 int listpos;
78 list_t *backpath;
79 int action;
80 char ilabel[64];
81 };
82
83 /* private functions */
84 static int file_is_m3u(const char *mrl);
85
86
87 /* body of mediamarks functions */
88
bpush(list_t * list,list_t * item)89 static void bpush (list_t *list, list_t *item) {
90
91 list_first_content(list);
92 list_insert_content(list, item);
93 }
94
bpop(list_t * list)95 static list_t *bpop (list_t *list) {
96
97 list_t *item;
98
99 item = list_first_content(list);
100 list_delete_current(list);
101
102 return item;
103 }
104
105 /*
106 * implements case insensitive lengthlexicographic order
107 *
108 * returns 1 if a <= b
109 * 0 otherwise.
110 */
111
in_cill_order(playitem_t * ita,playitem_t * itb,int type)112 static int in_cill_order(playitem_t *ita, playitem_t *itb, int type) {
113
114 char *a = ita->title;
115 char *b = itb->title;
116
117 char *ap, *bp; /* pointers to a, b */
118 char ac, bc;
119
120 int rtypea=0, rtypeb=0;
121
122 ap = a; bp = b;
123
124 /* printf("%s %s\n", a, b);
125 stat(ita->mrl, &filestat1);
126 stat(itb->mrl, &filestat2);*/
127
128 if(type == PLAYITEM_SORT_DIRFILES) {
129 if((ita->type == TYPE_DIR)||(ita->type == TYPE_RDIR)) rtypea = 1;
130 if((itb->type == TYPE_DIR)||(itb->type == TYPE_RDIR)) rtypeb = 1;
131
132 if((rtypea)&&(!rtypeb)) return 1;
133 if((!rtypea)&&(rtypeb)) return 0;
134
135 /*if((S_ISDIR(filestat1.st_mode))&&(!S_ISDIR(filestat2.st_mode))) return 1;
136 if((!S_ISDIR(filestat1.st_mode))&&(S_ISDIR(filestat2.st_mode))) return 0;*/
137 }
138
139 while (1) {
140 ac = *ap; bc = *bp;
141 if ((ac >= 'A') && (ac <= 'Z')) ac |= 0x20;
142 if ((bc >= 'A') && (bc <= 'Z')) bc |= 0x20;
143
144 if (ac < bc) return 1;
145 if (ac > bc) return 0;
146 if (!ac) return 1;
147
148 ap++; bp++;
149 }
150 return 1;
151 }
152
153
playitem_sort_into(playitem_t * item,list_t * list,int type)154 static void playitem_sort_into(playitem_t *item, list_t *list, int type) {
155
156 playitem_t *i;
157
158 i = list_first_content(list);
159
160 while(i) {
161 if (in_cill_order(item, i, type)) {
162 list_insert_content(list, item);
163 return;
164 }
165 i = list_next_content(list);
166 }
167 list_append_content(list, item);
168
169 }
170
playitem_append(playitem_t * item,list_t * list)171 static void playitem_append(playitem_t *item, list_t *list) {
172
173 list_append_content(list, item);
174 }
175
playitem_new(int type,const char * title,const char * mrl,list_t * sub)176 static playitem_t *playitem_new(int type, const char *title, const char *mrl, list_t *sub) {
177
178 playitem_t *item = ho_new(playitem_t);
179
180 item->type = type;
181 item->sub = sub;
182 if (title) item->title = ho_strdup(title);
183 if (mrl) item->mrl = ho_strdup(mrl);
184
185 return item;
186 }
187
free_subtree(list_t * list,int i)188 static void free_subtree(list_t *list, int i) {
189
190 playitem_t *item;
191
192 if (!list) return;
193
194 item = list_first_content(list);
195 while (item) {
196 if (item->title) ho_free(item->title);
197 if (item->mrl) ho_free(item->mrl);
198 if(item->type != TYPE_UP) free_subtree(item->sub, 1);
199 list_delete_current(list);
200 ho_free(item);
201 item = list_first_content(list);
202 }
203
204 if(i) list_free(list);
205 }
206
207 /*static void playitem_free(playitem_t *item) {
208 playitem_t *child;
209
210 if(item->title) ho_free(item->title);
211 if(item->mrl) ho_free(item->mrl);
212
213 if(item->sub) {
214 child = list_first_content(item->sub);
215
216 while(child) {
217 playitem_free(child);
218 list_delete_current(item->sub);
219 child = list_first_content(item->sub);
220 }
221 list_free(item->sub);
222 }
223
224 ho_free(item);
225 }*/
226
playitem_load(xml_node_t * node)227 static playitem_t *playitem_load (xml_node_t *node) {
228
229 playitem_t *play_item;
230 struct stat filestat;
231
232 play_item = ho_new(playitem_t);
233
234 while (node) {
235
236 if (!strcasecmp (node->name, "title")) {
237 play_item->title = ho_strdup (node->data);
238 #ifdef LOG
239 printf ("mediamarks: title = %s\n", play_item->title);
240 #endif
241 } else if (!strcasecmp (node->name, "ref")) {
242 const char *type = xml_parser_get_property(node, "type");
243 play_item->mrl = ho_strdup(xml_parser_get_property (node, "href"));
244 if(type) {
245 if(!strcasecmp(type, "multiple")) {
246 play_item->type = TYPE_MULTIPLE;
247 play_item->sub = list_new();
248 }
249 } else {
250 filestat.st_mode = 0;
251 stat(play_item->mrl, &filestat);
252
253 if(S_ISDIR(filestat.st_mode)) {
254 play_item->sub = list_new();
255 play_item->type = TYPE_RDIR;
256 } else {
257 play_item->type = TYPE_REG;
258 if(file_is_m3u(play_item->mrl)) play_item->type = TYPE_M3U;
259 }
260 }
261
262 #ifdef LOG
263 printf ("mediamarks: mrl = %s\n", play_item->mrl);
264 #endif
265 } else if (!strcasecmp (node->name, "time")) {
266 } else
267 printf ("mediamarks: error while loading mediamarks file, unknown node '%s'\n", node->name);
268
269 node = node->next;
270 }
271
272 return play_item;
273 }
274
parse_multiple(oxine_t * oxine,const char * mrl,list_t * list)275 static int parse_multiple(oxine_t *oxine, const char *mrl, list_t *list) {
276 int i=0, num=0;
277 playitem_t *item;
278 #if XINE_MAJOR_VERSION < 1 || (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION < 2)
279 char **str;
280 #else
281 const char * const *str;
282 #endif
283
284 str = xine_get_autoplay_mrls (oxine->xine, mrl, &num);
285 if (num<=0) return 0;
286
287 free_subtree(list, 0);
288
289 while(i < num) {
290 /* printf("%d %s\n", i, str[i]); */
291 item = playitem_new(TYPE_REG, str[i], str[i], list_new());
292 playitem_append(item, list);
293 i++;
294 }
295 return 1;
296 }
297
read_directory(oxine_t * oxine,const char * dir,list_t * list)298 static int read_directory(oxine_t *oxine, const char *dir, list_t *list) {
299
300 DIR *dirp;
301 struct dirent *entp;
302 playitem_t *item;
303 int ret = 0;
304
305 free_subtree(list, 0);
306
307 #if 0
308 printf("Processing %s\n", dir);
309 #endif
310 dirp = opendir (dir);
311 if (dirp != NULL)
312 {
313 while ((entp = readdir(dirp))) {
314
315 struct stat filestat;
316 char *mrl;
317 char *title = NULL;
318 int type = 0;
319
320 if((!strcmp(entp->d_name, "."))||(!strcmp(entp->d_name, "..")))
321 continue;
322
323 #ifndef SHOW_HIDDEN_FILES
324 if(entp->d_name[0] == '.')
325 continue;
326 #endif
327
328 if (asprintf(&mrl, "%s/%s", dir, entp->d_name) < 1)
329 continue;
330 if (stat(mrl, &filestat) < 0) {
331 free(mrl);
332 continue;
333 }
334
335 if(file_is_m3u(mrl)) {
336 if (asprintf(&title, "[%s]", entp->d_name) < 0)
337 title = NULL;
338 type = TYPE_M3U;
339 } else if(S_ISDIR(filestat.st_mode)) {
340 if (asprintf(&title, "[%s]", entp->d_name) < 0)
341 title = NULL;
342 type = TYPE_RDIR;
343 } else if (S_ISREG(filestat.st_mode)) {
344 title = strdup(entp->d_name);
345 type = TYPE_RREG;
346 }
347
348 item = playitem_new(type, NULL, NULL, list_new());
349 item->mrl = mrl;
350 item->title = title;
351 #if 0
352 printf("mrl : %s\n", item->mrl);
353 printf("title : %s\n", item->title);
354 printf("type : %d\n", item->type);
355 #endif
356 /* printf("ei %d\n", oxine->mm_sort_type); */
357 /* xine_config_lookup_entry (oxine->xine, "oxine.sort_type", &entry); */
358
359 playitem_sort_into(item, list, oxine->mm_sort_type);
360 }
361 closedir (dirp);
362 ret = 1;
363 }
364 else
365 printf ("mediamarks: Couldn't open the directory.\n");
366
367 return ret;
368 }
369
read_entire_file(const char * mrl,int * file_size)370 static char *read_entire_file (const char *mrl, int *file_size) {
371
372 char *buf;
373 struct stat statb;
374 int fd;
375
376 if (stat (mrl, &statb) < 0) {
377 printf ("mediamarks: cannot stat '%s'\n", mrl);
378 return NULL;
379 }
380
381 *file_size = statb.st_size;
382
383 fd = xine_open_cloexec(mrl, O_RDONLY);
384 if (fd<0)
385 return NULL;
386
387 /* buf = malloc (sizeof(char)*((*file_size)+1)); */
388 buf = ho_newstring((*file_size)+1);
389
390 if (!buf)
391 return NULL;
392
393 buf[*file_size]=0;
394
395 *file_size = read (fd, buf, *file_size);
396
397 close (fd);
398
399 return buf;
400 }
401
file_is_m3u(const char * mrl)402 static int file_is_m3u(const char *mrl) {
403
404 #ifdef M3U_AS_SUBDIR
405
406 FILE *file;
407 char **line;
408 int *n;
409 int a;
410
411 file = fopen(mrl, "r");
412 if(!file) return 0;
413
414 n = ho_new(size_t);
415 line = ho_new(char *);
416
417 *line = NULL;
418 *n = 0;
419 a = getline(line, n, file);
420
421 fclose(file);
422
423 if(a<=0) {
424 ho_free(n);
425 ho_free(line);
426 return 0;
427 }
428 if(!strncmp(*line, "#EXTM3U", 7)) {
429 ho_free(n);
430 ho_free(line);
431 return 1;
432 }
433
434 ho_free(n);
435 ho_free(line);
436
437 if( strstr(mrl,".m3u") != NULL || strstr(mrl,".M3U") )
438 return 1;
439 else
440 return 0;
441
442 #else
443
444 return 0; /* m3u is a normal file here, let xine-ui handle it as playlist */
445
446 #endif
447 }
448
parse_m3u(const char * mrl,list_t * items)449 static void parse_m3u(const char *mrl, list_t *items) {
450 FILE *file;
451 char **line;
452 size_t *n;
453 int a;
454
455 file = fopen(mrl, "r");
456 if(!file) return ;
457
458 n = ho_new(size_t);
459 line = ho_new(char *);
460
461 *line = NULL;
462 *n = 0;
463 a = getline(line, n, file);
464 if(a<=0) {
465 fclose(file);
466 return;
467 }
468
469 while((a = getline(line, n, file))>0) {
470 char *str;
471 playitem_t *item;
472
473 if(*line[0] == '#') continue;
474 str = strndup(*line, a-1);
475 /* printf("%s\n", str); */
476 item = playitem_new (TYPE_REG, basename(str), str, list_new());
477 ho_free(str);
478 playitem_append(item, items);
479 }
480 ho_free(line);
481 ho_free(n);
482 fclose(file);
483 }
484
read_subs(xml_node_t * node,list_t * items)485 static void read_subs(xml_node_t *node, list_t *items) {
486
487 playitem_t *item = NULL;
488
489 while (node) {
490
491 if (!strcasecmp (node->name, "entry")) {
492
493 item = playitem_load (node->child);
494
495 } else if(!strcasecmp (node->name, "sub")) {
496 char title[256];
497
498 snprintf(title, 255, "[%s]", xml_parser_get_property (node, "name"));
499 item = playitem_new (TYPE_DIR, title, NULL, list_new());
500 read_subs(node->child, item->sub);
501 }
502 playitem_append(item, items);
503 node=node->next;
504 }
505 }
506
read_mediamarks(list_t * list,const char * mrl)507 static int read_mediamarks(list_t *list, const char *mrl) {
508
509 int size;
510 char *file = read_entire_file(mrl, &size);
511 xml_node_t *node;
512
513 if (!file) return 0;
514
515 xml_parser_init_R (xml_parser_t *xml, file, strlen (file), XML_PARSER_CASE_INSENSITIVE);
516
517 if (xml_parser_build_tree_R (xml, &node)<0) {
518 printf("mediamarks: xml parsing of %s failed\n", mrl);
519 xml_parser_finalize_R (xml);
520 return 0;
521 }
522
523 if (strcasecmp (node->name, "oxinemm")) {
524 printf ("mediamarks: error, root node must be OXINEMM\n");
525 xml_parser_finalize_R (xml);
526 return 0;
527 }
528
529 read_subs(node->child, list);
530 xml_parser_free_tree(node);
531 xml_parser_finalize_R (xml);
532 ho_free(file);
533
534 return 1;
535 }
536
changelist(otk_widget_t * list,list_t * backpath)537 static void changelist (otk_widget_t *list, list_t *backpath) {
538
539 playitem_t *item, *back;
540 list_t *current = list_first_content(backpath);
541 list_t *parent = list_next_content(backpath);
542
543 otk_remove_listentries(list);
544
545 item = list_first_content(current);
546
547 if((item->type != TYPE_UP)&&(parent)) {
548 back = playitem_new(TYPE_UP, "[..]", NULL, parent);
549 list_insert_content(current, back);
550 //otk_add_listentry(list, back->title, back, -1);
551 }
552 item = list_first_content(current);
553 while (item) {
554 /*printf("item : %s\n" ,item->title);*/
555 otk_add_listentry(list, item->title, item, -1);
556
557 item = list_next_content(current);
558 }
559 otk_list_set_pos(list,0);
560 otk_set_focus(list);
561 }
562
action_changed(void * data,int pos)563 static void action_changed (void *data, int pos) {
564
565 mm_session_t *session = (mm_session_t*) data;
566
567 session->action = pos;
568 }
569
set_ilabel(mm_session_t * session)570 static void set_ilabel(mm_session_t *session) {
571 int n;
572
573 n = gGui->playlist.num;
574 sprintf(session->ilabel, "Selected: %3d", n);
575 }
576
mediamarks_play_cb(void * data,void * entry)577 static void mediamarks_play_cb(void *data, void *entry) {
578
579 mm_session_t *session = (mm_session_t*) data;
580 oxine_t *oxine = session->oxine;
581 playitem_t *item = (playitem_t*) entry;
582
583 session->listpos = otk_list_get_pos(session->list);
584 #ifdef LOG
585 printf("mediamarks: mediamarks_play_cb %s\n", item->mrl);
586 fflush(NULL);
587 #endif
588
589 if (item->type == TYPE_DIR) {
590 bpush(session->backpath, item->sub);
591 changelist(session->list, session->backpath);
592 otk_draw_all(oxine->otk);
593 } else if (item->type == TYPE_RDIR) {
594 if( read_directory(oxine, item->mrl, item->sub) ) {
595 bpush(session->backpath, item->sub);
596 changelist(session->list, session->backpath);
597 otk_draw_all(oxine->otk);
598 }
599 } else if (item->type == TYPE_UP) {
600 bpop(session->backpath);
601 changelist(session->list, session->backpath);
602 otk_draw_all(oxine->otk);
603 } else if (session->action == MM_ACTION_PLAY) {
604 if ((item->type == TYPE_REG)||(item->type == TYPE_RREG)) {
605 printf("mediamarks: playing %s\n", item->mrl);
606 /* if (is_movie(item->mrl)) {
607 oxine->main_window = NULL;
608 oxine->info_window = NULL;
609 otk_clear(oxine->otk);
610 if (oxine->filename)
611 ho_free(oxine->filename);
612 oxine->filename=ho_strdup(item->mrl);
613 select_subtitle_cb(get_dirname(oxine->filename), oxine, 0);
614 } else */ if(odk_open_and_play(oxine->odk, item->mrl)) {
615 set_ilabel(session);
616 oxine->main_window = NULL;
617 oxine->info_window = NULL;
618 oxine->mode = OXINE_MODE_NORMAL;
619 otk_clear(oxine->otk);
620 } /* else play_error(oxine); */
621 set_ilabel(session);
622 } else if (item->type == TYPE_M3U) {
623 parse_m3u(item->mrl, item->sub);
624 bpush(session->backpath, item->sub);
625 changelist(session->list, session->backpath);
626 otk_draw_all(oxine->otk);
627 } else if (item->type == TYPE_MULTIPLE) {
628 if (parse_multiple(oxine, item->mrl, item->sub)) {
629 bpush(session->backpath, item->sub);
630 changelist(session->list, session->backpath);
631 otk_draw_all(oxine->otk);
632 } else
633 printf("mediamarks: error getting autoplay mrls\n");
634 } else printf("mediamarks: %s is of unknown type\n", item->mrl);
635 } else if (session->action == MM_ACTION_ADD) {
636 if ((item->type == TYPE_REG)||(item->type == TYPE_RREG)) {
637 odk_enqueue(session->oxine->odk, item->mrl); /* FIXME: item->title unused */
638 set_ilabel(session);
639 otk_draw_all(session->oxine->otk);
640 }
641 }
642 }
643
mediamarks_leavetoplaylist_cb(void * data)644 static void mediamarks_leavetoplaylist_cb (void *data) {
645
646 mm_session_t *session = (mm_session_t*) data;
647
648 session->oxine->reentry = NULL;
649 session->oxine->reentry_data = NULL;
650
651 playlist_cb(session->oxine);
652
653 }
654
mediamarks_freeall(void * data)655 static void mediamarks_freeall(void *data) {
656
657 mm_session_t *session = (mm_session_t *) data;
658 list_t *current = list_first_content(session->backpath);
659
660 current = list_first_content(session->backpath);
661
662 while(current) {
663 free_subtree(current, 1);
664 list_delete_current(session->backpath);
665 current = list_first_content(session->backpath);
666 }
667
668 list_free(session->backpath);
669 }
670
mediamarks_leave_cb(void * data)671 static void mediamarks_leave_cb (void *data) {
672
673 mm_session_t *session = (mm_session_t*) data;
674
675 session->oxine->reentry = NULL;
676 session->oxine->reentry_data = NULL;
677
678 mediamarks_freeall(session);
679
680 session->oxine->main_menu_cb(session->oxine);
681 ho_free(session);
682 }
683
mediamarks_reentry_cb(void * data)684 static void mediamarks_reentry_cb (void *data) {
685
686 mm_session_t *session = (mm_session_t*) data;
687 oxine_t *oxine = session->oxine;
688 otk_widget_t *b, *list_window;
689
690 lock_job_mutex();
691 if (oxine->info_window && oxine->media_info_close_cb) {
692 oxine->media_info_close_cb(oxine);
693 }
694 unlock_job_mutex();
695
696 otk_clear(oxine->otk);
697
698 oxine->main_window = otk_window_new (oxine->otk, NULL, 20, 130, 225, 420);
699
700 otk_label_new (oxine->main_window, 108, 60, OTK_ALIGN_CENTER|OTK_ALIGN_BOTTOM, "Action:");
701 b = otk_selector_new (oxine->main_window, 10, 80, 195, 60, mm_action_strings, 2, action_changed, session);
702 otk_set_focus(b);
703 otk_selector_set(b, session->action);
704 otk_button_new (oxine->main_window, 10, 170, 195, 60, "Playlist", mediamarks_leavetoplaylist_cb, session);
705 set_ilabel(session);
706 otk_label_new (oxine->main_window, 108, 280, OTK_ALIGN_CENTER|OTK_ALIGN_BOTTOM, session->ilabel);
707 otk_button_new (oxine->main_window, 10, 340, 195, 60, "Back", mediamarks_leave_cb, session);
708
709 list_window = otk_window_new (oxine->otk, NULL, 245, 130, 535, 420);
710
711 session->list = otk_list_new(list_window, 10, 15, 523, 390, mediamarks_play_cb, session);
712 changelist(session->list, session->backpath);
713 otk_list_set_pos(session->list, session->listpos);
714
715 otk_draw_all(oxine->otk);
716 }
717
718
mediamarks_cb(void * data)719 void mediamarks_cb (void *data) {
720
721 oxine_t *oxine = (oxine_t*) data;
722 char mmpath[XITK_NAME_MAX];
723 mm_session_t *session;
724 list_t *items = list_new();
725
726 session = ho_new(mm_session_t);
727 session->oxine = oxine;
728 session->backpath = list_new();
729 session->action = 1;
730 session->listpos = 0;
731
732 memset(mmpath,0,sizeof(mmpath));
733 snprintf(mmpath,sizeof(mmpath),"%s/.xine/oxine/mediamarks", xine_get_homedir());
734 if (!read_mediamarks(items, mmpath)) {
735 lprintf("trying to load system wide mediamarks\n");
736 snprintf(mmpath,sizeof(mmpath),"%s/mediamarks", XINE_OXINEDIR);
737 if (read_mediamarks(items, mmpath)) {
738 bpush(session->backpath, items);
739 oxine->reentry_data = session;
740 oxine->reentry = mediamarks_reentry_cb;
741 mediamarks_reentry_cb(session);
742 } else {
743 list_free(items);
744 list_free(session->backpath);
745 ho_free(session);
746 }
747 } else {
748 bpush(session->backpath, items);
749 oxine->reentry_data = session;
750 oxine->reentry = mediamarks_reentry_cb;
751 mediamarks_reentry_cb(session);
752 }
753 }
754