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