1 /*
2 * Copyright (C) 2009-2014 Christian Heckendorf <heckendorfc@gmail.com>
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "util.h"
19 #include "defs.h"
20 #include "dbact.h"
21 #include "plugins/plugin.h"
22
experr(const char * epath,int eerrno)23 int experr(const char *epath, int eerrno){
24 fprintf(stderr,"error: %d on %s\n",eerrno,epath);
25 return 0;
26 }
27
isURL(const char * in)28 int isURL(const char *in){
29 return strncmp("http://",in,7)==0;
30 }
31
expand(char * in)32 char *expand(char *in){
33 char tmp[250];
34 char *in_ptr=in-1;
35 char *tmp_ptr=tmp;
36 int url=0,x;
37
38 if(isURL(in))url=1; /* Clean \n but do no globbing */
39
40 for(x=0;x<249 && in[x];x++);
41 if(x>0 && in[x-1]=='\n')
42 in[x-1]=0;
43 if(x>1 && in[x-2]=='\r')
44 in[x-2]=0;
45 in[x]=0;
46
47 if(url)return in;
48
49 if(in[0]=='~'){
50 strcpy(tmp,getenv("HOME"));
51 strcat(tmp,"/");
52 strcat(tmp,in+2);
53 strcpy(in,tmp);
54 }
55 else if(in[0]!='/'){
56 strcpy(tmp,getenv("PWD"));
57 strcat(tmp,"/");
58 if(in[0]=='.' && in[1]=='/')
59 strcat(tmp,in+2);
60 else
61 strcat(tmp,in);
62 strcpy(in,tmp);
63 }
64 while(*(++in_ptr)){
65 if(*in_ptr=='[' || *in_ptr=='\'' || *in_ptr=='\"')
66 *(tmp_ptr++)='\\';
67 *(tmp_ptr++)=*in_ptr;
68 }
69 *tmp_ptr=0;
70
71
72 glob_t pglob;
73 glob(tmp,GLOB_TILDE,&experr,&pglob);
74 if(pglob.gl_pathc>0){
75 memmove(tmp,pglob.gl_pathv[0],100);
76 debug(1,"Glob OK!");
77 }
78 else
79 debug(1,"No files match");
80 globfree(&pglob);
81 return in;
82 }
83
fileFormat(struct pluginitem ** list,const char * arg)84 int fileFormat(struct pluginitem **list, const char *arg){
85 FILE *ffd=NULL;
86 struct pluginitem **ret=list;
87 int type=PLUGIN_NULL;
88 int x=0;
89 int i,j;
90
91 // Find by magic numbers
92 for(i=0;type==PLUGIN_NULL && i<PLUGIN_NULL;i++){
93 if(ret[i]==NULL)
94 continue;
95
96 if((ffd=ret[i]->modopen(arg,"rb"))!=NULL){
97 if(ret[i]->moddata(ffd)){
98 type=i;
99 }
100 ret[i]->modclose(ffd);
101 }
102 }
103 if(ffd==NULL)
104 type=PLUGIN_NULL;
105
106 if(type!=PLUGIN_NULL){
107 return type;
108 }
109
110 // Find by extension
111 for(x=0;arg[x];x++);
112 for(;arg[x-1]!='.' && x>0;x--);
113 for(i=0;i<PLUGIN_NULL;i++){
114 if(ret[i]==NULL)
115 continue;
116
117 for(j=0;ret[i]->extension[j];j++)
118 if(strcasecmp(arg+x,ret[i]->extension[j])==0)
119 return i;
120 }
121
122 return PLUGIN_NULL;
123 }
124
125 #if 0
126 int getFileTypeByName(const char *name){
127 char query[200];
128 int type=0;
129
130 if(!name)return 0;
131
132 sprintf(query,"SELECT TypeID FROM FileType WHERE Name='%s' LIMIT 1",name);
133 harp_sqlite3_exec(conn,query,uint_return_cb,&type,NULL);
134
135 return type;
136 }
137
138 int findPluginIDByType(int type){
139 static int last_type=0;
140 static int index=0;
141 unsigned int id=0;
142 char query[150];
143
144 if(type==0 || (type!=last_type && last_type!=0)){
145 last_type=index=0;
146 if(type==0)return 0;
147 }
148 last_type=type;
149
150 sprintf(query,"SELECT PluginID FROM PluginType WHERE TypeID=%d LIMIT %d,1",type,index);
151
152 harp_sqlite3_exec(conn,query,uint_return_cb,&id,NULL);
153 index++;
154
155 return id;
156 }
157
158 struct pluginitem *findPluginByID(struct pluginitem *list, int id){
159 while(list && list->id!=id)
160 list=list->next;
161 return list;
162 }
163
164 int getPluginModule(void **module, char *lib){
165 char library[250];
166
167 sprintf(library,"%s/%s",LIB_PATH,lib);
168 debug(2,library);
169 dlerror();
170
171 *module=dlopen(library,RTLD_LAZY);
172 if(!*module){
173 fprintf(stderr,"Can't open plugin.\n\t%s\n",dlerror());
174 return 2;
175 }
176 else{
177 dlerror();
178 }
179
180 return 1;
181 }
182
183 int getPlugin(struct dbitem *dbi, const int index, void **module){
184 if(!fetch_row(dbi)){
185 if(dbi->current_row==dbi->column_count || dbi->row_count==0)
186 fprintf(stderr,"No plugins found.\n\tSee README for information about installing plugins.\n");
187 return 0;
188 }
189
190 return getPluginModule(module,dbi->row[index]);
191 }
192
193 struct pluginitem *closePlugin(struct pluginitem *head){
194 struct pluginitem *ptr=head;
195
196 if(!head)return NULL;
197
198 ptr=ptr->next;
199 if(head->module)
200 dlclose(head->module);
201 free(head);
202
203 return ptr;
204 }
205
206 void closePluginList(struct pluginitem *head){
207 struct pluginitem *ptr=head;
208
209 do{
210 ptr=closePlugin(ptr);
211 }while(ptr);
212 }
213
214 static int regPluginFunctions(struct pluginitem *node){
215 if(!node->module)return 1;
216
217 if(!(node->modopen=dlsym(node->module,"plugin_open")) ||
218 !(node->moddata=dlsym(node->module,"filetype_by_data")) ||
219 !(node->modmeta=dlsym(node->module,"plugin_meta")) ||
220 !(node->modplay=dlsym(node->module,"plugin_run")) ||
221 !(node->modseek=dlsym(node->module,"plugin_seek")) ||
222 !(node->modclose=dlsym(node->module,"plugin_close")))
223 return 1;
224
225 return 0;
226 }
227
228 static int open_plugin_cb(void *arg, int col_count, char **row, char **titles){
229 struct pluginitem **node = (struct pluginitem**)arg;
230 struct pluginitem *next;
231
232 if(!(next=malloc(sizeof(struct pluginitem)))){
233 debug(2,"Malloc failed (open_plugin_cb).");
234 return 1;
235 }
236 if(getPluginModule(&(next->module),row[1])!=1){
237 free(next);
238 return 1;
239 }
240 else{
241 if(regPluginFunctions(next)){
242 gree(nexu);
243 return 1;
244 }
245 else{
246 (*node)->next=next;
247 *node=next;
248 next->next=NULL;
249 next->id=strtol(row[0],NULL,10);
250 next->contenttype=strdup(row[2]);
251 }
252 }
253
254 return 0;
255 }
256
257 struct pluginitem **openPlugins(){
258 struct pluginitem prehead,*ptr;
259
260 return plugin_head;
261
262 prehead.next=NULL;
263 ptr=&prehead;
264 // Add order by active?
265 if(harp_sqlite3_exec(conn,"SELECT Plugin.PluginID,Plugin.Library,FileType.ContentType FROM Plugin NATURAL JOIN PluginType NATURAL JOIN FileType",open_plugin_cb,&ptr,NULL)!=SQLITE_OK){
266 closePluginList(prehead.next);
267 fprintf(stderr,"Error opening plugins.\n");
268 return NULL;
269 }
270
271 //return prehead.next; /* List starts at this next */
272 }
273 #endif
274
getFilename(const char * path)275 char *getFilename(const char *path){
276 char *filestart=(char *)path;
277 while(*path){
278 if(*(path++)=='/')
279 filestart=(char *)path;
280 }
281 return filestart;
282 }
283
printIDMatchRow(struct dbitem * dbi)284 static void printIDMatchRow(struct dbitem *dbi){
285 int x;
286 printf("%s\t%s",dbi->row[0],dbi->row[1]);
287 if(dbi->column_count>2){
288 printf("\t(%s",dbi->row[2]);
289 for(x=3;x<dbi->column_count;x++)
290 printf(": %s",dbi->row[x]);
291 printf(")");
292 }
293 printf("\n");
294 }
295
getBestIDMatch(struct dbitem * dbi,const char * argv)296 int getBestIDMatch(struct dbitem *dbi, const char *argv){
297 int id=0,found=0;
298 if(!dbi)return 0;
299
300 printf("Total matches for string '%s': %d\n",argv,dbi->row_count);
301 while(fetch_row(dbi)){
302 printIDMatchRow(dbi);
303 if(!strcmp(argv,dbi->row[1])){ // Perfect matches take priority
304 id=(int)strtol(dbi->row[0],NULL,10);
305 if(id)found++;
306 }
307 else if(!strcasecmp(argv,dbi->row[1])){ // Case-insensitive matches can be used
308 id=(int)strtol(dbi->row[0],NULL,10); // Continue to look for perfect matches
309 }
310 }
311 if(id && found==1)
312 printf("Using best match: %d\n",id);
313 else{
314 char choice[100];
315 printf("Please choose an ID: ");
316 if(!fgets(choice,sizeof(choice),stdin))
317 id=0;
318 else
319 id=(int)strtol(choice,NULL,10);
320 }
321 return id;
322 }
323
strToID(const char * argv)324 int strToID(const char *argv){ // TODO: add type param
325 char query[351];
326 char clean_str[200];
327 int id=0;
328 struct dbitem dbi;
329
330 db_safe(clean_str,argv,200);
331
332 if(!arglist[ATYPE].subarg)return -1;
333 dbiInit(&dbi);
334
335 switch(arglist[ATYPE].subarg[0]){
336 case 's':sprintf(query,"SELECT SongID,SongTitle,ArtistName,AlbumTitle FROM SongPubInfo WHERE SongTitle LIKE '%%%s%%'",clean_str);break;
337 case 'p':sprintf(query,"SELECT PlaylistID,Title FROM Playlist WHERE Title LIKE '%%%s%%'",clean_str);break;
338 case 'r':sprintf(query,"SELECT ArtistID,Name FROM Artist WHERE Name LIKE '%%%s%%'",clean_str);break;
339 case 'a':sprintf(query,"SELECT AlbumID,Title,Artist.Name FROM Album NATURAL JOIN AlbumArtist NATURAL JOIN Artist WHERE Title LIKE '%%%s%%'",clean_str);break;
340 case 'g':sprintf(query,"SELECT CategoryID,Name FROM Category WHERE Name LIKE '%%%s%%'",clean_str);break;
341 case 't':sprintf(query,"SELECT TagID,Value FROM Tag WHERE Value LIKE '%%%s%%'",clean_str);break;
342 default:return -1;
343 }
344
345 if(doQuery(query,&dbi)==1 && fetch_row(&dbi)){
346 id=(int)strtol(dbi.row[0],NULL,10);
347 }
348 else if(dbi.row_count>1)
349 id=getBestIDMatch(&dbi,argv);
350
351 dbiClean(&dbi);
352 return id;
353 }
354
verifyID(const int id)355 int verifyID(const int id){
356 char query[100];
357 struct dbitem dbi;
358 dbiInit(&dbi);
359 switch(arglist[ATYPE].subarg[0]){
360 case 's':sprintf(query,"SELECT SongID FROM Song WHERE SongID=%d",id);break;
361 case 'p':sprintf(query,"SELECT PlaylistID FROM Playlist WHERE PlaylistID=%d",id);break;
362 case 'r':sprintf(query,"SELECT ArtistID FROM Artist WHERE ArtistID=%d",id);break;
363 case 'a':sprintf(query,"SELECT AlbumID FROM Album WHERE AlbumID=%d",id);break;
364 case 'g':sprintf(query,"SELECT CategoryID FROM Category WHERE CategoryID=%d",id);break;
365 case 't':sprintf(query,"SELECT TagID FROM Tag WHERE TagID=%d",id);break;
366 default:return 0;
367 }
368 if(doQuery(query,&dbi)==1){
369 dbiClean(&dbi);
370 return 1;
371 }
372 dbiClean(&dbi);
373 return 0;
374 }
375
getID(const char * arg)376 int getID(const char *arg){
377 int id;
378 char *endptr;
379 if(arg==NULL){
380 printf("Required argument not provided\n");
381 return -1;
382 }
383 id=(int)strtol(arg,&endptr,10);
384 if(*endptr!=0){
385 if((id=strToID(arg))<1 ){
386 debug(1,"No ID found from given name.");
387 return -1;
388 }
389 }
390 if(!verifyID(id)){
391 debug(1,"No ID found.");
392 return -1;
393 }
394 return id;
395 }
396
getMulti(char * arg,int * length)397 int *getMulti(char *arg, int *length){
398 const char del[]=",";
399 char *token;
400 int *list,*ptr;
401
402 *length=1;
403 token=arg;
404 while(*token){
405 if(*(token++)==',')
406 (*length)++;
407 }
408 if(!(ptr=list=malloc(sizeof(int)*(*length)))){
409 debug(2,"Malloc failed (list).");
410 return NULL;
411 }
412
413 while((token=strsep(&arg,del))){
414 if(!((*ptr=(int)strtol(token,NULL,10))>0 && verifyID(*ptr))){
415 if(!(*ptr=strToID(token))){
416 continue;
417 }
418 }
419 ptr++;
420 }
421 if(ptr==list)*list=-1;
422
423 return list;
424 }
425
getGroupSongIDs(char * args,const int arglen,struct IDList * id_struct)426 int getGroupSongIDs(char *args, const int arglen, struct IDList *id_struct){
427 int y,x=0;
428 int *song_ids=malloc(sizeof(int));
429 int *group_ids=malloc(sizeof(int));
430 int song_idlen;
431 int group_idlen;
432 char query[200],query_tail[100],*ptr;
433 char temp=0;
434 struct dbitem dbi;
435 dbiInit(&dbi);
436 query_tail[0]=0;
437
438 /* Default should be hit. Avoid selecting another option. */
439 if(args[x+1]!=' '){
440 temp=args[x];
441 args[x]=' ';
442 }
443
444 switch(args[x]){
445 case 'a':
446 arglist[ATYPE].subarg[0]='a';
447 sprintf(query,"SELECT SongID FROM Song INNER JOIN Album ON Album.AlbumID=Song.AlbumID WHERE Album.AlbumID=");
448 sprintf(query_tail,"ORDER BY Track");
449 ptr=&query[91];
450 break;
451 case 'r':
452 arglist[ATYPE].subarg[0]='r';
453 sprintf(query,"SELECT SongID FROM Song NATURAL JOIN AlbumArtist WHERE ArtistID=");
454 sprintf(query_tail,"ORDER BY Song.AlbumID,Track");
455 ptr=&query[64];
456 break;
457 case 't':
458 arglist[ATYPE].subarg[0]='t';
459 sprintf(query,"SELECT Song.SongID FROM Song NATURAL JOIN SongTag WHERE TagID=");
460 sprintf(query_tail,"ORDER BY Song.AlbumID,Track");
461 ptr=&query[62];
462 break;
463 case 'g':
464 arglist[ATYPE].subarg[0]='g';
465 sprintf(query,"SELECT SongID FROM Song NATURAL JOIN SongCategory WHERE CategoryID=");
466 sprintf(query_tail,"ORDER BY Song.AlbumID,Track");
467 ptr=&query[67];
468 break;
469 case 's':
470 x++;
471 default:
472 if(temp)args[x]=temp;
473 // List of SongIDs.
474 for(;x<arglen && args[x] && args[x]==' ';x++);
475 arglist[ATYPE].subarg[0]='s';
476 if(!args[x] || !(song_ids=getMulti(&args[x],&song_idlen)) || song_ids[0]<1)
477 return HARP_RET_ERR;
478 id_struct->tempselectid=insertTempSelect(song_ids,song_idlen);
479 id_struct->songid=song_ids;
480 id_struct->length=song_idlen;
481 return HARP_RET_OK;
482 }
483
484 debug(3,query);
485 // Get SongIDs from album, artist, or genre
486 for(++x;x<arglen && args[x] && args[x]==' ';x++);
487 if(!(group_ids=getMulti(&args[x],&group_idlen)))return HARP_RET_ERR;
488 if(group_ids[0]<1){
489 fprintf(stderr,"No results found.\n");
490 free(group_ids);
491 return HARP_RET_ERR;
492 }
493 for(song_idlen=x=0;x<group_idlen;x++){
494 sprintf(ptr,"%d %s",group_ids[x],query_tail);
495 if(doQuery(query,&dbi)<1)continue;
496 y=song_idlen;
497 song_idlen+=dbi.row_count;
498 if(!(song_ids=realloc(song_ids,sizeof(int)*(song_idlen)))){
499 debug(2,"Realloc failed (song_ids).");
500 dbiClean(&dbi);
501 free(group_ids);
502 return HARP_RET_ERR;
503 }
504 for(;fetch_row(&dbi);y++)
505 song_ids[y]=(int)strtol(dbi.row[0],NULL,10);
506 }
507 dbiClean(&dbi);
508 free(group_ids); // Done with groups. We have the SongIDs.
509
510 if(!song_idlen){
511 fprintf(stderr,"No results found.\n");
512 free(song_ids);
513 return HARP_RET_ERR;
514 }
515 id_struct->tempselectid=insertTempSelect(song_ids,song_idlen);
516 id_struct->songid=song_ids;
517 id_struct->length=song_idlen;
518
519 return HARP_RET_OK;
520 }
521
cleanTempSelect(const int tempid)522 void cleanTempSelect(const int tempid){
523 char query[100];
524 sprintf(query,"DELETE FROM TempSelect WHERE TempID=%d",tempid);
525 harp_sqlite3_exec(conn,query,NULL,NULL,NULL);
526 }
527
createTempSelect()528 static void createTempSelect(){
529 harp_sqlite3_exec(conn,"CREATE TEMP TABLE IF NOT EXISTS TempSelect(TempSelectID integer not null primary key, TempID integer not null, SelectID integer not null)",NULL,NULL,NULL);
530 }
531
getNextTempSelectID()532 static int getNextTempSelectID(){
533 int tempid=0;
534 /* Replace with MAX(TempID)? */
535 if(harp_sqlite3_exec(conn,"SELECT TempID FROM TempSelect ORDER BY TempID DESC LIMIT 1",uint_return_cb,&tempid,NULL)==SQLITE_DONE)
536 return 1;
537 return ++tempid;
538 }
539
mergeTempSelect(int ida,int idb)540 int mergeTempSelect(int ida, int idb){
541 char query[150];
542
543 if(ida==idb)
544 return ida;
545
546 sprintf(query,"UPDATE TempSelect SET TempID=%d where TempID=%d",ida,idb);
547 harp_sqlite3_exec(conn,query,NULL,NULL,NULL);
548
549 return ida;
550 }
551
insertTempSelect(const int * ids,const int idlen)552 int insertTempSelect(const int *ids, const int idlen){
553 unsigned int x,currentlimit,tempid;
554 struct dbitem dbi;
555 dbiInit(&dbi);
556 char query[150];
557
558 createTempSelect();
559
560 tempid=getNextTempSelectID();
561
562 x=currentlimit=0;
563 while(x<idlen){
564 if((currentlimit+=DB_BATCH_SIZE)>idlen)currentlimit=idlen;
565 harp_sqlite3_exec(conn,"BEGIN",NULL,NULL,NULL);
566 for(;x<currentlimit;x++){
567 sprintf(query,"INSERT INTO TempSelect(TempID,SelectID) VALUES(%d,%d)",tempid,ids[x]);
568 harp_sqlite3_exec(conn,query,NULL,NULL,NULL);
569 }
570 harp_sqlite3_exec(conn,"COMMIT",NULL,NULL,NULL);
571 }
572
573 return tempid;
574 }
575
insertTempSelectQuery(const char * query)576 int insertTempSelectQuery(const char *query){
577 const char *ins_q="INSERT INTO TempSelect(TempID,SelectID) ";
578 unsigned int tempid;
579 char *temp_q,*ptr;
580
581 if(!(temp_q=malloc(strlen(ins_q)+strlen(query)+1))){
582 debug(2,"Malloc failed (tempquery)");
583 exit(1);
584 }
585
586 createTempSelect();
587
588 tempid=getNextTempSelectID();
589 sprintf(temp_q,query,tempid);
590 ptr=strdup(temp_q);
591 sprintf(temp_q,"%s%s",ins_q,ptr);
592 debug(2,temp_q);
593
594 harp_sqlite3_exec(conn,temp_q,NULL,NULL,NULL);
595
596 free(temp_q);
597 free(ptr);
598 return tempid;
599 }
600
insertTempSelectQueryCount(const char * query,int * count)601 int insertTempSelectQueryCount(const char *query,int *count){
602 int ret=insertTempSelectQuery(query);
603
604 *count=sqlite3_changes(conn);
605
606 return ret;
607 }
608
miClean(struct musicInfo * mi)609 void miClean(struct musicInfo *mi){
610 memset(mi->title,0,MI_TITLE_SIZE);
611 memset(mi->album,0,MI_ALBUM_SIZE);
612 memset(mi->artist,0,MI_ARTIST_SIZE);
613 memset(mi->year,0,MI_YEAR_SIZE);
614 memset(mi->track,0,MI_TRACK_SIZE);
615 mi->length=insertconf.length;
616 }
617
miFree(struct musicInfo * mi)618 void miFree(struct musicInfo *mi){
619 if(mi->title!=NULL)
620 free(mi->title);
621 if(mi->album!=NULL)
622 free(mi->album);
623 if(mi->artist!=NULL)
624 free(mi->artist);
625 if(mi->year!=NULL)
626 free(mi->year);
627 if(mi->track!=NULL)
628 free(mi->track);
629 }
630
db_clean(char * str,const char * data,const size_t size)631 void db_clean(char *str, const char *data, const size_t size){
632 int x;
633 for(x=0;*(data+x)==' ' && x<size;x++); // Strip starting spaces
634 while(*data && x++<size){ // Strip multi space
635 if(*data==' ' && *(data+1)==' '){
636 data++;
637 continue;
638 }
639 *(str++)=*(data++);
640 }
641 if(x>0 && *(str-1)==' ')str--; // Remove trailing space
642 *str=0;
643 }
644
db_safe(char * str,const char * data,const size_t size)645 void db_safe(char *str, const char *data, const size_t size){
646 int x=0;
647 while(*data && x++<size){ // Escape single quotes
648 if(*data=='\''){
649 *(str++)='\'';
650 }
651 *(str++)=*(data++);
652 }
653 *str=0;
654 }
655
656