#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #define MESSAGE_EXPIRE 15 #define SECUSEC 1000000ULL #include "config.h" #include "player.h" #include "blocker.h" #include "resolver.h" #include "stdioutil.h" #include "scm-rights.h" #include "stringgetter.h" #include "cdif.h" static const char *dev_null_path = "/dev/null"; static const char *savefile_v1_magic = "$MYLfo`>ccRw6B% +Zul v1."; static const char *savefile_name = ".cdif-save"; typedef enum { TGT_LEAF = 1, TGT_GROUP, } TGT; typedef enum { MTW_TIME = 1, MTW_SPACE, MTW_MANUAL, } MTW; typedef enum { EP_ABORT = 1, EP_DONE, } EPSTAT; typedef struct childclose CHILDCLOSE; typedef struct cdlist CDLIST; typedef struct cd CD; typedef struct cdinfo CDINFO; typedef struct track TRACK; typedef struct tg TG; typedef struct sethost SETHOST; typedef struct connip CONNIP; typedef struct msg MSG; typedef struct msgtconc MSGTCONC; typedef struct tglist TGLIST; typedef struct cdvstk CDVSTK; typedef struct cdd CDD; typedef struct dlist DLIST; typedef struct dlops DLOPS; typedef struct tgdisp TGDISP; typedef struct tgdstk TGDSTK; typedef struct plystk PLYSTK; typedef struct ple PLE; struct dlops { void (*init)(void); int (*n)(void); const char *(*name)(void); const char *(*string)(int); int (*wt)(void); int (*cur)(void); char (*mark)(int); int (*nmark)(void); void (*setwt)(int); void (*setcur)(int); void (*setmark)(int, char); void (*markall)(void); void (*reorder)(int *); void (*subset)(int, int *); void (*list_save)(int (*)(int)); void (*list_fetch)(void); void (*list_clear)(void); int (*list_n)(void); void (*stackpop)(void); int (*stackdepth)(void); int (*keystroke)(unsigned char); int (*longcmd)(char *, int, char *, int); void (*help)(void); void (*longhelp)(void); } ; #define DLO_INIT(name) { \ &dl_##name##_init, \ &dl_##name##_n, \ &dl_##name##_name, \ &dl_##name##_string, \ &dl_##name##_wt, \ &dl_##name##_cur, \ &dl_##name##_mark, \ &dl_##name##_nmark, \ &dl_##name##_setwt, \ &dl_##name##_setcur, \ &dl_##name##_setmark, \ &dl_##name##_markall, \ &dl_##name##_reorder, \ &dl_##name##_subset, \ &dl_##name##_list_save, \ &dl_##name##_list_fetch, \ &dl_##name##_list_clear, \ &dl_##name##_list_n, \ &dl_##name##_stackpop, \ &dl_##name##_stackdepth, \ &dl_##name##_keystroke, \ &dl_##name##_longcmd, \ &dl_##name##_help, \ &dl_##name##_longhelp \ } struct plystk { PLYSTK *link; int n; int a; PLE **v; char *mv; int nm; int wt; int cur; } ; struct ple { CD *cd; TG *tg; char *text; } ; struct tgdstk { TGDSTK *link; int dn; TGDISP **dv; int nm; int wintop; int cur; } ; struct tgdisp { unsigned int flags; #define TGDF_EXPANDED 0x00000001 #define TGDF_MARK 0x00000002 int depth; CD *cd; TG *tg; char *text; } ; struct cdvstk { CDVSTK *link; int dn; CDD **dv; int nm; int wintop; int cur; } ; struct cdd { CD *cd; char mark; } ; struct msgtconc { int n; MSG *head; MSG **tail; } ; struct msg { MSG *link; char *text; MTW when; union { unsigned long long int time; int dead; } ; } ; struct connip { int bid; int pid; int fd; struct addrinfo *ailist; struct sockaddr *curaddr; int curaddrlen; void (*onsuccess)(int, void *); void (*onfail)(void *); void *cookie; } ; struct sethost { unsigned int flags; #define SHF_DEAD 0x00000001 #define SHF_BUSY 0x00000002 #define SHF_ATEM 0x00000004 int bid; int pid; int kid; int fd; AIO_OQ oq; char *host; void (*success)(void); void (*failure)(void); DEBLOCKER *db; struct addrinfo **aip; struct addrinfo *ai_list; struct addrinfo *ai_info; struct addrinfo *ai_data; void (*nextstep)(SETHOST *); STRINGGETTER *sg; CDLIST *newlist; CDLIST **newlistt; CDLIST *nextcd; } ; struct track { int num; int start; int length; } ; struct tg { char *text; TGT type; TG *parent; union { TRACK *leaf; struct { int n; TG **v; } group; } ; } ; struct tglist { TGLIST *link; TG *tg; } ; struct cdinfo { int ntracks; TRACK *tracks; int minstart; } ; struct cd { char *text; CDINFO *info; TG *trkroot; unsigned int flags; #define CDF_EXPAND 0x00000001 } ; struct cdlist { CDLIST *link; CD *cd; } ; struct childclose { CHILDCLOSE *link; int fd; } ; static int devnull; static CHILDCLOSE *childcloses; static CDLIST *allcds; static char *storage_host; static int scroll_id; static MSGTCONC scroll_msgs; static MSGTCONC timed_msgs; static MSGTCONC space_msgs; static MSGTCONC manual_msgs; static unsigned int want_update; #define WU_UPDATE 0x00000001 #define WU_CLEAR 0x00000002 static int update_id; static struct addrinfo *hostai_list; static struct addrinfo *hostai_info; static struct addrinfo *hostai_data; static int editing_line; /* * These state variables are a little complicated. * * line_b0 is the editing buffer. This is the variable used to malloc * or realloc the buffer. The prompt fills an initial part of this. * * line_b1 points into the editing buffer just after the prompt; that * is, it points to the mutable part, the part the editor edits. * * line_a is the amount of space line_b0 points to; that is, this * includes both the prompt and the mutable part. * * line_n is the length of just the mutable part. * * line_o is the display offset. Since the prompt is displayed, this * can be thought of as an offset into line_b0. * * line_c is the cursor offset. This is relative to line_b1. * * line_pl is the length of the prompt; that is, line_b1 always equals * line_b0 plus line_pl. */ static char *line_b0; static char *line_b1; static int line_a; static int line_n; static int line_o; static int line_c; static int line_pl; static int (*line_keystroke)(char); static void (*onekey_fn)(int); #define ONEKEY_ADDPROMPT (-1) static void (*postline)(EPSTAT); static int stdin_id; static SETHOST *hostrunning; static CDVSTK *cdvstack; static CDVSTK cdvlist; static TGDSTK *tgdstack; static TGDSTK tgdlist; static PLYSTK *plystack; static PLYSTK plylist; static void (*cur_in_window)(int); static int last_cd_space; static char *lastsearch; static int lastsearchlen; static int lastsearchdir; static DLOPS dlo_cds; /* forward */ static DLOPS dlo_tgs; /* forward */ static DLOPS dlo_ply; /* forward */ static DLOPS *dlo; static char filterflags; #define FF_REGEX 0x00000001 #define FF_COMPLEMENT 0x00000002 static AIO_OQ play_oq; static int play_io_id; static int audio_open; static int (*process_player_input)(const void *, int); static int ppi_nb; static int ppi_errno; static int ppi_status; static FILE *ppi_f; static char *ppi_s; static int ppi_l; static const char *ppi_what; static int paused; static PLYSTK playlist; static char *playlist_lastplay; static char *playlist_shortstatus; static int have_playlist_msgs; static void *playlist_prev_msg; static void *playlist_text_msg; static void *playlist_status_msg; static void *playlist_next_msg; static int playlist_last_m; static int playlist_last_s; static int playlist_last_sw; static char *stars; static char *dashes; static int dashlen; static int ignore_done_to_flush; static volatile sig_atomic_t got_fatal_sig; static int signal_id; static int loadfile_id; int write_logs = 0; #if 0 #define Q4(n) (n), (n)+1, (n)+2, (n)+3 #define Q16(n) Q4(n), Q4((n)+4), Q4((n)+8), Q4((n)+12) #define Q64(n) Q16(n), Q16((n)+16), Q16((n)+32), Q16((n)+48) static const unsigned char equiv_nil[256] = { Q64(0), Q64(64), Q64(128), Q64(192) }; #undef Q4 #undef Q16 #undef Q64 #endif static const unsigned char equiv_casefold[256] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* ^@ - ^G */ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, /* ^H - ^O */ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, /* ^P - ^W */ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, /* ^X - ^_ */ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, /* sp - ' */ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, /* ( - / */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, /* 0 - 7 */ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, /* 8 - ? */ 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, /* @ - G */ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, /* H - O */ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, /* P - W */ 0x78, 0x79, 0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, /* X - _ */ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, /* ` - g */ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, /* h - o */ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, /* p - w */ 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, /* x - DEL */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, /* 0x80 - ESA */ 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, /* HTS - SS3 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, /* DCS - EPA */ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, /* 0x98 - APC */ 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, /* nbsp - § */ 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, /* ¨ - ¯ */ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, /* ° - · */ 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, /* ¹ - ¿ */ 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, /* À - Ç */ 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, /* È - Ï */ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xd7, /* Ð - × */ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xdf, /* Ø - ß */ 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, /* à - ç */ 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, /* è - ï */ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, /* ð - ÷ */ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, /* ø - ÿ */ }; #define Cisspace(x) isspace((unsigned char)(x)) static void dlog(const char *, ...) __attribute__((__format__(__printf__,1,2),__used__)); static void dlog(const char *fmt, ...) { va_list ap; char *s; int sn; char *ts; int tsn; static int fd = -2; struct timeval tv; time_t tt; struct tm *tm; struct iovec iov[2]; if (! write_logs) return; if (fd == -2) fd = open("cdif.log",O_TRUNC|O_WRONLY|O_APPEND|O_CREAT,0666); if (fd < 0) return; gettimeofday(&tv,0); tt = tv.tv_sec; tm = localtime(&tt); tsn = asprintf( &ts,"%04d-%02d-%02d %02d:%02d:%02d.%06d ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, (int)tv.tv_usec ); va_start(ap,fmt); sn = vasprintf(&s,fmt,ap); va_end(ap); iov[0].iov_base = ts; iov[0].iov_len = tsn; iov[1].iov_base = s; iov[1].iov_len = sn; writev(fd,&iov[0],2); free(ts); free(s); } static unsigned int stoui_max(const char *s, const char **ep, int maxlen) { int o; unsigned int v; unsigned int dv; v = 0; o = 0; while <"loop"> (o < maxlen) { switch (s[o]) { case '0': dv = 0; if (0) { case '1': dv = 1; } if (0) { case '2': dv = 2; } if (0) { case '3': dv = 3; } if (0) { case '4': dv = 4; } if (0) { case '5': dv = 5; } if (0) { case '6': dv = 6; } if (0) { case '7': dv = 7; } if (0) { case '8': dv = 8; } if (0) { case '9': dv = 9; } v = (v * 10) + dv; o ++; break; default: break <"loop">; } } *ep = s + o; return(v); } void panic(void) __attribute__((__used__,__noreturn__)); void panic(void) { abort(); } static void open_dev_null(void) { devnull = open(dev_null_path,O_RDWR,0); if (devnull < 0) { fprintf(stderr,"%s: open %s: %s\n",__progname,dev_null_path,strerror(errno)); exit(1); } } static void add_childclose(int fd) { CHILDCLOSE *cc; cc = malloc(sizeof(CHILDCLOSE)); cc->fd = fd; cc->link = childcloses; childcloses = cc; } static void close_childcloses(void) { CHILDCLOSE *cc; while (childcloses) { cc = childcloses; childcloses = cc->link; close(cc->fd); free(cc); } } #define AS_NRVS 8 static char *addr_string(const struct sockaddr *sa, int salen) { char host[NI_MAXHOST]; char serv[NI_MAXSERV]; int e; static char *rvs[AS_NRVS] = { 0 }; static int rvhand = 0; char *rv; e = getnameinfo(sa,salen,&host[0],NI_MAXHOST,&serv[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV); if (e) { asprintf(&rv,"[getnameinfo (af %d) failed: %s]",sa->sa_family,gai_strerror(e)); } else { asprintf(&rv,"%s/%s",&host[0],&serv[0]); } free(rvs[rvhand]); rvs[rvhand] = rv; rvhand = (rvhand ? : AS_NRVS) - 1; return(rv); } void set_nonblock(int fd) { fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); } static void fork_player(void) { pid_t kid; int sp[2]; int dgp[2]; if (socketpair(AF_LOCAL,SOCK_STREAM,0,&sp[0]) < 0) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } if (socketpair(AF_LOCAL,SOCK_DGRAM,0,&dgp[0]) < 0) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } fflush(0); kid = fork(); if (kid == 0) { close_childcloses(); close(sp[0]); close(dgp[0]); playerfd = sp[1]; playerdg = dgp[1]; if (devnull != 0) { dup2(devnull,0); close(devnull); devnull = 0; } dup2(0,1); player_main(); _exit(1); } close(sp[1]); close(dgp[1]); playerfd = sp[0]; playerdg = dgp[0]; set_nonblock(playerfd); set_nonblock(playerdg); add_childclose(playerfd); add_childclose(playerdg); } static void fork_resolver(void) { pid_t kid; int p[2]; if (socketpair(AF_LOCAL,SOCK_STREAM,0,&p[0]) < 0) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } fflush(0); kid = fork(); if (kid == 0) { close_childcloses(); close(p[0]); resolverfd = p[1]; if (devnull != 0) { dup2(devnull,0); close(devnull); devnull = 0; } dup2(0,1); resolver_main(); _exit(1); } close(p[1]); resolverfd = p[0]; set_nonblock(resolverfd); add_childclose(resolverfd); } static void msg_free(MSG *m) { free(m->text); free(m); } static void msgtconc_init(MSGTCONC *tc) { tc->n = 0; tc->head = 0; tc->tail = &tc->head; } static void msgtconc_append(MSGTCONC *tc, MSG *m) { m->link = 0; *tc->tail = m; tc->tail = &m->link; tc->n ++; } static MSG *msgtconc_remove(MSGTCONC *tc) { MSG *m; m = tc->head; if (m) { tc->head = m->link; if (! tc->head) tc->tail = &tc->head; tc->n --; } return(m); } static PLE *ple_make(CD *cd, TG *tg) { PLE *e; e = malloc(sizeof(PLE)); e->cd = cd; e->tg = tg; if (tg && cd) { asprintf(&e->text,"%s [%s]",tg->text,cd->text); } else { e->text = strdup(""); } return(e); } static PLE *ple_copy(PLE *old) { return(ple_make(old->cd,old->tg)); } static void playlist_init(PLYSTK *pl) { pl->n = 0; pl->a = 0; pl->v = 0; } static void plystk_append_ple(PLYSTK *s, PLE *e) { if (s->n >= s->a) { s->v = realloc(s->v,(s->a=s->n+8)*sizeof(*s->v)); s->mv = realloc(s->mv,s->a); } s->v[s->n] = e; s->mv[s->n] = 0; s->n ++; } static void playlist_append(PLYSTK *pl, CD *cd, TG *tg) { if (pl != &playlist) panic(); plystk_append_ple(pl,ple_make(cd,tg)); } static void playlist_var_delete(int *vp, int i) { int v; v = *vp; if (v <= 3) return; if (i+2 < v) *vp = v - 1; } static void playlist_remove_ple(PLYSTK *s, PLE *e) { int i; for (i=s->n-1;i>=0;i--) { if (s->v[i] == e) { s->n --; if (s->mv[i]) s->nm --; playlist_var_delete(&s->wt,i); playlist_var_delete(&s->cur,i); if (i < s->n) { bcopy(s->v+i+1,s->v+i,(s->n-i)*sizeof(*s->v)); bcopy(s->mv+i+1,s->mv+i,s->n-i); } if (s->wt >= s->n) s->wt = s->n - 1; if (s->cur >= s->n) s->cur = s->n - 1; } } } static PLE *playlist_remove(PLYSTK *pl) { PLE *e; PLYSTK *s; if (pl != &playlist) panic(); if (pl->n < 1) return(0); e = pl->v[0]; playlist_remove_ple(&playlist,e); playlist_remove_ple(&plylist,e); for (s=plystack;s;s=s->link) playlist_remove_ple(s,e); return(e); } static void ple_free(PLE *e) { free(e->text); free(e); } static void tg_free(TG *tg) { int i; if (! tg) return; free(tg->text); switch (tg->type) { default: panic(); break; case TGT_LEAF: break; case TGT_GROUP: for (i=tg->group.n-1;i>=0;i--) tg_free(tg->group.v[i]); free(tg->group.v); break; } free(tg); } static TG *tg_new(TGT type, TG *parent) { TG *tg; tg = malloc(sizeof(TG)); tg->text = 0; tg->type = type; tg->parent = parent; switch (type) { case TGT_LEAF: tg->leaf = 0; break; case TGT_GROUP: tg->group.n = 0; tg->group.v = 0; break; default: panic(); break; } return(tg); } static void cdinfo_free(CDINFO *inf) { if (! inf) return; free(inf->tracks); free(inf); } static void cd_free(CD *cd) { free(cd->text); cdinfo_free(cd->info); tg_free(cd->trkroot); free(cd); } static void cdlist_free_list(CDLIST *list) { CDLIST *l; while (list) { l = list; list = l->link; cd_free(l->cd); free(l); } } static void free_my_addrinfo_list(struct addrinfo *list) { struct addrinfo *ai; while (list) { ai = list; list = ai->ai_next; free(ai->ai_addr); free(ai); } } static int kill_sethost(void *shv) { SETHOST *sh; sh = shv; if (sh->flags & SHF_BUSY) return(AIO_BLOCK_NIL); if (sh->bid != AIO_PL_NOID) aio_remove_block(sh->bid); if (sh->pid != AIO_PL_NOID) aio_remove_poll(sh->pid); aio_remove_block(sh->kid); if (sh->fd >= 0) close(sh->fd); aio_oq_flush(&sh->oq); if (sh->db) deblocker_done(sh->db); if (sh->sg) sg_done(sh->sg); free(sh->host); free_my_addrinfo_list(sh->ai_list); free_my_addrinfo_list(sh->ai_info); free_my_addrinfo_list(sh->ai_data); *sh->newlistt = 0; cdlist_free_list(sh->newlist); free(sh); return(AIO_BLOCK_LOOP); } static void scroll_timed(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void scroll_timed(const char *fmt, ...) { va_list ap; char *s; MSG *m; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); m = malloc(sizeof(MSG)); m->text = s; m->when = MTW_TIME; dlog("scroll_timed: %s\n",s); msgtconc_append(&scroll_msgs,m); } static void scroll_space_string(char *s) { MSG *m; m = malloc(sizeof(MSG)); m->text = s; m->when = MTW_SPACE; dlog("scroll_space: %s\n",s); msgtconc_append(&scroll_msgs,m); } static void scroll_space(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void scroll_space(const char *fmt, ...) { va_list ap; char *s; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); scroll_space_string(s); } static void *scroll_manual(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void *scroll_manual(const char *fmt, ...) { va_list ap; char *s; MSG *m; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); m = malloc(sizeof(MSG)); m->text = s; m->when = MTW_MANUAL; dlog("scroll_manual: %s\n",s); msgtconc_append(&scroll_msgs,m); return(m); } static void manual_msg_change(void *, const char *, ...) __attribute__((__format__(__printf__,2,3))); static void manual_msg_change(void *mv, const char *fmt, ...) { va_list ap; char *s; MSG *m; m = mv; if (m->when != MTW_MANUAL) panic(); va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); free(m->text); m->text = s; want_update |= WU_UPDATE; } static const char *manual_msg_text(void *mv) { MSG *m; m = mv; if (m->when != MTW_MANUAL) panic(); return(m->text); } static void manual_msg_done(void *mv) { MSG *m; m = mv; if (m->when != MTW_MANUAL) panic(); m->dead = 1; } static void merge_list(int *l1, int n1, int *l2, int n2, int *to, int (*lt)(int, int)) { while (1) { if (n1 && (!n2 || (*lt)(l1[0],l2[0]))) { *to++ = *l1++; n1 --; } else if (n2) { *to++ = *l2++; n2 --; } else { return; } } } static void sort_list(int *from, int n, int *to, int *tmp, int (*lt)(int, int)) { int n1; int n2; if (n < 1) panic(); if (n < 2) { to[0] = from[0]; return; } n1 = n / 2; n2 = n - n1; sort_list(from,n1,tmp,to,lt); sort_list(from+n1,n2,tmp+n1,to,lt); merge_list(tmp,n1,tmp+n1,n2,to,lt); } static void sethost_shutdown(SETHOST *sh) { if (sh->flags & SHF_DEAD) return; sh->flags |= SHF_DEAD; if (sh->pid != AIO_PL_NOID) { aio_remove_poll(sh->pid); sh->pid = AIO_PL_NOID; } if (sh->bid != AIO_PL_NOID) { aio_remove_block(sh->bid); sh->bid = AIO_PL_NOID; } sh->kid = aio_add_block(&kill_sethost,sh); if (sh == hostrunning) hostrunning = 0; } static void sethost_fail_msg(SETHOST *sh, char *msg) { scroll_space_string(msg); sethost_shutdown(sh); (*sh->failure)(); } static void sethost_fail(SETHOST *, const char *, ...) __attribute__((__format__(__printf__,2,3))); static void sethost_fail(SETHOST *sh, const char *fmt, ...) { va_list ap; char *s; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); sethost_fail_msg(sh,s); } static int sethost_wtest(void *vsh) { SETHOST *sh; sh = vsh; return((sh->flags & SHF_DEAD) ? 0 : aio_oq_nonempty(&sh->oq)); } static void sh_wr_resolve(void *vsh) { SETHOST *sh; int w; sh = vsh; w = aio_oq_writev(&sh->oq,resolverfd,0); if (w < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } sethost_fail(sh,"resolver write error: %s",strerror(errno)); return; } if (w == 0) return; aio_oq_dropdata(&sh->oq,w); } static void sh_rd_resolve(void *vsh) { SETHOST *sh; char rbuf[512]; int r; sh = vsh; r = read(resolverfd,&rbuf[0],sizeof(rbuf)); if (r < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } sh->flags &= ~SHF_BUSY; sethost_fail(sh,"resolver read error: %s",strerror(errno)); return; } if (r == 0) { sh->flags &= ~SHF_BUSY; sethost_fail(sh,"resolver read EOF"); return; } deblocker_push(sh->db,&rbuf[0],r); } static void sethost_resolve(SETHOST *sh, int port, struct addrinfo **aip, void (*nextstep)(SETHOST *)) { if (sh->flags & SHF_DEAD) return; *aip = 0; sh->aip = aip; aio_oq_queue_point(&sh->oq,sh->host,strlen(sh->host)+1); aio_oq_queue_printf(&sh->oq,"%d%c",port,'\0'); sh->pid = aio_add_poll(resolverfd,&aio_rwtest_always,&sethost_wtest,&sh_rd_resolve,&sh_wr_resolve,sh); sh->nextstep = nextstep; sh->flags |= SHF_BUSY; } static void sethost_resolve_error(void *vsh, const char *fmt, ...) { va_list ap; char *s; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); sethost_fail_msg(vsh,s); } static int connect_free(void *cv) { CONNIP *c; c = cv; aio_remove_block(c->bid); c->bid = AIO_PL_NOID; free(c); return(AIO_BLOCK_LOOP); } static void connect_success(CONNIP *c) { (*c->onsuccess)(c->fd,c->cookie); c->bid = aio_add_block(&connect_free,c); } static int connect_try_next(void *); /* forward */ static void connect_get_status(void *cv) { CONNIP *c; int err; socklen_t errlen; c = cv; aio_remove_poll(c->pid); c->pid = AIO_PL_NOID; errlen = sizeof(err); if (getsockopt(c->fd,SOL_SOCKET,SO_ERROR,&err,&errlen) < 0) { err = errno; scroll_space("%s getsockopt SO_ERROR: %s",addr_string(c->curaddr,c->curaddrlen),strerror(err)); } else if (err != 0) { scroll_space("%s connect: %s",addr_string(c->curaddr,c->curaddrlen),strerror(err)); } else { connect_success(c); return; } c->bid = aio_add_block(&connect_try_next,c); } static int connect_try_next(void *cv) { CONNIP *c; int s; struct addrinfo *ai; c = cv; while (1) { ai = c->ailist; if (! ai) { (*c->onfail)(c->cookie); aio_remove_block(c->bid); c->bid = aio_add_block(&connect_free,c); return(AIO_BLOCK_LOOP); } c->ailist = ai->ai_next; c->curaddr = ai->ai_addr; c->curaddrlen = ai->ai_addrlen; s = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (s < 0) { scroll_space("%s socket: %s",addr_string(c->curaddr,c->curaddrlen),strerror(errno)); continue; } set_nonblock(s); if (connect(s,ai->ai_addr,ai->ai_addrlen) < 0) { if (errno == EINPROGRESS) { aio_remove_block(c->bid); c->bid = AIO_PL_NOID; c->pid = aio_add_poll(s,&aio_rwtest_never,&aio_rwtest_always,0,&connect_get_status,c); c->fd = s; return(AIO_BLOCK_LOOP); } scroll_space("%s connect: %s",addr_string(c->curaddr,c->curaddrlen),strerror(errno)); close(s); } else { aio_remove_block(c->bid); c->bid = AIO_PL_NOID; c->fd = s; connect_success(c); return(AIO_BLOCK_LOOP); } } } static void start_connect(struct addrinfo *ailist, void (*succ)(int, void *), void (*fail)(void *), void *arg) { CONNIP *c; c = malloc(sizeof(CONNIP)); c->bid = AIO_PL_NOID; c->pid = AIO_PL_NOID; c->fd = -1; c->ailist = ailist; c->onsuccess = succ; c->onfail = fail; c->cookie = arg; c->bid = aio_add_block(&connect_try_next,c); } static void sethost_connfail_info(void *shv) { SETHOST *sh; sh = shv; sh->flags &= ~SHF_BUSY; sethost_fail(sh,"CD info connection failed"); } static void sethost_queue_info(SETHOST *sh) { CDLIST *l; for (l=sh->nextcd;l;l=l->link) { aio_oq_queue_point(&sh->oq,l->cd->text,strlen(l->cd->text)+1); } aio_oq_queue_special(&sh->oq,0,0); } static void sethost_wr(void *shv) { SETHOST *sh; int w; int special(void *v __attribute__((__unused__)), int i __attribute__((__unused__))) { shutdown(sh->fd,SHUT_WR); dlog("sent CD info fetch list\n"); return(0); } sh = shv; if (sh->flags & SHF_DEAD) return; w = aio_oq_writev(&sh->oq,sh->fd,&special); if (w < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } sethost_fail(sh,"network write error: %s",strerror(errno)); return; } if (w == 0) return; aio_oq_dropdata(&sh->oq,w); } static int sethost_windup(void *shv) { SETHOST *sh; sh = shv; if (sh->flags & SHF_DEAD) panic(); free(storage_host); storage_host = strdup(sh->host); free_my_addrinfo_list(hostai_list); hostai_list = sh->ai_list; sh->ai_list = 0; free_my_addrinfo_list(hostai_info); hostai_info = sh->ai_info; sh->ai_info = 0; free_my_addrinfo_list(hostai_data); hostai_data = sh->ai_data; sh->ai_data = 0; cdlist_free_list(allcds); allcds = sh->newlist; sh->newlist = 0; sethost_shutdown(sh); (*sh->success)(); return(AIO_BLOCK_LOOP); } static void sethost_rd_cd_info(void *shv) { SETHOST *sh; char rbuf[512]; int r; sh = shv; if (sh->flags & SHF_DEAD) return; r = read(sh->fd,&rbuf[0],sizeof(rbuf)); if (r < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } sethost_fail(sh,"network read error: %s",strerror(errno)); return; } if (r == 0) { if (! (sh->flags & SHF_ATEM)) { sethost_fail(sh,"network read premature EOF"); return; } dlog("CD info fetch got EOF\n"); aio_remove_poll(sh->pid); sh->pid = AIO_PL_NOID; sh->bid = aio_add_block(&sethost_windup,sh); return; } dlog("sethost_rd_cd_info: deblocker_push %d\n",r); deblocker_push(sh->db,&rbuf[0],r); } static void sethost_cd_info_errmsg(const void *, int, void *); /* forward */ static int sethost_lastline_end(STRINGGOT *s) { return(s->len < 0); } static int sethost_endgroup_end(STRINGGOT *s) { return((s->len == 1) && (s->str[0] == ')')); } static char *sethost_read_group(CD *cd, TG *parent, STRINGGETTER *sg, int (*end)(STRINGGOT *)) { __label__ failthrow; STRINGGOT g; TG *new; unsigned int uiv; const char *ep; int so; int i; char *msg; void gfail(const char *s) { asprintf(&msg,"%s [%.*s]",s,g.len,g.str); goto failthrow; } if (parent->type != TGT_GROUP) panic(); dlog("sethost_read_group: %p for %s\n",(void *)parent,cd->text); while (1) { g = sg_get(sg); if (g.len < 0) dlog("sethost_read_group: got EOF\n"); else dlog("sethost_read_group: line %.*s\n",g.len,g.str); if ((*end)(&g)) { dlog("sethost_read_group: group end, returning\n"); return(0); } if (g.len < 0) return(strdup("invalid text info: premature EOF")); if (g.len < 2) return(strdup("invalid text info: empty line")); if ((g.str[0] == '(') && (g.str[1] == ' ')) { so = 2; new = tg_new(TGT_GROUP,parent); dlog("sethost_read_group: open subgroup\n"); } else { uiv = stoui_max(g.str,&ep,g.len); if (ep == g.str) gfail("invalid text info: bad/missing track number"); if (ep == g.str+g.len) gfail("invalid text info: missing track number terminator"); if (*ep != ' ') gfail("invalid text info: bad track number terminator"); i = uiv; if ((i < 0) || (i != uiv)) gfail("invalid text info: track number out of range"); so = (ep+1) - g.str; new = tg_new(TGT_LEAF,parent); dlog("sethost_read_group: leaf\n"); } new->text = malloc((g.len-so)+1); bcopy(g.str+so,new->text,g.len-so); new->text[g.len-so] = '\0'; parent->group.v = realloc(parent->group.v,(parent->group.n+1)*sizeof(*parent->group.v)); parent->group.v[parent->group.n++] = new; dlog("sethost_read_group: text %s\n",new->text); switch (new->type) { default: panic(); break; case TGT_LEAF: for (i=cd->info->ntracks-1;(i>=0)&&(cd->info->tracks[i].num!=uiv);i--) ; if (i < 0) gfail("invalid text info: nonexistent track number"); new->leaf = &cd->info->tracks[i]; break; case TGT_GROUP: msg = sethost_read_group(cd,new,sg,&sethost_endgroup_end); if (msg) return(msg); break; } } panic(); failthrow:; return(msg); } static void synthesize_cd_info_text(CD *cd) { CDINFO *inf; TG *tg; TG *tg2; int i; int *v1; int *v2; int *v3; int lt(int x1, int x2) { return(inf->tracks[x1].starttracks[x2].start); } inf = cd->info; tg = tg_new(TGT_GROUP,0); tg->text = 0; tg->group.n = inf->ntracks; tg->group.v = malloc(tg->group.n*sizeof(*tg->group.v)); v1 = malloc(tg->group.n*3*sizeof(int)); v2 = v1 + tg->group.n; v3 = v2 + tg->group.n; for (i=tg->group.n-1;i>=0;i--) { tg->group.v[i] = 0; v1[i] = i; } sort_list(v1,tg->group.n,v2,v3,<); for (i=tg->group.n-1;i>=0;i--) { tg2 = tg_new(TGT_LEAF,tg); asprintf(&tg2->text,"#%d",inf->tracks[v2[i]].num); tg2->leaf = &inf->tracks[v2[i]]; tg->group.v[i] = tg2; } cd->trkroot = tg; free(v1); } static void sethost_cd_info_text(const void *data, int len, void *shv) { SETHOST *sh; STRINGGETTER *sg; int o; CD *cd; static char *msg = 0; int get(void *arg __attribute__((__unused__))) { if (o >= len) return(EOF); return(((const unsigned char *)data)[o++]); } sh = shv; if (sh->flags & SHF_DEAD) return; dlog("sethost_cd_info_text: len %d\n",len); if (len < 1) { synthesize_cd_info_text(sh->nextcd->cd); } else { cd = sh->nextcd->cd; cd->trkroot = tg_new(TGT_GROUP,0); o = 0; sg = sg_init_pull(&get,&sg_term_nul,0); free(msg); msg = sethost_read_group(cd,cd->trkroot,sg,&sethost_lastline_end); if (msg) { sg_done(sg); tg_free(cd->trkroot); cd->trkroot = 0; sethost_fail(sh,"at %d in %s: %s",o,cd->text,msg); return; } sg_done(sg); } deblocker_set_string(sh->db,&sethost_cd_info_errmsg); sh->flags |= SHF_ATEM; sh->nextcd = sh->nextcd->link; if (sh->nextcd) dlog("advanced nextcd to %s\n",sh->nextcd->cd->text); else dlog("nextcd now at end-of-list\n"); } static void sethost_cd_info_tracking(const void *data, int len, void *shv) { __label__ throw; SETHOST *sh; const char *dp; int o; char *ep; TRACK t; CDINFO *i; int j; int val(void) { unsigned long int v; int iv; v = strtoul(dp+o,&ep,10); if (ep == dp+o) { sethost_fail(sh,"invalid tracking info: missing/invalid number"); goto throw; } iv = v; if ((iv < 0) || (iv != v)) { sethost_fail(sh,"invalid tracking info: number too large"); goto throw; } return(iv); } sh = shv; dlog("tracking info, length %d\n",len); i = malloc(sizeof(CDINFO)); sh->nextcd->cd->info = i; i->ntracks = 0; i->tracks = 0; dp = data; if (len < 1) { sethost_fail(sh,"invalid tracking info: impossibly short"); return; } if (dp[len-1]) { sethost_fail(sh,"invalid tracking info: last terminator wrong"); return; } o = 0; while (o < len) { t.num = val(); if (*ep != ' ') { sethost_fail(sh,"invalid tracking info: incorrect terminator"); return; } o = (ep - dp) + 1; t.start = val(); if (*ep != ' ') { sethost_fail(sh,"invalid tracking info: incorrect terminator"); return; } o = (ep - dp) + 1; t.length = val(); if (*ep != '\0') { sethost_fail(sh,"invalid tracking info: incorrect terminator"); return; } o = (ep - dp) + 1; i->tracks = realloc(i->tracks,(i->ntracks+1)*sizeof(*i->tracks)); i->tracks[i->ntracks++] = t; } i->minstart = i->tracks[0].start; for (j=i->ntracks-1;j>0;j--) if (i->tracks[j].start < i->minstart) i->minstart = i->tracks[j].start; dlog("minstart for %s is %d\n",sh->nextcd->cd->text,i->minstart); deblocker_set_string(sh->db,&sethost_cd_info_text); throw:; } static void sethost_cd_info_errmsg(const void *data, int len, void *shv) { SETHOST *sh; sh = shv; if (sh->flags & SHF_DEAD) return; sh->flags &= ~SHF_ATEM; if (! sh->nextcd) { sethost_fail(sh,"too much returned data"); return; } if (len > 0) { sethost_fail(sh,"CD %s: %.*s",sh->nextcd->cd->text,len,(const char *)data); return; } dlog("no error, cd %s\n",sh->nextcd->cd->text); deblocker_set_string(sh->db,&sethost_cd_info_tracking); } static void sethost_cd_info_error(void *, const char *, ...) __attribute__((__format__(__printf__,2,3))); static void sethost_cd_info_error(void *shv, const char *fmt, ...) { va_list ap; char *s; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); sethost_fail_msg(shv,s); } static void sethost_get_cd_info(int fd, void *shv) { SETHOST *sh; sh = shv; sh->flags &= ~SHF_BUSY; if (sh->flags & SHF_DEAD) return; sh->fd = fd; sh->nextcd = sh->newlist; sethost_queue_info(sh); sh->db = deblocker_open(&sethost_cd_info_errmsg,&sethost_cd_info_error,sh); sh->flags |= SHF_ATEM; sh->pid = aio_add_poll(fd,&aio_rwtest_always,&sethost_wtest,&sethost_rd_cd_info,&sethost_wr,sh); } static void sethost_rd_cd_list(void *shv) { SETHOST *sh; unsigned char buf[512]; int r; int i; sh = shv; if (sh->flags & SHF_DEAD) return; r = read(sh->fd,&buf[0],sizeof(buf)); if (r < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } sethost_fail(sh,"CD list read error: %s",strerror(errno)); return; } if (r == 0) { sg_put(sh->sg,EOF); aio_remove_poll(sh->pid); sh->pid = AIO_PL_NOID; close(sh->fd); sh->fd = -1; aio_oq_flush(&sh->oq); *sh->newlistt = 0; { CDLIST *l; for (i=0,l=sh->newlist;l;l=l->link,i++) ; dlog("CD list end, count = %d\n",i); } dlog("starting CD info fetch\n"); sh->flags |= SHF_BUSY; start_connect(sh->ai_info,&sethost_get_cd_info,&sethost_connfail_info,sh); return; } for (i=0;isg,buf[i]); } static void sethost_got_cd(STRINGGOT s, void *shv) { SETHOST *sh; CDLIST *l; CD *cd; sh = shv; if (sh->flags & SHF_DEAD) return; if (s.len < 0) return; l = malloc(sizeof(CDLIST)); cd = malloc(sizeof(CD)); cd->text = malloc(s.len+1); bcopy(s.str,cd->text,s.len); cd->text[s.len] = '\0'; cd->info = 0; cd->trkroot = 0; l->cd = cd; *sh->newlistt = l; sh->newlistt = &l->link; } static void sethost_get_cd_list(int fd, void *shv) { SETHOST *sh; sh = shv; sh->flags &= ~SHF_BUSY; if (sh->flags & SHF_DEAD) return; sh->fd = fd; sh->sg = sg_init_push(&sethost_got_cd,&sg_term_nul,sh); sh->pid = aio_add_poll(fd,&aio_rwtest_always,&aio_rwtest_never,&sethost_rd_cd_list,0,sh); dlog("CD list connection succeeded\n"); } static void sethost_connfail_list(void *shv) { SETHOST *sh; sh = shv; sh->flags &= ~SHF_BUSY; if (sh->flags & SHF_DEAD) return; sethost_fail(sh,"CD list connection failed\n"); } static void sethost_fetch(SETHOST *sh) { if (sh->flags & SHF_DEAD) return; dlog("starting CD list fetch\n"); sh->flags |= SHF_BUSY; start_connect(sh->ai_list,&sethost_get_cd_list,&sethost_connfail_list,sh); } static void sethost_resolve_data(SETHOST *sh) { if (sh->flags & SHF_DEAD) return; dlog("resolving %s/%d\n",sh->host,PORT_DATA); sethost_resolve(sh,PORT_DATA,&sh->ai_data,&sethost_fetch); } static void sethost_resolve_info(SETHOST *sh) { if (sh->flags & SHF_DEAD) return; dlog("resolving %s/%d\n",sh->host,PORT_INFO); sethost_resolve(sh,PORT_INFO,&sh->ai_info,&sethost_resolve_data); } static void sethost_resolve_errmsg(const void *, int, void *); /* forward */ static void sethost_resolve_result(const void *data, int len, void *vsh) { SETHOST *sh; const unsigned char *dbuf; struct addrinfo *ai; sh = vsh; if (sh->flags & SHF_DEAD) return; if (len == 0) { deblocker_set_string(sh->db,&sethost_resolve_errmsg); aio_remove_poll(sh->pid); sh->pid = AIO_PL_NOID; dlog("resolver: end of list\n"); (*sh->nextstep)(sh); return; } dbuf = data; if (len < (3*sizeof(int))+sizeof(size_t)) { sh->flags &= ~SHF_BUSY; sethost_fail(sh,"resolver protocol error: length %d too small",len); return; } ai = malloc(sizeof(struct addrinfo)); bcopy(dbuf,&ai->ai_family,sizeof(int)); bcopy(dbuf+sizeof(int),&ai->ai_socktype,sizeof(int)); bcopy(dbuf+(2*sizeof(int)),&ai->ai_protocol,sizeof(int)); bcopy(dbuf+(3*sizeof(int)),&ai->ai_addrlen,sizeof(size_t)); if (len != (3*sizeof(int))+sizeof(size_t)+ai->ai_addrlen) { sh->flags &= ~SHF_BUSY; sethost_fail(sh,"resolver protocol error: length %d wrong",len); return; } ai->ai_addr = malloc(ai->ai_addrlen); bcopy(dbuf+(3*sizeof(int))+sizeof(size_t),ai->ai_addr,ai->ai_addrlen); ai->ai_next = *sh->aip; *sh->aip = ai; dlog("resolver: got %s\n",addr_string(ai->ai_addr,ai->ai_addrlen)); } static void sethost_resolve_errmsg(const void *data, int len, void *vsh) { SETHOST *sh; sh = vsh; if (len > 0) { sh->flags &= ~SHF_BUSY; sethost_fail(sh,"resolver failure: %.*s",len,(const char *)data); return; } dlog("resolver: no error\n"); deblocker_set_string(sh->db,&sethost_resolve_result); } static void switch_hosts(const char *host, void (*success)(void), void (*failure)(void)) { SETHOST *sh; if (hostrunning) panic(); if (loadfile_id != AIO_PL_NOID) { aio_remove_block(loadfile_id); loadfile_id = AIO_PL_NOID; } sh = malloc(sizeof(SETHOST)); sh->flags = 0; sh->bid = AIO_PL_NOID; sh->pid = AIO_PL_NOID; sh->kid = AIO_PL_NOID; sh->fd = -1; aio_oq_init(&sh->oq); sh->host = strdup(host); sh->success = success; sh->failure = failure; sh->db = deblocker_open(&sethost_resolve_errmsg,&sethost_resolve_error,sh); sh->sg = 0; sh->newlistt = &sh->newlist; dlog("resolving %s/%d\n",sh->host,PORT_LIST); sethost_resolve(sh,PORT_LIST,&sh->ai_list,&sethost_resolve_info); hostrunning = sh; } static void cdd_free(CDD *d) { free(d); } static void cdvstk_free(CDVSTK *s) { int i; for (i=s->dn-1;i>=0;i--) cdd_free(s->dv[i]); free(s->dv); free(s); } static void tgdisp_free(TGDISP *d) { free(d->text); free(d); } static void tgdstk_free(TGDSTK *s) { int i; for (i=s->dn-1;i>=0;i--) tgdisp_free(s->dv[i]); free(s->dv); free(s); } static void plystk_free(PLYSTK *s, int structtoo) { free(s->v); free(s->mv); if (structtoo) free(s); } static void cdvstack_clear(void) { CDVSTK *s; while (cdvstack) { s = cdvstack; cdvstack = s->link; cdvstk_free(s); } } static void cdvlist_clear(void) { int i; for (i=cdvlist.dn-1;i>=0;i--) cdd_free(cdvlist.dv[i]); free(cdvlist.dv); cdvlist.dv = 0; cdvlist.dn = 0; } static void tgdstack_clear(void) { TGDSTK *s; while (tgdstack) { s = tgdstack; tgdstack = s->link; tgdstk_free(s); } } static void tgdlist_clear(void) { int i; for (i=tgdlist.dn-1;i>=0;i--) tgdisp_free(tgdlist.dv[i]); free(tgdlist.dv); tgdlist.dv = 0; tgdlist.dn = 0; } static void plystack_clear(void) { PLYSTK *s; while (plystack) { s = plystack; plystack = s->link; plystk_free(s,1); } } static void playlist_clear(void) { int i; for (i=playlist.n-1;i>=0;i--) ple_free(playlist.v[i]); playlist.n = 0; } static CDVSTK *cdvstk_new(int n, CDD **v) { CDVSTK *s; s = malloc(sizeof(CDVSTK)); s->dn = n; s->dv = v ? : malloc(n*sizeof(*s->dv)); s->nm = 0; s->wintop = 0; s->cur = 0; return(s); } static CDD *cdd_new(CD *cd) { CDD *d; d = malloc(sizeof(CDD)); d->cd = cd; d->mark = 0; return(d); } static CDD *cdd_copy(CDD *old) { CDD *new; new = malloc(sizeof(CDD)); new->cd = old->cd; new->mark = 0; return(new); } static TGDSTK *tgdstk_new(int n, TGDISP **v) { TGDSTK *s; s = malloc(sizeof(TGDSTK)); s->dn = n; s->dv = v ? : malloc(n*sizeof(*s->dv)); s->nm = 0; s->wintop = 0; s->cur = 0; return(s); } static TGDISP *tgdisp_new(void) { TGDISP *d; d = malloc(sizeof(TGDISP)); d->flags = 0; d->depth = 0; d->cd = 0; d->tg = 0; d->text = 0; return(d); } static TGDISP *tgdisp_copy(TGDISP *old) { TGDISP *new; new = malloc(sizeof(TGDISP)); new->flags = old->flags & ~TGDF_MARK; new->depth = old->depth; new->cd = old->cd; new->tg = old->tg; new->text = strdup(old->text); return(new); } static PLYSTK *plystk_new(int n, PLE *v) { PLYSTK *s; s = malloc(sizeof(PLYSTK)); s->n = n; s->a = n; s->v = v ? : malloc(n*sizeof(*s->v)); s->mv = malloc(n); bzero(s->mv,n); s->nm = 0; s->wt = 0; s->cur = 0; return(s); } static void cdvstack_push_all(void) { int n; CDLIST *l; CDVSTK *s; CDD **v; int i; n = 0; for (l=allcds;l;l=l->link) n ++; v = malloc(n*sizeof(*v)); i = 0; for (l=allcds;l;l=l->link) { if (i >= n) panic(); v[i++] = cdd_new(l->cd); } if (i != n) panic(); s = cdvstk_new(n,v); s->link = cdvstack; cdvstack = s; } static void new_host(void) { want_update |= WU_CLEAR; plystack_clear(); playlist_clear(); tgdstack_clear(); tgdlist_clear(); cdvstack_clear(); cdvlist_clear(); cdvstack_push_all(); cdvstack->wintop = 0; cdvstack->cur = 0; dlo = &dlo_cds; } static void startup_succ(void) { scroll_space("Info loaded"); new_host(); } static void startup_fail(void) { scroll_space("Startup failed"); } static void line_minroom(int n) { if (line_a < line_pl+n) { int b1; b1 = line_b1 - line_b0; line_b0 = realloc(line_b0,line_a=line_pl+n+8); line_b1 = line_b0 + b1; } } static void line_nul(void) { line_minroom(line_n+1); line_b1[line_n] = '\0'; } static void line_clear(void) { line_n = 0; line_o = 0; line_c = 0; } static int edit_kshook_nil(char ks __attribute__((__unused__))) { return(0); } static void edit_line(const char *prompt, void (*post)(EPSTAT)) { int pl; pl = strlen(prompt); line_pl = 0; line_b1 = line_b0; line_minroom(pl); line_pl = pl; line_b1 = line_b0 + pl; bcopy(prompt,line_b0,pl); line_keystroke = &edit_kshook_nil; line_clear(); editing_line = 1; postline = post; } static void edit_set_prompt(const char *p) { int pl; pl = strlen(p); if (pl <= line_pl) { if ((line_n > 0) && (pl < line_pl)) { bcopy(line_b1,line_b0+pl,line_n); } } else { line_minroom(line_n+pl-line_pl); if (line_n > 0) bcopy(line_b1,line_b0+pl,line_n); } bcopy(p,line_b0,pl); line_pl = pl; line_b1 = line_b0 + line_pl; } static void edit_keystroke_hook(int (*hook)(char)) { line_keystroke = hook; } static int ui_scroll_messages(void *arg __attribute__((__unused__))) { MSG *m; MSG **mp; struct timeval tv; unsigned long long int now; unsigned long long int exp; int chg; chg = 0; gettimeofday(&tv,0); now = tv.tv_usec + (tv.tv_sec * SECUSEC); while (timed_msgs.head && (timed_msgs.head->time <= now)) { m = msgtconc_remove(&timed_msgs); msg_free(m); chg = 1; } mp = &manual_msgs.head; while ((m = *mp)) { if (m->dead) { *mp = m->link; msg_free(m); manual_msgs.n --; chg = 1; } else { mp = &m->link; } } if (! manual_msgs.head) manual_msgs.tail = &manual_msgs.head; exp = now + (MESSAGE_EXPIRE * SECUSEC); while (scroll_msgs.head) { m = msgtconc_remove(&scroll_msgs); switch (m->when) { default: panic(); break; case MTW_TIME: m->time = exp; msgtconc_append(&timed_msgs,m); break; case MTW_SPACE: msgtconc_append(&space_msgs,m); break; case MTW_MANUAL: m->dead = 0; msgtconc_append(&manual_msgs,m); break; } chg = 1; } if (chg) { want_update |= WU_UPDATE; return(AIO_BLOCK_LOOP); } else if (timed_msgs.head) { exp = timed_msgs.head->time - now; return((exp+999)/1000); } else { return(AIO_BLOCK_NIL); } } static void ciw_move_wintop(int r) { if ( ((*dlo->wt)() < (*dlo->cur)() - (r-1)) || ((*dlo->wt)() > (*dlo->cur)()) ) { (*dlo->setwt)((*dlo->cur)()-(r/2)); if ((*dlo->wt)() >= (*dlo->n)()) (*dlo->setwt)((*dlo->n)()-1); if ((*dlo->wt)() < 0) (*dlo->setwt)(0); } } static void ciw_move_cur(int r) { if ( ((*dlo->wt)() < (*dlo->cur)() - (r-1)) || ((*dlo->wt)() > (*dlo->cur)()) ) { (*dlo->setcur)((*dlo->wt)()+(r/2)); if ((*dlo->cur)() >= (*dlo->n)()) (*dlo->setcur)((*dlo->n)()-1); if ((*dlo->cur)() < 0) (*dlo->setcur)(0); } } static int ui_update(void *arg __attribute__((__unused__))) { static MSGTCONC * const lists[] = { &space_msgs, &timed_msgs, &manual_msgs }; static const int nlists = sizeof(lists) / sizeof(lists[0]); MSGTCONC *tc; int listx; int i; MSG *m; int small; int large; int col; int r; int any; int cdl; if (! want_update) return(AIO_BLOCK_NIL); if (want_update & WU_CLEAR) clearok(stdscr,TRUE); want_update = 0; r = LINES-1; any = 0; for (i=0;in) { if (any) r --; r -= tc->n; any = 1; } } r --; listx = 0; m = lists[0]->head; any = 0; i = 0; if (r < 0) r = 0; if (dlo) { if ((*dlo->cur)() >= (*dlo->n)()) (*dlo->setcur)((*dlo->n)()-1); if ((*dlo->cur)() < 0) (*dlo->setcur)(0); if ((*dlo->wt)() >= (*dlo->n)()) (*dlo->setwt)((*dlo->n)()-1); if ((*dlo->wt)() < 0) (*dlo->setwt)(0); if (r > 0) { if ((*dlo->n)() <= r) (*dlo->setwt)(0); (*cur_in_window)(r); } } cur_in_window = &ciw_move_wintop; last_cd_space = r; cdl = dlo ? (*dlo->wt)() : 0; while (i < LINES-1) { move(i,0); if (i < r) { if (dlo) { if (cdl >= (*dlo->n)()) { printw("~"); clrtoeol(); } else { printw("%c ",(*dlo->mark)(cdl)?'*':' '); if (cdl == (*dlo->cur)()) standout(); printw("%.*s",COLS-3,(*dlo->string)(cdl)); if (cdl == (*dlo->cur)()) standend(); clrtoeol(); cdl ++; } } } else if (i == r) { if (dlo) { FILE *pre_f; char *pre_s; int pre_l; FILE *post_f; char *post_s; int post_l; int v; pre_f = fopenalloc(&pre_s,&pre_l); post_f = fopenalloc(&post_s,&post_l); if (! audio_open) fprintf(pre_f,"Down "); if (paused) fprintf(pre_f,"Paused "); v = (*dlo->nmark)(); if (v) fprintf(pre_f,"M%d ",v); v = (*dlo->list_n)(); if (v) fprintf(pre_f,"L%d ",v); fprintf(pre_f,"%s",(*dlo->name)()); v = (*dlo->stackdepth)(); if (v > 0) fprintf(post_f,"[%d] ",v); if ((*dlo->n)() <= r) { fprintf(post_f,"All"); } else { if ((*dlo->wt)() < 1) { fprintf(post_f,"Top"); } else { fprintf(post_f,"%d",(*dlo->wt)()); } fprintf(post_f," %d ",(*dlo->n)()); if ((*dlo->wt)()+r < (*dlo->n)()) { fprintf(post_f,"%d",(*dlo->n)()-((*dlo->wt)()+r)); } else { fprintf(post_f,"Bot"); } } fclose(pre_f); fclose(post_f); if (post_l >= COLS-1) { move(i,0); addnstr(post_s+post_l-(COLS-1),COLS-1); } else if (pre_l+1+post_l >= COLS-1) { move(i,0); addnstr(pre_s,COLS-1-post_l-1); addch('-'); addnstr(post_s,post_l); } else { addnstr(pre_s,pre_l); addnstr(dashes,COLS-1-pre_l-post_l); addnstr(post_s,post_l); } free(pre_s); free(post_s); } else { addstr(dashes); } } else { if (! m) { if (! any) { listx ++; m = lists[listx]->head; continue; } else { addstr(dashes); any = 0; } } else { printw("%.*s",COLS-1,m->text); m = m->link; any = 1; } } clrtoeol(); i ++; } move(LINES-1,0); if (editing_line) { small = (COLS-1) / 5; large = ((COLS-1) * 2) / 3; line_nul(); if (line_b1 != line_b0+line_pl) panic(); dlog("ui_update line start: pl=%d a=%d o=%d c=%d n=%d b0=%.*s\n", line_pl,line_a,line_o,line_c,line_n,line_n+line_pl,line_b0); if (line_c < 0) line_c = 0; if (line_c > line_n) line_c = line_n; if (line_c+line_pl < line_o+small) { line_o = line_c + line_pl - large; } if (line_c+line_pl > line_o + COLS-1 - small) { line_o = line_c + line_pl - (COLS-1 - large); } if (line_o < 0) line_o = 0; dlog("ui_update line: o now %d\n",line_o); if (line_o > 0) { addstr("..."); col = 3; } else { col = 0; } i = COLS-1 - col - 3; if (i > line_n+line_pl-line_o) i = line_n + line_pl - line_o; dlog("ui_update line: i %d, addnstr %.*s\n",i,i,line_b0+line_o); if (i > 0) addnstr(line_b0+line_o,i); if (line_n+line_pl > line_o+i) addstr("..."); clrtoeol(); move(LINES-1,col+line_c+line_pl-line_o); } else if (space_msgs.head) { addstr("Space to continue"); clrtoeol(); move(LINES-1,COLS-1); } else { (*onekey_fn)(ONEKEY_ADDPROMPT); clrtoeol(); move(LINES-1,COLS-1); } refresh(); return(AIO_BLOCK_LOOP); } static void quit(void) { move(LINES-1,0); clrtoeol(); refresh(); endwin(); exit(0); } static void host_succ(void) { scroll_space("Host switch done"); new_host(); } static void host_fail(void) { scroll_space("Host switch failed"); } static void filter_list(int (*want)(int), int (*keep)(void)) { int i; int j; int n; int cur; int wt; int *newv; int cur_lastbefore; int cur_firstafter; int wt_lastbefore; int wt_firstafter; n = (*dlo->n)(); cur = (*dlo->cur)(); wt = (*dlo->wt)(); newv = malloc(n*sizeof(int)); cur_lastbefore = -1; cur_firstafter = -1; wt_lastbefore = -1; wt_firstafter = -1; j = 0; for (i=0;i= cur)) cur_firstafter = j; if (i < wt) wt_lastbefore = j; if ((wt_firstafter < 0) && (i >= wt)) wt_firstafter = j; j ++; } } if (!(*keep)() || (j == 0) || (j == n)) { free(newv); return; } (*dlo->subset)(j,newv); free(newv); (*dlo->setwt)( (wt_firstafter >= 0) ? wt_firstafter : (wt_lastbefore >= 0) ? wt_lastbefore : 0 ); (*dlo->setcur)( (cur_firstafter >= 0) ? cur_firstafter : (cur_lastbefore >= 0) ? cur_lastbefore : 0 ); } static void cmd_help(void) { scroll_space(": - enter a long command (:longhelp for :-command help)"); scroll_space("j, k - move up and down"); scroll_space("b, space - page up and down"); scroll_space("z, Z - scroll up and down"); scroll_space("/, ? - search forward/backward"); scroll_space("m - toggle mark"); scroll_space("M - other mark operations - M? for help"); scroll_space("Q - quit"); scroll_space("F - filter-and-push list"); scroll_space("U - pop (`up') the list stack"); scroll_space("^T - flush timed-expire messages"); if (dlo) (*dlo->help)(); } static void cmd_longhelp(void) { scroll_space(":help - keystroke command help"); scroll_space(":longhelp - this help"); scroll_space(":quit - quit"); scroll_space(":host HOST - switch CD data host"); scroll_space(":hostabort - abort a `host' command"); scroll_space(":list CMD - manipulate temporary list (`:list help' for help)"); scroll_space(":keep - keep only marked items"); scroll_space(":toss - keep only unmarked items"); if (dlo) (*dlo->longhelp)(); } static void cmd_host(const char *arg) { if (*arg) { if (hostrunning) { scroll_space("Switch already in progress (to %s)",hostrunning->host); scroll_space("Use `hostabort' to abort it."); } else { scroll_space("Switching to %s...",arg); switch_hosts(arg,&host_succ,&host_fail); } } else { if (storage_host) { scroll_space("Host is %s.",storage_host); } else { scroll_space("No host set."); } if (hostrunning) { scroll_space("Switch in progress to %s.",hostrunning->host); } } } static void cmd_hostabort(void) { if (hostrunning) sethost_shutdown(hostrunning); } static int string_lt(const unsigned char *s1, const unsigned char *s2) { int i; int z1; int z2; int e1; int e2; i = 0; while (1) { if (s1[i] != s2[i]) break; if (! s1[i]) return(0); i ++; } if ( (!isdigit(s1[i]) && !isdigit(s2[i])) || ( ((i == 0) || !isdigit(s1[i-1])) && (!isdigit(s1[i]) || !isdigit(s2[i])) ) ) return(s1[i]= 0) && isdigit(s1[i])); s1 += i + 1; s2 += i + 1; for (z1=0;s1[0]=='0';s1++,z1++) ; for (z2=0;s2[0]=='0';s2++,z2++) ; for (e1=0;isdigit(s1[e1]);e1++) ; for (e2=0;isdigit(s2[e2]);e2++) ; if (e1 != e2) return(e1n)()-1;i>=0;i--) (*dlo->setmark)(i,0); } static void mark_all(void) { (*dlo->markall)(); } static void mark_toggle(void) { int i; for (i=(*dlo->n)()-1;i>=0;i--) (*dlo->setmark)(i,!(*dlo->mark)(i)); } static void mark_gather(void) { int *marked; int nm; int *unmarked; int nu; int *order; int cur; int n; int i; int c; int cm; n = (*dlo->n)(); cur = (*dlo->cur)(); cm = (*dlo->mark)(cur); if ((*dlo->nmark)() < 1) return; marked = malloc(3*n*sizeof(int)); unmarked = marked + n; order = unmarked + n; nm = 0; nu = 0; c = -1; for (i=0;imark)(i)) { marked[nm++] = i; } else { unmarked[nu++] = i; if (i <= cur) c = nu; } } if (nm != (*dlo->nmark)()) panic(); if (nm+nu != n) panic(); if (nu > 0) { if (c < 0) c = 0; if (c) bcopy(unmarked,order,c*sizeof(int)); bcopy(marked,order+c,nm*sizeof(int)); if (c < nu) bcopy(unmarked+c,order+c+nm,(nu-c)*sizeof(int)); (*dlo->reorder)(order); } free(marked); } static void cmd_list_add(void) { int marked(int x) { return((*dlo->mark)(x)); } int cur(int x) { return(x==(*dlo->cur)()); } if ((*dlo->nmark)()) { (*dlo->list_save)(&marked); } else { (*dlo->list_save)(&cur); } } static void cmd_list(const char *arg) { if (!strcmp(arg,"help")) { scroll_space(":list help - show this help"); scroll_space(":list add - add marked lines (or current line) to temp list"); scroll_space(":list clear - clear temp list"); scroll_space(":list fetch - use the temp list"); scroll_space(":list copy - use the temp list, don't clear it"); return; } if (! dlo) { scroll_space("Nothing to work with!"); return; } if (!strcmp(arg,"add")) { cmd_list_add(); } else if (!strcmp(arg,"clear")) { (*dlo->list_clear)(); } else if (!strcmp(arg,"fetch")) { (*dlo->list_fetch)(); (*dlo->list_clear)(); } else if (!strcmp(arg,"copy")) { (*dlo->list_fetch)(); } else { scroll_space("`:list %s' unrecognized",arg); scroll_space("`:list help' for help"); beep(); } } static void do_sort(void) { int n; const char **sv; int i; int *iv1; int *iv2; int *iv3; int lt(int a, int b) { return(string_lt(sv[a],sv[b])); } if (! dlo) return; n = (*dlo->n)(); if (n < 2) return; sv = malloc(n*sizeof(char *)); iv1 = malloc(n*3*sizeof(int)); iv2 = iv1 + n; iv3 = iv2 + n; for (i=n-1;i>=0;i--) { sv[i] = (*dlo->string)(i); iv1[i] = i; } sort_list(iv1,n,iv2,iv3,<); (*dlo->reorder)(iv2); free(sv); free(iv1); } static void randomize_cds(void) { int i; int j; int t; int *iv; if (cdvstack->dn < 2) return; iv = malloc(cdvstack->dn*sizeof(int)); for (i=cdvstack->dn-1;i>=0;i--) iv[i] = i; for (i=cdvstack->dn-1;i>=0;i--) { j = random() % cdvstack->dn; t = iv[i]; iv[i] = iv[j]; iv[j] = t; } (*dlo->reorder)(iv); free(iv); } static void randomize_tgs(void) { typedef struct ent ENT; struct ent { int at; int len; } ; ENT *ev; int ea; int en; int i; TGDISP *d; ENT ent; int j; int *order; int k; ev = 0; ea = 0; en = 0; for (i=0;idn;) { d = tgdstack->dv[i]; ent.at = i; switch (d->tg->type) { default: panic(); break; case TGT_GROUP: if (d->flags & TGDF_EXPANDED) { for (j=i+1;(jdn)&&(tgdstack->dv[j]->tg->parent!=d->tg->parent);j++) ; ent.len = j - i; } else { case TGT_LEAF: ent.len = 1; } break; } if (en >= ea) ev = realloc(ev,(ea=en+8)*sizeof(*ev)); ev[en++] = ent; i += ent.len; } if (en >= 2) { for (j=en-1;j>=0;j--) { i = random() % en; if (i != j) { ent = ev[i]; ev[i] = ev[j]; ev[j] = ent; } } order = malloc(tgdstack->dn*sizeof(int)); k = 0; for (i=0;idn) panic(); (*dlo->reorder)(order); free(order); } free(ev); } static void cmd_keeptoss(int (*test)(int)) { int foundany; int want(int x) { if ((*test)(x)) { foundany = 1; return(1); } return(0); } int any(void) { return(foundany); } foundany = 0; filter_list(&want,&any); } static int keeptoss_marked(int x) { return((*dlo->mark)(x)); } static int keeptoss_unmarked(int x) { return(!(*dlo->mark)(x)); } static void cmd_keep(void) { cmd_keeptoss(&keeptoss_marked); } static void cmd_toss(void) { cmd_keeptoss(&keeptoss_unmarked); } static void send_player_head(unsigned char opc) { aio_oq_queue_copy(&play_oq,&opc,1); } static void tell_player_play(CD *cd, TG *tg) { int start; send_player_head(PLAYER_M2P_PLAY); aio_oq_queue_copy(&play_oq,cd->text,strlen(cd->text)+1); start = tg->leaf->start - cd->info->minstart; aio_oq_queue_copy(&play_oq,&start,sizeof(int)); aio_oq_queue_copy(&play_oq,&tg->leaf->length,sizeof(int)); } static void queue_play(CD *cd, TG *tg) { switch (tg->type) { default: panic(); break; case TGT_LEAF: playlist_append(&playlist,cd,tg); tell_player_play(cd,tg); break; case TGT_GROUP: { int i; for (i=0;igroup.n;i++) queue_play(cd,tg->group.v[i]); } break; } } static void data_conn_succ(int fd, void *arg __attribute__((__unused__))) { struct msghdr mh; struct cmsghdr cmh; struct iovec iov; unsigned char ctlbuf[CMSPACE(sizeof(int))]; iov.iov_base = &fd; iov.iov_len = 1; cmh.cmsg_len = CMLEN(sizeof(int)); cmh.cmsg_level = SOL_SOCKET; cmh.cmsg_type = SCM_RIGHTS; bcopy(&cmh,&ctlbuf[0],sizeof(struct cmsghdr)); bcopy(&fd,&ctlbuf[CMSKIP(&cmh)],sizeof(int)); mh.msg_name = 0; mh.msg_namelen = 0; mh.msg_iov = &iov; mh.msg_iovlen = 1; mh.msg_control = (void *) &ctlbuf[0]; mh.msg_controllen = CMLEN(sizeof(int)); mh.msg_flags = 0; if (sendmsg(playerdg,&mh,0) < 0) { fprintf(stderr,"%s: sendmsg to player datagram socket: %s\n",__progname,strerror(errno)); exit(1); } send_player_head(PLAYER_M2P_DATA_CONN); } static void data_conn_fail(void *arg __attribute__((__unused__))) { send_player_head(PLAYER_M2P_DATA_CONN_FAIL); scroll_space("Audio data connection failed"); } static int ppi_pkt_begin(const void *, int); /* forward */ static void ppi_audio_fail_err(void) { scroll_space("Audio %s failed; %s",ppi_what,strerror(ppi_errno)); audio_open = 0; process_player_input = &ppi_pkt_begin; want_update |= WU_UPDATE; } static int ppi_int(int *ip, const void *data, int len, void (*done)(void)) { int n; n = sizeof(int) - ppi_nb; if (n > len) n = len; bcopy(data,((char *)ip)+ppi_nb,n); ppi_nb += n; if (ppi_nb >= sizeof(int)) (*done)(); return(n); } static int ppi_audio_fail(const void *data, int len) { return(ppi_int(&ppi_errno,data,len,&ppi_audio_fail_err)); } static void ppi_showstatus(void) { int m; int s; int max; int cols; int scols; int mins_w; int sw; int ssw; int ps; if (have_playlist_msgs) { if (playlist.n > 0) { if (playlist.v[0]->tg->type != TGT_LEAF) panic(); max = playlist.v[0]->tg->leaf->length; ps = (ppi_status < 0) ? 0 : ppi_status; s = (ppi_status / 75) % 60; m = ppi_status / (75 * 60); if (max >= 10*75*60) { cols = COLS - 1 - 5; mins_w = 2; } else { cols = COLS - 1 - 4; mins_w = 1; } scols = cols - 2; sw = ((cols + 1) * ppi_status) / (max + 1); ssw = ((scols + 1) * ppi_status) / (max + 1); if ((m != playlist_last_m) || (s != playlist_last_s) || (sw != playlist_last_sw)) { manual_msg_change(playlist_status_msg,"%*d:%02d%.*s%.*s",mins_w,m,s,sw,stars,cols-sw,dashes); free(playlist_shortstatus); asprintf(&playlist_shortstatus,"%*d:%02d%.*s%.*s",mins_w,m,s,ssw,stars,scols-ssw,dashes); } } else { manual_msg_change(playlist_status_msg,""); } } process_player_input = &ppi_pkt_begin; } static int ppi_playstatus(const void *data, int len) { return(ppi_int(&ppi_status,data,len,&ppi_showstatus)); } static int ppi_message(const void *data, int len) { const char *np; np = memchr(data,'\0',len); if (np) { if (ppi_f) { fwrite(data,1,np-(const char *)data,ppi_f); fclose(ppi_f); scroll_space("%.*s",ppi_l,ppi_s); free(ppi_s); } else { scroll_space("%.*s",(int)(np-(const char *)data),(const char *)data); } process_player_input = &ppi_pkt_begin; return((np+1)-(const char *)data); } if (! ppi_f) ppi_f = fopenalloc(&ppi_s,&ppi_l); fwrite(data,1,len,ppi_f); return(len); } static void update_playlist_msg(void) { int m; int s; int max; if (playlist.n > 0) { if (! have_playlist_msgs) { playlist_prev_msg = scroll_manual(""); playlist_text_msg = scroll_manual(""); playlist_status_msg = scroll_manual(""); playlist_next_msg = scroll_manual(""); have_playlist_msgs = 1; } if (playlist.v[0]->tg->type != TGT_LEAF) panic(); max = playlist.v[0]->tg->leaf->length; s = (max / 75) % 60; m = max / (75 * 60); manual_msg_change(playlist_prev_msg,"%s",playlist_lastplay); manual_msg_change(playlist_text_msg,"%d:%02d %s",m,s,playlist.v[0]->text); if (playlist.n > 1) { manual_msg_change(playlist_next_msg,"%s",playlist.v[1]->text); } else { manual_msg_change(playlist_next_msg,""); } } else { if (have_playlist_msgs) { manual_msg_done(playlist_prev_msg); manual_msg_done(playlist_text_msg); manual_msg_done(playlist_status_msg); manual_msg_done(playlist_next_msg); have_playlist_msgs = 0; } } } static void track_play_done(void) { PLE *e; e = playlist_remove(&playlist); if (e) { free(playlist_lastplay); playlist_lastplay = strdup(e->text); ple_free(e); update_playlist_msg(); ppi_status = -1; } } static int ppi_pkt_begin(const void *data, int len) { if (len < 1) panic(); switch (*(const unsigned char *)data) { case PLAYER_P2M_AUDIO_OPEN: dlog("PLAYER_P2M_AUDIO_OPEN\n"); audio_open = 1; want_update |= WU_UPDATE; break; case PLAYER_P2M_AUDIO_FAIL_OPEN: dlog("PLAYER_P2M_AUDIO_FAIL_OPEN\n"); process_player_input = &ppi_audio_fail; ppi_what = "open"; break; case PLAYER_P2M_AUDIO_FAIL_SETUP: dlog("PLAYER_P2M_AUDIO_FAIL_SETUP\n"); process_player_input = &ppi_audio_fail; ppi_what = "setup"; break; case PLAYER_P2M_WANT_DATA_CONN: dlog("PLAYER_P2M_WANT_DATA_CONN\n"); start_connect(hostai_data,&data_conn_succ,&data_conn_fail,0); break; case PLAYER_P2M_PAUSED: dlog("PLAYER_P2M_PAUSED\n"); paused = 1; want_update |= WU_UPDATE; break; case PLAYER_P2M_UNPAUSED: dlog("PLAYER_P2M_UNPAUSED\n"); paused = 0; want_update |= WU_UPDATE; break; case PLAYER_P2M_AUDIO_CLOSED: dlog("PLAYER_P2M_AUDIO_CLOSED\n"); audio_open = 0; want_update |= WU_UPDATE; break; case PLAYER_P2M_MESSAGE: dlog("PLAYER_P2M_MESSAGE\n"); process_player_input = &ppi_message; ppi_f = 0; break; case PLAYER_P2M_PLAYSTATUS: dlog("PLAYER_P2M_PLAYSTATUS\n"); process_player_input = &ppi_playstatus; break; case PLAYER_P2M_PLAYDONE: dlog("PLAYER_P2M_PLAYDONE\n"); if (! ignore_done_to_flush) track_play_done(); break; case PLAYER_P2M_FLUSHDONE: dlog("PLAYER_P2M_FLUSHDONE\n"); if (! ignore_done_to_flush) panic(); ignore_done_to_flush = 0; update_playlist_msg(); break; default: panic(); break; } ppi_nb = 0; return(1); } static int wtest_player(void *arg __attribute__((__unused__))) { return(aio_oq_nonempty(&play_oq)); } static void rd_player(void *arg __attribute__((__unused__))) { int r; int o; int n; unsigned char buf[512]; r = read(playerfd,&buf[0],sizeof(buf)); if (r < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: master<-player read: %s\n",__progname,strerror(errno)); exit(1); } if (r == 0) { fprintf(stderr,"%s: master<-player read EOF\n",__progname); exit(1); } o = 0; while (o < r) { n = (*process_player_input)(&buf[o],r-o); o += n; } } static void wr_player(void *arg __attribute__((__unused__))) { int w; w = aio_oq_writev(&play_oq,playerfd,0); if (w < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: master->player write: %s\n",__progname,strerror(errno)); exit(1); } aio_oq_dropdata(&play_oq,w); } static void play_marked_or_cur_tgs(void) { int i; if (! tgdstack->nm) tgdstack->dv[tgdstack->cur]->flags |= TGDF_MARK; for (i=0;idn;i++) { if (tgdstack->dv[i]->flags & TGDF_MARK) { queue_play(tgdstack->dv[i]->cd,tgdstack->dv[i]->tg); } } update_playlist_msg(); if (! tgdstack->nm) tgdstack->dv[tgdstack->cur]->flags &= ~TGDF_MARK; } static void play_marked_or_cur_cds(void) { int i; if (cdvstack->dn < 1) return; if (cdvstack->nm < 1) cdvstack->dv[cdvstack->cur]->mark = 1; for (i=0;idn;i++) { if (cdvstack->dv[i]->mark) { queue_play(cdvstack->dv[i]->cd,cdvstack->dv[i]->cd->trkroot); } } update_playlist_msg(); if (cdvstack->nm < 1) cdvstack->dv[cdvstack->cur]->mark = 0; } static void audio_down(void) { send_player_head(PLAYER_M2P_CLOSE_AUDIO); } static void audio_up(void) { send_player_head(PLAYER_M2P_OPEN_AUDIO); } static void play_next(void) { int one; send_player_head(PLAYER_M2P_PLAY_NEXT); one = 1; aio_oq_queue_copy(&play_oq,&one,sizeof(int)); } static void play_stop(void) { int n; n = playlist.n; if (n < 1) return; send_player_head(PLAYER_M2P_PLAY_NEXT); aio_oq_queue_copy(&play_oq,&n,sizeof(int)); } static void do_cmd(EPSTAT stat) { int k0; int k1; int klen; int a0; switch (stat) { default: panic(); break; case EP_DONE: break; case EP_ABORT: return; break; } line_nul(); for (k0=0;(k0longcmd)(line_b1+k0,klen,line_b1+a0,line_n-a0)) beep(); } } static void do_search(EPSTAT stat, int direction) { int i; const char *pattern; int pl; const char *s; int n; int cur; unsigned char (*tbl)[256]; switch (stat) { default: panic(); break; case EP_DONE: break; case EP_ABORT: return; break; } if (! dlo) { scroll_timed("Nothing to search in!"); return; } pl = line_n; if (pl > 255) { scroll_timed("Search pattern too long!"); return; } if (pl == 0) { if (lastsearchlen < 0) { scroll_timed("No previous search string!"); return; } pattern = lastsearch; pl = lastsearchlen; } else { pattern = line_b1; free(lastsearch); lastsearchlen = pl; lastsearch = malloc(pl); bcopy(pattern,lastsearch,pl); } lastsearchdir = direction; tbl = malloc(pl*sizeof(*tbl)); searchstr_maketbl(tbl,pattern,pl,&equiv_casefold[0]); n = (*dlo->n)(); cur = (*dlo->cur)(); do <"search"> { for (i=cur+direction;(i=0);i+=direction) { s = (*dlo->string)(i); if (searchstr(s,strlen(s),tbl,pl) >= 0) { (*dlo->setcur)(i); break <"search">; } } for (i=(direction<0)?(n-1):0;(direction<0)?(i>=cur):(i<=cur);i+=direction) { s = (*dlo->string)(i); if (searchstr(s,strlen(s),tbl,pl) >= 0) { (*dlo->setcur)(i); scroll_timed("Search wrapped"); break <"search">; } } scroll_timed("Not found"); } while (0); free(tbl); } static void do_search_f(EPSTAT stat) { do_search(stat,1); } static void do_search_b(EPSTAT stat) { do_search(stat,-1); } static void do_filter(EPSTAT stat) { int i; int j; int pl; regex_t rx; unsigned char (*tbl)[256]; int foundany; int want(int x) { const char *s; s = (*dlo->string)(x); if ( ( tbl ? (searchstr(s,strlen(s),tbl,pl) >= 0) : (regexec(&rx,s,0,0,0) == 0) ) ? !(filterflags & FF_COMPLEMENT) : (filterflags & FF_COMPLEMENT) ) { foundany = 1; return(1); } return(0); } int any(void) { if (! foundany) { scroll_timed("No matches, list not changed"); return(0); } return(1); } switch (stat) { default: panic(); break; case EP_DONE: break; case EP_ABORT: return; break; } if (line_n == 0) { scroll_space("No search string!"); return; } tbl = 0; if (filterflags & FF_REGEX) { rx.re_endp = line_b1 + line_n; i = regcomp(&rx,line_b1,REG_EXTENDED|REG_ICASE|REG_NOSUB); } else { pl = line_n; if (pl < 255) { tbl = malloc(pl*sizeof(*tbl)); searchstr_maketbl(tbl,line_b1,pl,&equiv_casefold[0]); i = 0; } else { rx.re_endp = line_b1 + line_n; i = regcomp(&rx,line_b1,REG_NOSPEC|REG_ICASE|REG_PEND|REG_NOSUB); } } if (i) { char *emsg; j = regerror(i,&rx,0,0); emsg = malloc(j); regerror(i,&rx,emsg,j); scroll_space("%s",emsg); free(emsg); return; } foundany = 0; filter_list(&want,&any); if (tbl) free(tbl); else regfree(&rx); } static void do_repeat_search(void) { if (lastsearchlen < 0) { scroll_timed("No search to repeat!"); return; } line_clear(); do_search(EP_DONE,lastsearchdir); } static void line_down(void) { if (dlo) (*dlo->setcur)((*dlo->cur)()+1); } static void line_up(void) { if (dlo) (*dlo->setcur)((*dlo->cur)()-1); } static void line_first(void) { if (dlo) (*dlo->setcur)(0); } static void line_last(void) { if (dlo) (*dlo->setcur)((*dlo->n)()-1); } static void page_down(void) { int n; if (! dlo) return; n = (last_cd_space * 3) / 4; if (n < 1) n = 1; (*dlo->setwt)((*dlo->wt)()+n); cur_in_window = &ciw_move_cur; } static void page_up(void) { int n; if (! dlo) return; n = (last_cd_space * 3) / 4; if (n < 1) n = 1; (*dlo->setwt)((*dlo->wt)()-n); cur_in_window = &ciw_move_cur; } static void scroll_up(void) { if (! dlo) return; (*dlo->setwt)((*dlo->wt)()+1); cur_in_window = &ciw_move_cur; } static void scroll_down(void) { if (! dlo) return; (*dlo->setwt)((*dlo->wt)()-1); cur_in_window = &ciw_move_cur; } static void flush_timed_msgs(void) { MSG *m; while (timed_msgs.head) { m = msgtconc_remove(&timed_msgs); msg_free(m); } } static void do_pop(void) { if (! dlo) { scroll_timed("Nothing to work with!"); beep(); } else if ((*dlo->stackdepth)() < 1) { scroll_timed("Stack empty!"); beep(); } else { (*dlo->stackpop)(); } } static void tgdisp_reset_text(TGDISP *d) { free(d->text); switch (d->tg->type) { default: panic(); break; case TGT_LEAF: asprintf(&d->text,"%*s %s",d->depth*2,"",strdup(d->tg->text)); break; case TGT_GROUP: asprintf(&d->text,"%*s%c %s",d->depth*2,"",(d->flags&TGDF_EXPANDED)?'+':'-',strdup(d->tg->text)); break; } } static void show_cd_tracks(void) { TG *tgroot; int i; int j; TGDISP *d; int n; int sx; if (tgdstack) panic(); if (! cdvstack->nm) cdvstack->dv[cdvstack->cur]->mark = 1; n = 0; for (i=cdvstack->dn-1;i>=0;i--) { if (cdvstack->dv[i]->mark) { n += cdvstack->dv[i]->cd->trkroot->group.n; } } tgdstack = tgdstk_new(n,0); sx = 0; for (i=0;idn;i++) { if (cdvstack->dv[i]->mark) { tgroot = cdvstack->dv[i]->cd->trkroot; if (!tgroot || (tgroot->type != TGT_GROUP)) panic(); for (j=tgroot->group.n-1;j>=0;j--) { d = tgdisp_new(); d->depth = 1; d->cd = cdvstack->dv[i]->cd; d->tg = tgroot->group.v[j]; tgdisp_reset_text(d); tgdstack->dv[sx+j] = d; } sx += tgroot->group.n; } } if (sx != tgdstack->dn) panic(); tgdstack->link = 0; if (! cdvstack->nm) cdvstack->dv[cdvstack->cur]->mark = 0; dlo = &dlo_tgs; } static void toggle_mark(void) { if (! dlo) { beep(); return; } (*dlo->setmark)((*dlo->cur)(),!(*dlo->mark)((*dlo->cur)())); } static void filter_reset_prompt(void) { switch (filterflags) { case 0: edit_set_prompt("Filter keep: "); break; case FF_REGEX: edit_set_prompt("Filter keep (regex): "); break; case FF_COMPLEMENT: edit_set_prompt("Filter toss: "); break; case FF_COMPLEMENT | FF_REGEX: edit_set_prompt("Filter toss (regex): "); break; } } static int filter_edit_keystroke(char ks) { switch (ks) { case 14: /* ^N */ filterflags ^= FF_COMPLEMENT; filter_reset_prompt(); return(1); break; case 18: /* ^R */ filterflags ^= FF_REGEX; filter_reset_prompt(); return(1); break; } return(0); } static void tgs_h(void) { TGDISP *d; int i; int j; d = tgdstack->dv[tgdstack->cur]; if (d->flags & TGDF_EXPANDED) { if (d->tg->type != TGT_GROUP) panic(); d->flags &= ~TGDF_EXPANDED; j = tgdstack->cur + 1; for (i=j;(idn)&&(tgdstack->dv[i]->tg->parent==d->tg);i++) tgdisp_free(tgdstack->dv[i]); if (i != j) for (;idn;i++) tgdstack->dv[j++] = tgdstack->dv[i]; tgdstack->dn = j; tgdisp_reset_text(d); } else if (d->tg->parent) { for (i=tgdstack->cur-1;(i>=0)&&(tgdstack->dv[i]->tg!=d->tg->parent);i--) ; if (i < 0) { beep(); return; } tgdstack->cur = i; } else { beep(); } } static void tgs_l(void) { TGDISP *d; TGDISP **newv; int i; TGDISP *n; d = tgdstack->dv[tgdstack->cur]; if (d->flags & TGDF_EXPANDED) { tgdstack->cur ++; } else if (d->tg->type == TGT_GROUP) { d->flags |= TGDF_EXPANDED; if (d->tg->group.n > 0) { newv = malloc((tgdstack->dn+d->tg->group.n)*sizeof(*newv)); bcopy(tgdstack->dv,newv,(tgdstack->cur+1)*sizeof(*newv)); if (tgdstack->cur+1 < tgdstack->dn) { bcopy(tgdstack->dv+tgdstack->cur+1,newv+tgdstack->cur+1+d->tg->group.n,(tgdstack->dn-tgdstack->cur-1)*sizeof(*newv)); } for (i=d->tg->group.n-1;i>=0;i--) { n = tgdisp_new(); n->depth = d->depth + 1; n->cd = d->cd; n->tg = d->tg->group.v[i]; tgdisp_reset_text(n); newv[tgdstack->cur+1+i] = n; } free(tgdstack->dv); tgdstack->dv = newv; tgdstack->dn += d->tg->group.n; } tgdisp_reset_text(d); } else { beep(); } } static void pause_playing(void) { send_player_head(PLAYER_M2P_PAUSE); } static void unpause_playing(void) { send_player_head(PLAYER_M2P_UNPAUSE); } static void onekey_mark(int ch) { switch (ch) { case ONEKEY_ADDPROMPT: addstr("Mark: ACGT or ? for help"); break; case 'A': mark_all(); break; case 'C': mark_clear(); break; case 'G': mark_gather(); break; case 'T': mark_toggle(); break; case '?': scroll_space("M? - show this help"); scroll_space("MA - mark all"); scroll_space("MC - clear all marks"); scroll_space("MT - toggle all marks"); scroll_space("MG - gather marked lines"); break; default: break; } } static void onekey_default(int ch) { switch (ch) { case ONEKEY_ADDPROMPT: addstr("Type a command (:help for help)"); break; case ':': edit_line(":",&do_cmd); break; case 'j': line_down(); break; case 'k': line_up(); break; case 'z': scroll_up(); break; case 'Z': scroll_down(); break; case '<': line_first(); break; case '>': line_last(); break; case '/': if (dlo) { edit_line("/",&do_search_f); } else { scroll_timed("Nothing to search!"); } break; case '?': if (dlo) { edit_line("?",&do_search_b); } else { scroll_timed("Nothing to search!"); } break; case 'n': do_repeat_search(); break; case 'm': toggle_mark(); break; case 'M': onekey_fn = &onekey_mark; break; case 'p': pause_playing(); break; case 'g': unpause_playing(); break; case 0x14: /* ^T */ flush_timed_msgs(); break; case 'F': edit_line("",&do_filter); edit_keystroke_hook(&filter_edit_keystroke); filterflags = 0; filter_reset_prompt(); break; case 'U': do_pop(); break; case ' ': page_down(); break; case 'b': page_up(); break; default: if (!dlo || !(*dlo->keystroke)(ch)) beep(); break; } } static void rd_stdin(void *arg __attribute__((__unused__))) { char ch; int nr; nr = read(0,&ch,1); if (nr < 0) exit(1); if (nr == 0) return; want_update |= WU_UPDATE; if (editing_line) { switch (ch) { case 1: /* ^A */ line_c = 0; break; case 2: /* ^B */ if (line_c > 0) line_c --; break; case 3: /* ^C */ endwin(); exit(0); break; case 4: /* ^D */ if (line_c < line_n+1) bcopy(line_b1+line_c+1,line_b1+line_c,line_n+1-line_c); if (line_c < line_n) line_n --; break; case 5: /* ^E */ line_c = line_n; break; case 6: /* ^F */ if (line_c < line_n) line_c ++; break; case 7: /* ^G */ beep(); break; case 8: /* ^H */ case 127: /* DEL */ if (line_c > 0) { if (line_c < line_n) bcopy(line_b1+line_c,line_b1+line_c-1,line_n-line_c); line_c --; line_n --; } break; case 10: /* ^J */ case 13: /* ^M */ editing_line = 0; (*postline)(EP_DONE); break; case 11: /* ^K */ line_n = line_c; break; case 12: /* ^L */ want_update |= WU_CLEAR; break; case 20: /* ^T */ if (line_c >= 2) { char t; t = line_b1[line_c-1]; line_b1[line_c-1] = line_b1[line_c-2]; line_b1[line_c-2] = t; } break; case 24: /* ^X */ line_n = 0; line_c = 0; break; case 27: /* ESC */ editing_line = 0; (*postline)(EP_ABORT); break; default: if ((unsigned char)ch >= 32) { line_minroom(line_n+1); if (line_c < line_n) bcopy(line_b1+line_c,line_b1+line_c+1,line_n-line_c); line_b1[line_c++] = ch; line_n ++; } else { if (! (*line_keystroke)(ch)) beep(); } break; } } else if (space_msgs.head && (ch == ' ')) { while (space_msgs.head) { MSG *m; m = msgtconc_remove(&space_msgs); msg_free(m); } } else { void (*fn)(int); fn = onekey_fn; onekey_fn = &onekey_default; (*fn)((unsigned char)ch); } } static void gen_dashes(void) { if (dashlen != COLS-1) { free(dashes); free(stars); dashlen = COLS - 1; dashes = malloc(dashlen+1); stars = malloc(dashlen+1); memset(dashes,'-',dashlen); dashes[dashlen] = '\0'; memset(stars,'*',dashlen); stars[dashlen] = '\0'; } } static void show_playlist(void) { if (playlist.n) { dlo = &dlo_ply; (*dlo->setwt)(0); (*dlo->setcur)(1); } else { scroll_timed("No playlist!"); } } static void show_info(CD *cd, TG *tg) { void tg_chain(TG *tg) { if (!tg || !tg->parent) return; tg_chain(tg->parent); scroll_space("%s",tg->text); } scroll_space(cd->text); tg_chain(tg); } static void reset_pending_playlist(void) { int i; send_player_head(PLAYER_M2P_FLUSH_PENDING); for (i=1;icd,playlist.v[i]->tg); ignore_done_to_flush = 1; } static void playlist_seek_cur(int off) { send_player_head(PLAYER_M2P_SEEK); aio_oq_queue_copy(&play_oq,&off,sizeof(int)); } static void replace_playlist(void) { PLE **copyv; char *copym; int n; int i; int wt; int cur; if (!plystack || (playlist.n < 2)) return; n = plystack->n; copyv = malloc(n*sizeof(PLE *)); copym = malloc(n); for (i=n-1;i>=0;i--) { copyv[i] = ple_copy(plystack->v[i]); copym[i] = plystack->mv[i]; } wt = (*dlo->wt)(); cur = (*dlo->cur)(); while (plystack) { PLYSTK *s; s = plystack; plystack = s->link; plystk_free(s,1); } plylist.n = 0; while (playlist.n > 1) { ple_free(playlist.v[playlist.n-1]); playlist.n --; } for (i=0;i0;i--) { if ((playlist.mv[i] = copym[i-1])) playlist.nm ++; } free(copyv); free(copym); (*dlo->setwt)(wt); (*dlo->setcur)(cur); reset_pending_playlist(); } static void dl_cds_init(void) { cdvlist.dn = 0; cdvlist.dv = 0; } static int dl_cds_n(void) { return(cdvstack?cdvstack->dn:0); } static const char *dl_cds_name(void) { return(""); } static const char *dl_cds_string(int n) { return(((n<0)||(n>=cdvstack->dn))?0:cdvstack->dv[n]->cd->text); } static int dl_cds_wt(void) { return(cdvstack?cdvstack->wintop:0); } static int dl_cds_cur(void) { return(cdvstack?cdvstack->cur:0); } static char dl_cds_mark(int n) { if ((n < 0) || (n >= cdvstack->dn)) panic(); return(cdvstack->dv[n]->mark); } static int dl_cds_nmark(void) { return(cdvstack->nm); } static void dl_cds_setwt(int v) { if (! cdvstack) panic(); cdvstack->wintop = v; } static void dl_cds_setcur(int v) { if (! cdvstack) panic(); cdvstack->cur = v; } static void dl_cds_setmark(int n, char v) { if ((n < 0) || (n >= cdvstack->dn)) panic(); if (v && !cdvstack->dv[n]->mark) cdvstack->nm ++; if (!v && cdvstack->dv[n]->mark) cdvstack->nm --; cdvstack->dv[n]->mark = v; } static void dl_cds_markall(void) { int i; for (i=cdvstack->dn-1;i>=0;i--) { if (! cdvstack->dv[i]->mark) { cdvstack->nm ++; cdvstack->dv[i]->mark = 1; } } } static void dl_cds_reorder(int *order) { CDD **ov; int n; CDD **nv; int i; char *flg; int nwt; int ncur; if (! cdvstack) panic(); ov = cdvstack->dv; n = cdvstack->dn; nv = malloc(n*sizeof(*nv)); flg = malloc(n); bzero(flg,n); nwt = -1; ncur = -1; for (i=n-1;i>=0;i--) { if ((order[i] < 0) || (order[i] >= n)) panic(); nv[i] = ov[order[i]]; if (order[i] == cdvstack->wintop) { if (nwt >= 0) panic(); nwt = i; } if (order[i] == cdvstack->cur) { if (ncur >= 0) panic(); ncur = i; } if (flg[order[i]]) panic(); flg[order[i]] = 1; } for (i=n-1;i>=0;i--) if (! flg[i]) panic(); if ((nwt < 0) || (ncur < 0)) panic(); free(flg); free(ov); cdvstack->dv = nv; cdvstack->wintop = nwt; cdvstack->cur = ncur; } static void dl_cds_subset(int n, int *vec) { int oldn; CDD **newv; int i; int j; char *flg; CDVSTK *s; if (! cdvstack) panic(); if (n < 1) panic(); oldn = cdvstack->dn; newv = malloc(n*sizeof(*newv)); flg = malloc(oldn); bzero(flg,oldn); for (i=n-1;i>=0;i--) { j = vec[i]; if ((j < 0) || (j >= oldn)) panic(); if (flg[j]) panic(); flg[j] = 1; newv[i] = cdd_copy(cdvstack->dv[j]); } s = cdvstk_new(n,newv); s->link = cdvstack; cdvstack = s; } static void dl_cds_list_save(int (*want)(int)) { int i; if (! cdvstack) panic(); for (i=0;idn;i++) { if ((*want)(i)) { cdvlist.dv = realloc(cdvlist.dv,(cdvlist.dn+1)*sizeof(*cdvlist.dv)); cdvlist.dv[cdvlist.dn++] = cdd_copy(cdvstack->dv[i]); } } } static void dl_cds_list_fetch(void) { CDVSTK *s; int i; if (! cdvstack) panic(); s = cdvstk_new(cdvlist.dn,0); for (i=cdvlist.dn-1;i>=0;i--) s->dv[i] = cdd_copy(cdvlist.dv[i]); s->link = cdvstack; cdvstack = s; } static void dl_cds_list_clear(void) { cdvlist_clear(); } static int dl_cds_list_n(void) { return(cdvlist.dn); } static void dl_cds_stackpop(void) { CDVSTK *s; if (!cdvstack || !cdvstack->link) panic(); s = cdvstack; cdvstack = s->link; cdvstk_free(s); } static int dl_cds_stackdepth(void) { CDVSTK *s; int d; if (! cdvstack) panic(); d = 0; for (s=cdvstack->link;s;s=s->link) d ++; return(d); } static int dl_cds_keystroke(unsigned char ks) { switch (ks) { case 'P': play_marked_or_cur_cds(); return(1); break; case 'R': randomize_cds(); return(1); break; case 'S': do_sort(); return(1); break; case 'L': show_playlist(); return(1); break; case 'T': if (! cdvstack) panic(); show_cd_tracks(); return(1); break; } return(0); } static int dl_cds_longcmd( char *cmd __attribute__((__unused__)), int cmdlen __attribute__((__unused__)), char *args __attribute__((__unused__)), int argslen __attribute__((__unused__)) ) { return(0); } static void dl_cds_help(void) { scroll_space("P - play marked/current CD(s)"); scroll_space("R - randomze list order"); scroll_space("S - sort list"); scroll_space("L - show playlist"); scroll_space("T - switch to track list for marked/current CD(s)"); } static void dl_cds_longhelp(void) { } static DLOPS dlo_cds = DLO_INIT(cds); static void dl_tgs_init(void) { tgdlist.dn = 0; tgdlist.dv = 0; } static int dl_tgs_n(void) { return(tgdstack->dn); } static const char *dl_tgs_name(void) { return(tgdstack->dv[tgdstack->cur]->cd->text); } static const char *dl_tgs_string(int n) { return(((n<0)||(n>=tgdstack->dn))?0:tgdstack->dv[n]->text); } static int dl_tgs_wt(void) { return(tgdstack->wintop); } static int dl_tgs_cur(void) { return(tgdstack->cur); } static char dl_tgs_mark(int n) { if ((n < 0) || (n >= tgdstack->dn)) panic(); return((tgdstack->dv[n]->flags&TGDF_MARK)?1:0); } static int dl_tgs_nmark(void) { return(tgdstack->nm); } static void dl_tgs_setwt(int v) { tgdstack->wintop = v; } static void dl_tgs_setcur(int v) { tgdstack->cur = v; } static void dl_tgs_setmark(int n, char v) { if ((n < 0) || (n >= tgdstack->dn)) panic(); if (v && !(tgdstack->dv[n]->flags&TGDF_MARK)) tgdstack->nm ++; if (!v && (tgdstack->dv[n]->flags&TGDF_MARK)) tgdstack->nm --; if (v) tgdstack->dv[n]->flags |= TGDF_MARK; else tgdstack->dv[n]->flags &= ~TGDF_MARK; } static void dl_tgs_markall(void) { int i; for (i=tgdstack->dn-1;i>=0;i--) { if (tgdstack->dv[i]->flags & TGDF_EXPANDED) { dl_tgs_setmark(i,0); } else { dl_tgs_setmark(i,1); } } } static void dl_tgs_reorder(int *order) { TGDISP **odv; TGDISP **ndv; int n; int i; char *flg; int nwt; int ncur; odv = tgdstack->dv; n = tgdstack->dn; ndv = malloc(n*sizeof(*ndv)); flg = malloc(n); bzero(flg,n); nwt = -1; ncur = -1; for (i=n-1;i>=0;i--) { if ((order[i] < 0) || (order[i] >= n)) panic(); ndv[i] = odv[order[i]]; if (order[i] == tgdstack->wintop) { if (nwt >= 0) panic(); nwt = i; } if (order[i] == tgdstack->cur) { if (ncur >= 0) panic(); ncur = i; } if (flg[order[i]]) panic(); flg[order[i]] = 1; } for (i=n-1;i>=0;i--) if (! flg[i]) panic(); if ((nwt < 0) || (ncur < 0)) panic(); free(flg); free(odv); tgdstack->dv = ndv; tgdstack->wintop = nwt; tgdstack->cur = ncur; } static void dl_tgs_subset(int n, int *vec) { int oldn; TGDISP **newv; int i; int j; char *flg; TGDSTK *ns; if (n < 1) panic(); oldn = tgdstack->dn; newv = malloc(n*sizeof(*newv)); flg = malloc(oldn); bzero(flg,oldn); for (i=n-1;i>=0;i--) { j = vec[i]; if ((j < 0) || (j >= oldn)) panic(); if (flg[j]) panic(); flg[j] = 1; newv[i] = tgdisp_copy(tgdstack->dv[j]); } ns = tgdstk_new(n,newv); ns->link = tgdstack; tgdstack = ns; } static void dl_tgs_list_save(int (*want)(int)) { int i; for (i=0;idn;i++) { if ((*want)(i)) { tgdlist.dv = realloc(tgdlist.dv,(tgdlist.dn+1)*sizeof(*tgdlist.dv)); tgdlist.dv[tgdlist.dn++] = tgdisp_copy(tgdstack->dv[i]); } } } static void dl_tgs_list_fetch(void) { TGDSTK *s; int i; if (! tgdstack) panic(); s = tgdstk_new(tgdlist.dn,0); for (i=tgdlist.dn-1;i>=0;i--) s->dv[i] = tgdisp_copy(tgdlist.dv[i]); s->link = tgdstack; tgdstack = s; } static void dl_tgs_list_clear(void) { tgdlist_clear(); } static int dl_tgs_list_n(void) { return(tgdlist.dn); } static void dl_tgs_stackpop(void) { TGDSTK *s; if (!tgdstack || !tgdstack->link) panic(); s = tgdstack; tgdstack = s->link; tgdstk_free(s); } static int dl_tgs_stackdepth(void) { TGDSTK *s; int d; d = 0; for (s=tgdstack->link;s;s=s->link) d ++; return(d); } static int dl_tgs_keystroke(unsigned char ks) { switch (ks) { case 'C': tgdstack_clear(); dlo = &dlo_cds; return(1); break; case 'R': randomize_tgs(); return(1); break; case 'P': play_marked_or_cur_tgs(); return(1); break; case 'L': show_playlist(); return(1); break; case 'h': tgs_h(); return(1); break; case 'l': tgs_l(); return(1); break; case 'I': show_info(tgdstack->dv[tgdstack->cur]->cd,tgdstack->dv[tgdstack->cur]->tg); return(1); break; } return(0); } static int dl_tgs_longcmd( char *cmd __attribute__((__unused__)), int cmdlen __attribute__((__unused__)), char *args __attribute__((__unused__)), int argslen __attribute__((__unused__)) ) { return(0); } static void dl_tgs_help(void) { scroll_space("C - return to CD list"); scroll_space("L - show playlist"); scroll_space("P - play marked/current track(s)"); scroll_space("R - randomize track order"); scroll_space("h - move up or collapse track group"); scroll_space("l - expand track group"); scroll_space("I - show info on current track"); } static void dl_tgs_longhelp(void) { } static DLOPS dlo_tgs = DLO_INIT(tgs); static void dl_ply_init(void) { } static int dl_ply_n(void) { if (plystack) { return(plystack->n+3); } else if (! playlist.n) { return(playlist_lastplay[0]?1:0); } else { return(playlist.n+2); } } static const char *dl_ply_name(void) { return("Playlist"); } static const char *dl_ply_string(int n) { switch (n) { case 0: return(playlist_lastplay); break; case 1: return(manual_msg_text(playlist_text_msg)); break; case 2: return(playlist_shortstatus); break; } if (n < 3) panic(); if (plystack) { if (n-3 >= plystack->n) panic(); return(plystack->v[n-3]->text); } else { if (n-2 >= playlist.n) panic(); return(playlist.v[n-2]->text); } } static int dl_ply_wt(void) { return((plystack?:&playlist)->wt); } static int dl_ply_cur(void) { return((plystack?:&playlist)->cur); } static char dl_ply_mark(int n) { if (n < 3) return(0); return( plystack ? plystack->mv[n-3] : playlist.mv[n-2] ); } static int dl_ply_nmark(void) { return((plystack?:&playlist)->nm); } static void dl_ply_setwt(int v) { (plystack?:&playlist)->wt = v; } static void dl_ply_setcur(int v) { (plystack?:&playlist)->cur = v; } static void dl_ply_setmark(int n, char v) { PLYSTK *s; char *mp; if (n < 3) return; if (plystack) { if (n-3 >= plystack->n) panic(); s = plystack; mp = &plystack->mv[n-3]; } else { if (n-2 >= playlist.n) panic(); s = &playlist; mp = &playlist.mv[n-2]; } if (v && !*mp) s->nm ++; if (!v && *mp) s->nm --; *mp = v; } static void dl_ply_markall(void) { if (plystack) { memset(plystack->mv,1,plystack->n); plystack->nm = plystack->n; } else { memset(playlist.mv+1,1,playlist.n-1); playlist.nm = playlist.n - 1; } } static void dl_ply_reorder(int *order) { int n; int i; int j; PLE **ev; int emn; char *mv; char *flg; PLE **nev; char *nmv; PLYSTK *s; int nwt; int ncur; n = dl_ply_n(); if (n < 2) return; j = n - 1; for (i=n-1;i>2;i--) { if (order[i] >= 3) { order[j] = order[i] - 3; j --; } } if (plystack) { ev = plystack->v; mv = plystack->mv; emn = plystack->n; s = plystack; } else { ev = playlist.v + 1; mv = playlist.mv + 1; emn = playlist.n - 1; s = &playlist; } order += 3; flg = malloc(emn); bzero(flg,emn); nev = malloc(emn*sizeof(*nev)); nmv = malloc(emn); nwt = -1; ncur = -1; for (i=emn-1;i>=0;i--) { if ((order[i] < 0) || (order[i] >= emn)) panic(); nev[i] = ev[order[i]]; nmv[i] = mv[order[i]]; if (s->wt == order[i]+3) { if (nwt >= 0) panic(); nwt = i + 3; } if (s->cur == order[i]+3) { if (ncur >= 0) panic(); ncur = i + 3; } if (flg[order[i]]) panic(); flg[order[i]] = 1; } for (i=emn-1;i>=0;i--) if (! flg[i]) panic(); if ( ((nwt < 0) && (s->wt > 2)) || ((ncur < 0) && (s->cur > 2)) ) panic(); bcopy(nev,ev,emn*sizeof(*ev)); bcopy(nmv,mv,emn); if (nwt >= 0) s->wt = nwt; if (ncur >= 0) s->cur = ncur; free(flg); free(nev); free(nmv); if (! plystack) reset_pending_playlist(); } static void dl_ply_subset(int n, int *vec) { int maxn; int i; int j; PLE **ov; PLYSTK *os; PLYSTK *ns; char *flg; int v; j = 0; for (i=0;i= 3) { if (j != i) vec[j] = vec[i]; j ++; } } n = j; maxn = dl_ply_n(); if (plystack) { os = plystack; ov = plystack->v; } else { os = &playlist; ov = playlist.v + 1; } ns = plystk_new(n,0); flg = malloc(n); bzero(flg,n); ns->wt = -1; ns->cur = -1; for (i=n-1;i>=0;i--) { if (flg[i]) panic(); v = vec[i]; if ((v < 3) || (v >= maxn)) panic(); ns->v[i] = ov[v-3]; if (v == os->wt) { if (ns->wt >= 0) panic(); ns->wt = i + 3; } if (v == os->cur) { if (ns->cur >= 0) panic(); ns->cur = i + 3; } } ns->link = plystack; plystack = ns; } static void dl_ply_list_save(int (*want)(int)) { int i; PLE **v; int n; if (plystack) { v = plystack->v; } else { v = playlist.v + 1; } n = dl_ply_n(); for (i=3;iv,plylist.n*sizeof(*s->v)); } static void dl_ply_list_clear(void) { plylist.n = 0; } static int dl_ply_list_n(void) { return(plylist.n); } static void dl_ply_stackpop(void) { PLYSTK *s; if (! plystack) panic(); s = plystack; plystack = s->link; plystk_free(s,1); } static int dl_ply_stackdepth(void) { int n; PLYSTK *s; n = 0; for (s=plystack;s;s=s->link) n ++; return(n); } static int dl_ply_keystroke(unsigned char ks) { int i; PLE *e; switch (ks) { case 'R': if (tgdstack) { plystack_clear(); mark_clear(); dlo = &dlo_tgs; } else { plystack_clear(); dlo = &dlo_cds; } return(1); break; case 'I': i = dl_ply_cur(); if (i < 0) panic(); switch (i) { case 0: scroll_timed("No info available for past tracks."); return(1); break; case 1: case 2: if (playlist.n < 1) panic(); e = playlist.v[0]; break; default: if (plystack) { i -= 3; if (i >= plystack->n) panic(); e = plystack->v[i]; } else { i -= 2; if (i >= playlist.n) panic(); e = playlist.v[i]; } break; } show_info(e->cd,e->tg); return(1); break; } return(0); } static int dl_ply_longcmd( char *cmd __attribute__((__unused__)), int cmdlen __attribute__((__unused__)), char *args __attribute__((__unused__)), int argslen __attribute__((__unused__)) ) { if ((cmdlen == 3) && !bcmp(cmd,"use",3)) { replace_playlist(); return(1); } return(0); } static void dl_ply_help(void) { scroll_space("R - return to track or CD list, as appropriate"); scroll_space("I - show info on the current track"); } static void dl_ply_longhelp(void) { scroll_space(":use - replace queued playlist with this list"); } static DLOPS dlo_ply = DLO_INIT(ply); static char *build_savefile_name(const char *suf) { static char home_unset; static char *home = &home_unset; char *s; if (home == &home_unset) home = getenv("HOME"); if (! home) return(0); asprintf(&s,"%s/%s%s",home,savefile_name,suf); return(s); } static TG *find_leaf_tg(TG *tg, TRACK *t) { int i; TG *f; switch (tg->type) { case TGT_LEAF: if (tg->leaf == t) return(tg); break; case TGT_GROUP: for (i=tg->group.n-1;i>=0;i--) { f = find_leaf_tg(tg->group.v[i],t); if (f) return(f); } break; } return(0); } static int restore_playlist_entry(const char *cdname, int trk) { CDLIST *l; CDINFO *inf; TG *tg; int i; do <"found"> { for (l=allcds;l;l=l->link) if (!strcmp(l->cd->text,cdname)) break <"found">; scroll_space("Restore: CD not found: %s",cdname); return(1); } while (0); inf = l->cd->info; if (! inf) { scroll_space("Restore: CD has no info: %s",cdname); return(1); } do <"found"> { for (i=inf->ntracks-1;i>=0;i--) if (inf->tracks[i].num == trk) break <"found">; scroll_space("Restore: no track %d found in CD %s",trk,cdname); return(1); } while (0); if (playlist.n < 1) playlist_append(&playlist,0,0); tg = find_leaf_tg(l->cd->trkroot,&inf->tracks[i]); if (! tg) { scroll_space("Restore: can't find track %d (#%d) in CD %s",trk,i,cdname); return(1); } playlist_append(&playlist,l->cd,tg); return(0); } static void save_file_read(void) { char *fn; FILE *f; int magiclen; char *mcmp; char *cd; int cda; int cdl; int trn; int n1; int n2; int ps; PLE *e; f = 0; mcmp = 0; cd = 0; cda = 0; do <"done"> { do <"err"> { fn = build_savefile_name(""); if (! fn) { scroll_space("Restore: build_savefile_name(\"\") failed"); break <"err">; } f = fopen(fn,"r"); if (f == 0) { scroll_space("Restore: open %s: %s",fn,strerror(errno)); break <"err">; } magiclen = strlen(savefile_v1_magic); mcmp = malloc(magiclen); if (mcmp == 0) break; if (fread(mcmp,1,magiclen,f) != magiclen) { scroll_space("Restore: read magic failed"); break <"err">; } if (bcmp(mcmp,savefile_v1_magic,magiclen)) { scroll_space("Restore: bad magic"); break <"err">; } n1 = -1; fscanf(f,"%d.%n",&ps,&n1); if (n1 < 0) { scroll_space("Restore: can't scan playing offset"); break <"err">; } while <"restore"> (1) { n1 = -1; n2 = -1; fscanf(f,"%d%n.%d.%n",&cdl,&n1,&trn,&n2); if (n1 < 0) break <"restore">; if (n2 < 0) { scroll_space("Restore: can't scan CD name length and track number"); break <"err">; } if (cdl+1 > cda) { cda = cdl + 1; free(cd); cd = malloc(cda); if (! cd) { scroll_space("Restore: can't malloc %d for CD name",cdl); break <"err">; } } if (fread(cd,1,cdl,f) != cdl) { scroll_space("Restore: can't read CD name"); break <"err">; } cd[cdl] = '\0'; if (restore_playlist_entry(cd,trn)) break <"err">; } ppi_status = ps; pause_playing(); reset_pending_playlist(); track_play_done(); if (ps > 0) playlist_seek_cur(ps); break <"done">; } while (0); while (playlist.n) { e = playlist_remove(&playlist); ple_free(e); } ppi_status = -1; } while (0); if (f) fclose(f); if (mcmp) free(mcmp); if (cd) free(cd); } static void save_file_write(void) { char *tfn; char *fn; FILE *f; int l; int i; fn = 0; tfn = 0; f = 0; do <"fail"> { fn = build_savefile_name(""); tfn = build_savefile_name(" TMP"); f = fopen(tfn,"w"); if (f == 0) return; l = strlen(savefile_v1_magic); if (fwrite(savefile_v1_magic,1,l,f) != l) break <"fail">; if (fprintf(f,"%d.",ppi_status) < 0) break <"fail">; for (i=0;itg->type != TGT_LEAF) abort(); if (fprintf(f,"%d.%d.%s",(int)strlen(playlist.v[i]->cd->text),playlist.v[i]->tg->leaf->num,playlist.v[i]->cd->text) < 0) break <"fail">; } if (fflush(f) == EOF) break <"fail">; if (fclose(f) == EOF) break <"fail">; f = 0; if (rename(tfn,fn) < 0) break <"fail">; free(tfn); free(fn); return; } while (0); if (f) fclose(f); if (tfn) { unlink(tfn); free(tfn); } if (fn) free(fn); } static int load_on_startup(void *arg __attribute__((__unused__))) { if (hostrunning) return(AIO_BLOCK_NIL); aio_remove_block(loadfile_id); loadfile_id = AIO_PL_NOID; save_file_read(); return(AIO_BLOCK_LOOP); } static int signal_watcher(void *arg __attribute__((__unused__))) { if (! got_fatal_sig) return(AIO_BLOCK_NIL); if (!hostrunning && (loadfile_id == AIO_PL_NOID)) save_file_write(); exit(0); } static void get_fatal_sig(int sig __attribute__((__unused__))) { got_fatal_sig = 1; } static void setup_signals(void) { struct sigaction sa; got_fatal_sig = 0; sa.sa_handler = &get_fatal_sig; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGINT,&sa,0); sigaction(SIGTERM,&sa,0); sigaction(SIGHUP,&sa,0); signal_id = aio_add_block(&signal_watcher,0); } static void startup(void) { initscr(); cbreak(); noecho(); loadfile_id = AIO_PL_NOID; allcds = 0; dlo = 0; cur_in_window = &ciw_move_wintop; lastsearchlen = -1; storage_host = 0; hostai_list = 0; hostai_info = 0; hostai_data = 0; (*dlo_cds.init)(); (*dlo_tgs.init)(); (*dlo_ply.init)(); msgtconc_init(&scroll_msgs); msgtconc_init(&timed_msgs); msgtconc_init(&space_msgs); msgtconc_init(&manual_msgs); playlist_init(&playlist); have_playlist_msgs = 0; playlist_last_m = -1; playlist_lastplay = strdup(""); playlist_shortstatus = strdup(""); scroll_id = aio_add_block(&ui_scroll_messages,0); update_id = aio_add_block(&ui_update,0); editing_line = 0; line_b0 = malloc(1); line_b1 = line_b0; line_pl = 0; line_a = 1; line_clear(); setup_signals(); onekey_fn = &onekey_default; stdin_id = aio_add_poll(0,&aio_rwtest_always,&aio_rwtest_never,&rd_stdin,0,0); scroll_space("Loading info from %s",STORE_HOST); switch_hosts(STORE_HOST,&startup_succ,&startup_fail); loadfile_id = aio_add_block(&load_on_startup,0); aio_oq_init(&play_oq); play_io_id = aio_add_poll(playerfd,&aio_rwtest_always,&wtest_player,&rd_player,&wr_player,0); paused = 0; audio_open = 0; process_player_input = &ppi_pkt_begin; dashes = 0; stars = 0; dashlen = -1; gen_dashes(); ignore_done_to_flush = 0; ppi_status = -1; } static void handleargs(int ac, char **av) { int errs; errs = 0; for (ac--,av++;ac;ac--,av++) { if (**av != '-') { fprintf(stderr,"%s: stray argument `%s'\n",__progname,*av); errs ++; continue; } if (!strcmp(*av,"-log")) { write_logs = 1; continue; } fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); open_dev_null(); setlocale(LC_ALL,""); aio_poll_init(); srandom(time(0)); childcloses = 0; fork_player(); fork_resolver(); startup(); while (1) { aio_pre_poll(); if (aio_do_poll() < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: poll: %s\n",__progname,strerror(errno)); exit(1); } aio_post_poll(); } return(0); }