├── config.rpath ├── m4 ├── .gitignore ├── libunistring.m4 └── blocks.m4 ├── src ├── mdns_avahi.c ├── rsp_query.h ├── ffmpeg_url_evbuffer.h ├── .gitignore ├── daap_query.h ├── avio_evbuffer.h ├── remote_pairing.h ├── httpd_rsp.h ├── httpd_daap.h ├── httpd_dacp.h ├── conffile.h ├── rng.h ├── filescanner.h ├── artwork.h ├── transcode.h ├── mdns.h ├── laudio.h ├── httpd.h ├── misc.h ├── logger.h ├── dacp_prop.gperf ├── network.h ├── daap_query.gperf ├── DAAP.g ├── rsp_query.gperf ├── dmap_common.h ├── avio_evbuffer.c ├── raop.h ├── player.h ├── filescanner_urlfile.c ├── ffmpeg_url_evbuffer.c ├── RSP.g ├── rng.c ├── Makefile.am ├── scan-mpc.c ├── rsp_query.c ├── daap_query.c ├── filescanner_m3u.c ├── http.h ├── evbuffer │ ├── evbuffer.h │ └── evbuffer.c ├── conffile.c ├── logger.c ├── scan-flac.c ├── DAAP2SQL.g ├── laudio_oss4.c ├── db.h └── RSP2SQL.g ├── NEWS ├── sqlext ├── Makefile.am └── sqlext.c ├── Makefile.am ├── AUTHORS ├── .gitignore ├── UPGRADING ├── forked-daapd.conf ├── forked-daapd.8 ├── ChangeLog ├── ffmpeg ├── ffmpeg-0.6.patch ├── libav-0.7.patch └── ffmpeg-0.5.tv.patch.sh ├── configure.in ├── INSTALL └── README /config.rpath: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /m4/.gitignore: -------------------------------------------------------------------------------- 1 | libtool.m4 2 | lt*.m4 3 | 4 | -------------------------------------------------------------------------------- /src/mdns_avahi.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonmc/forked-daapd/HEAD/src/mdns_avahi.c -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | forked-daapd NEWS 2 | ----------------- 3 | 4 | version 0.10: 5 | It's released, and that's news. Yup. 6 | -------------------------------------------------------------------------------- /src/rsp_query.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __RSP_QUERY_H__ 3 | #define __RSP_QUERY_H__ 4 | 5 | 6 | char * 7 | rsp_query_parse_sql(const char *rsp_query); 8 | 9 | #endif /* !__RSP_QUERY_H__ */ 10 | -------------------------------------------------------------------------------- /src/ffmpeg_url_evbuffer.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __FFMPEG_URL_EVBUFFER_H__ 3 | #define __FFMPEG_URL_EVBUFFER_H__ 4 | 5 | int 6 | register_ffmpeg_evbuffer_url_protocol(void); 7 | 8 | #endif /* !__FFMPEG_URL_EVBUFFER_H__ */ 9 | -------------------------------------------------------------------------------- /sqlext/Makefile.am: -------------------------------------------------------------------------------- 1 | pkglib_LTLIBRARIES = forked-daapd-sqlext.la 2 | 3 | forked_daapd_sqlext_la_SOURCES = sqlext.c 4 | forked_daapd_sqlext_la_LDFLAGS = -avoid-version -module -shared 5 | forked_daapd_sqlext_la_LIBADD = @LIBUNISTRING@ 6 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | forked-daapd 2 | 3 | *.tokens 4 | *Lexer.[ch] 5 | *Parser.[ch] 6 | DAAP2SQL.[ch] 7 | RSP2SQL.[ch] 8 | 9 | *.u 10 | 11 | daap_query_hash.c 12 | rsp_query_hash.c 13 | dacp_prop_hash.c 14 | dmap_fields_hash.c 15 | -------------------------------------------------------------------------------- /src/daap_query.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __DAAP_QUERY_H__ 3 | #define __DAAP_QUERY_H__ 4 | 5 | #include "logger.h" 6 | #include "misc.h" 7 | 8 | 9 | char * 10 | daap_query_parse_sql(const char *daap_query); 11 | 12 | #endif /* !__DAAP_QUERY_H__ */ 13 | -------------------------------------------------------------------------------- /src/avio_evbuffer.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __AVIO_EVBUFFER_H__ 3 | #define __AVIO_EVBUFFER_H__ 4 | 5 | AVIOContext * 6 | avio_evbuffer_open(struct evbuffer *evbuf); 7 | 8 | void 9 | avio_evbuffer_close(AVIOContext *s); 10 | 11 | #endif /* !__AVIO_EVBUFFER_H__ */ 12 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ACLOCAL_AMFLAGS = -I m4 2 | 3 | sysconf_DATA = forked-daapd.conf 4 | 5 | EXTRA_DIST = configure 6 | SUBDIRS = sqlext src 7 | 8 | man_MANS = forked-daapd.8 9 | 10 | install-data-hook: 11 | $(MKDIR_P) $(DESTDIR)$(localstatedir)/cache/forked-daapd 12 | 13 | -------------------------------------------------------------------------------- /src/remote_pairing.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __REMOTE_PAIRING_H__ 3 | #define __REMOTE_PAIRING_H__ 4 | 5 | void 6 | remote_pairing_read_pin(char *path); 7 | 8 | int 9 | remote_pairing_init(void); 10 | 11 | void 12 | remote_pairing_deinit(void); 13 | 14 | #endif /* !__REMOTE_PAIRING_H__ */ 15 | -------------------------------------------------------------------------------- /src/httpd_rsp.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __HTTPD_RSP_H__ 3 | #define __HTTPD_RSP_H__ 4 | 5 | #include "http.h" 6 | 7 | int 8 | rsp_init(void); 9 | 10 | void 11 | rsp_deinit(void); 12 | 13 | int 14 | rsp_request(struct http_connection *c, struct http_request *req, struct http_response *r); 15 | 16 | int 17 | rsp_is_request(char *uri); 18 | 19 | #endif /* !__HTTPD_RSP_H__ */ 20 | -------------------------------------------------------------------------------- /src/httpd_daap.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __HTTPD_DAAP_H__ 3 | #define __HTTPD_DAAP_H__ 4 | 5 | #include "http.h" 6 | 7 | int 8 | daap_init(void); 9 | 10 | void 11 | daap_deinit(void); 12 | 13 | int 14 | daap_request(struct http_connection *c, struct http_request *req, struct http_response *r); 15 | 16 | int 17 | daap_is_request(char *uri); 18 | 19 | #endif /* !__HTTPD_DAAP_H__ */ 20 | -------------------------------------------------------------------------------- /src/httpd_dacp.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __HTTPD_DACP_H__ 3 | #define __HTTPD_DACP_H__ 4 | 5 | #include "http.h" 6 | 7 | int 8 | dacp_init(void); 9 | 10 | void 11 | dacp_deinit(void); 12 | 13 | int 14 | dacp_request(struct http_connection *c, struct http_request *req, struct http_response *r); 15 | 16 | int 17 | dacp_is_request(char *uri); 18 | 19 | #endif /* !__HTTPD_DACP_H__ */ 20 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | forked-daapd is a fork/rewrite of mt-daapd (Firefly Media Server) 2 | written by Julien BLACHE . 3 | 4 | mt-daapd (Firefly Media Server) was originally written by 5 | Ron Pedde and a handful of contributors. 6 | 7 | Contributors to forked-daapd include: 8 | - Ace Jones 9 | - Dustin King 10 | - Kai Elwert 11 | 12 | -------------------------------------------------------------------------------- /src/conffile.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __CONFFILE_H__ 3 | #define __CONFFILE_H__ 4 | 5 | #include 6 | 7 | #include 8 | 9 | #define CONFFILE CONFDIR "/forked-daapd.conf" 10 | 11 | extern cfg_t *cfg; 12 | extern uint64_t libhash; 13 | extern uid_t runas_uid; 14 | extern gid_t runas_gid; 15 | 16 | int 17 | conffile_load(char *file); 18 | 19 | void 20 | conffile_unload(void); 21 | 22 | #endif /* !__CONFFILE_H__ */ 23 | -------------------------------------------------------------------------------- /src/rng.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __RNG_H__ 3 | #define __RNG_H__ 4 | 5 | struct rng_ctx { 6 | int32_t iy; 7 | int32_t iv[32]; /* shuffle array */ 8 | int32_t seed; 9 | }; 10 | 11 | 12 | void 13 | rng_init(struct rng_ctx *ctx); 14 | 15 | int32_t 16 | rng_rand(struct rng_ctx *ctx); 17 | 18 | int32_t 19 | rng_rand_range(struct rng_ctx *ctx, int32_t min, int32_t max); 20 | 21 | void 22 | shuffle_ptr(struct rng_ctx *ctx, void **values, int len); 23 | 24 | #endif /* !__RNG_H__ */ 25 | 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | Makefile.in 4 | Makefile 5 | *.o 6 | *.lo 7 | *.a 8 | *.la 9 | .deps/ 10 | .libs/ 11 | 12 | # autofoo stuff 13 | autom4te.cache 14 | aclocal.m4 15 | compile 16 | config.guess 17 | config.h 18 | config.h.in 19 | config.log 20 | config.status 21 | config.sub 22 | configure 23 | depcomp 24 | install-sh 25 | libtool 26 | ltmain.sh 27 | missing 28 | stamp-h1 29 | autotools-stamp 30 | build-stamp 31 | 32 | # ignore debian packaging for convenience 33 | debian/ 34 | 35 | -------------------------------------------------------------------------------- /src/filescanner.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __FILESCANNER_H__ 3 | #define __FILESCANNER_H__ 4 | 5 | #include "db.h" 6 | 7 | int 8 | filescanner_init(void); 9 | 10 | void 11 | filescanner_deinit(void); 12 | 13 | /* Actual scanners */ 14 | int 15 | scan_metadata_ffmpeg(char *file, struct media_file_info *mfi); 16 | 17 | int 18 | scan_url_file(char *file, struct media_file_info *mfi); 19 | 20 | void 21 | scan_m3u_playlist(char *file); 22 | 23 | #ifdef ITUNES 24 | void 25 | scan_itunes_itml(char *file); 26 | #endif 27 | 28 | #endif /* !__FILESCANNER_H__ */ 29 | -------------------------------------------------------------------------------- /src/artwork.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __ARTWORK_H__ 3 | #define __ARTWORK_H__ 4 | 5 | #define ART_CAN_PNG (1 << 0) 6 | #define ART_CAN_JPEG (1 << 1) 7 | 8 | #define ART_FMT_PNG 1 9 | #define ART_FMT_JPEG 2 10 | 11 | 12 | int 13 | artwork_get_item_filename(char *filename, int max_w, int max_h, int format, struct evbuffer *evbuf); 14 | 15 | int 16 | artwork_get_item(int id, int max_w, int max_h, int format, struct evbuffer *evbuf); 17 | 18 | int 19 | artwork_get_group(int id, int max_w, int max_h, int format, struct evbuffer *evbuf); 20 | 21 | #endif /* !__ARTWORK_H__ */ 22 | -------------------------------------------------------------------------------- /src/transcode.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __TRANSCODE_H__ 3 | #define __TRANSCODE_H__ 4 | 5 | #include "http.h" 6 | 7 | struct transcode_ctx; 8 | 9 | int 10 | transcode(struct transcode_ctx *ctx, struct evbuffer *evbuf, int wanted); 11 | 12 | int 13 | transcode_seek(struct transcode_ctx *ctx, int ms); 14 | 15 | struct transcode_ctx * 16 | transcode_setup(struct media_file_info *mfi, off_t *est_size, int wavhdr); 17 | 18 | void 19 | transcode_cleanup(struct transcode_ctx *ctx); 20 | 21 | int 22 | transcode_needed(struct http_request *req, char *file_codectype); 23 | 24 | #endif /* !__TRANSCODE_H__ */ 25 | -------------------------------------------------------------------------------- /src/mdns.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __MDNS_H__ 3 | #define __MDNS_H__ 4 | 5 | #include "misc.h" 6 | 7 | #define MDNS_WANT_V4 (1 << 0) 8 | #define MDNS_WANT_V4LL (1 << 1) 9 | #define MDNS_WANT_V6 (1 << 2) 10 | #define MDNS_WANT_V6LL (1 << 3) 11 | 12 | #define MDNS_WANT_DEFAULT (MDNS_WANT_V4 | MDNS_WANT_V6 | MDNS_WANT_V6LL) 13 | 14 | typedef void (* mdns_browse_cb)(const char *name, const char *type, const char *domain, const char *hostname, int family, const char *address, int port, struct keyval *txt); 15 | 16 | /* mDNS interface functions */ 17 | /* Call only from the main thread */ 18 | int 19 | mdns_init(void); 20 | 21 | void 22 | mdns_deinit(void); 23 | 24 | int 25 | mdns_register(char *name, char *type, int port, char **txt); 26 | 27 | int 28 | mdns_browse(char *type, int flags, mdns_browse_cb cb); 29 | 30 | #endif /* !__MDNS_H__ */ 31 | -------------------------------------------------------------------------------- /src/laudio.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __LAUDIO_H__ 3 | #define __LAUDIO_H__ 4 | 5 | #define LAUDIO_F_STARTED (1 << 15) 6 | 7 | enum laudio_state 8 | { 9 | LAUDIO_CLOSED = 0, 10 | LAUDIO_STOPPING = 1, 11 | LAUDIO_OPEN = 2, 12 | LAUDIO_STARTED = LAUDIO_F_STARTED, 13 | LAUDIO_RUNNING = LAUDIO_F_STARTED | 0x01, 14 | 15 | LAUDIO_FAILED = -1, 16 | }; 17 | 18 | typedef void (*laudio_status_cb)(enum laudio_state status); 19 | 20 | void 21 | laudio_write(uint8_t *buf, uint64_t rtptime); 22 | 23 | uint64_t 24 | laudio_get_pos(void); 25 | 26 | void 27 | laudio_set_volume(int vol); 28 | 29 | int 30 | laudio_start(uint64_t cur_pos, uint64_t next_pkt); 31 | 32 | void 33 | laudio_stop(void); 34 | 35 | int 36 | laudio_open(void); 37 | 38 | void 39 | laudio_close(void); 40 | 41 | int 42 | laudio_init(laudio_status_cb cb); 43 | 44 | void 45 | laudio_deinit(void); 46 | 47 | #endif /* !__LAUDIO_H__ */ 48 | -------------------------------------------------------------------------------- /UPGRADING: -------------------------------------------------------------------------------- 1 | Upgrading forked-daapd 2 | ---------------------- 3 | 4 | From time to time, newer versions of forked-daapd may need to perform a 5 | database upgrade. This upgrade is handled by forked-daapd upon startup if 6 | required. 7 | 8 | Before upgrading forked-daapd, it is always a good idea to backup your 9 | database, just in case. 10 | 11 | The database upgrade procedure is built into forked-daapd; there is no 12 | external upgrade script to run. Depending on the changes done to the database 13 | structure, the upgrade process will take more or less time and may need some 14 | space in /tmp for temporary data. The upgrade can also require some more space 15 | in the directory containing the database file. 16 | 17 | Before running the new forked-daapd version, make sure you have done your 18 | backups and checked your disk space. 19 | 20 | Some upgrades can also trigger a full rescan to rebuild parts of the database, 21 | so startup will be a bit slower and more resource-intensive than usual. 22 | -------------------------------------------------------------------------------- /src/httpd.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __HTTPD_H__ 3 | #define __HTTPD_H__ 4 | 5 | #include "evbuffer/evbuffer.h" 6 | #include "http.h" 7 | 8 | 9 | struct httpd_hdl { 10 | struct http_connection *c; 11 | struct http_request *req; 12 | struct http_response *r; 13 | 14 | struct keyval *query; 15 | }; 16 | 17 | 18 | int 19 | httpd_stream_file(struct http_connection *c, struct http_request *req, struct http_response *r, int id); 20 | 21 | int 22 | httpd_send_reply(struct http_connection *c, struct http_request *req, struct http_response *r, struct evbuffer *evbuf); 23 | 24 | int 25 | httpd_send_error(struct http_connection *c, struct http_response *r, int code, char *reason); 26 | 27 | char * 28 | httpd_fixup_uri(struct http_request *req); 29 | 30 | int 31 | httpd_basic_auth(struct http_connection *c, struct http_request *req, struct http_response *r, char *user, char *passwd, char *realm); 32 | 33 | int 34 | httpd_init(void); 35 | 36 | void 37 | httpd_deinit(void); 38 | 39 | #endif /* !__HTTPD_H__ */ 40 | -------------------------------------------------------------------------------- /src/misc.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __MISC_H__ 3 | #define __MISC_H__ 4 | 5 | #include 6 | 7 | 8 | struct onekeyval { 9 | char *name; 10 | char *value; 11 | 12 | struct onekeyval *next; 13 | }; 14 | 15 | struct keyval { 16 | struct onekeyval *head; 17 | struct onekeyval *tail; 18 | }; 19 | 20 | 21 | int 22 | safe_atoi32(const char *str, int32_t *val); 23 | 24 | int 25 | safe_atou32(const char *str, uint32_t *val); 26 | 27 | int 28 | safe_hextou32(const char *str, uint32_t *val); 29 | 30 | int 31 | safe_atoi64(const char *str, int64_t *val); 32 | 33 | int 34 | safe_atou64(const char *str, uint64_t *val); 35 | 36 | int 37 | safe_hextou64(const char *str, uint64_t *val); 38 | 39 | 40 | /* Key/value functions */ 41 | int 42 | keyval_add(struct keyval *kv, const char *name, const char *value); 43 | 44 | int 45 | keyval_add_size(struct keyval *kv, const char *name, const char *value, size_t size); 46 | 47 | void 48 | keyval_remove(struct keyval *kv, const char *name); 49 | 50 | const char * 51 | keyval_get(struct keyval *kv, const char *name); 52 | 53 | void 54 | keyval_clear(struct keyval *kv); 55 | 56 | 57 | char * 58 | m_realpath(const char *pathname); 59 | 60 | char * 61 | unicode_fixup_string(char *str); 62 | 63 | uint32_t 64 | djb_hash(void *data, size_t len); 65 | 66 | char * 67 | b64_decode(const char *b64); 68 | 69 | char * 70 | b64_encode(uint8_t *in, size_t len); 71 | 72 | uint64_t 73 | murmur_hash64(const void *key, int len, uint32_t seed); 74 | 75 | #endif /* !__MISC_H__ */ 76 | -------------------------------------------------------------------------------- /src/logger.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __LOGGER_H__ 3 | #define __LOGGER_H__ 4 | 5 | #include 6 | 7 | /* Log domains */ 8 | #define L_CONF 0 9 | #define L_DAAP 1 10 | #define L_DB 2 11 | #define L_HTTPD 3 12 | #define L_MAIN 4 13 | #define L_MDNS 5 14 | #define L_MISC 6 15 | #define L_RSP 7 16 | #define L_SCAN 8 17 | #define L_XCODE 9 18 | /* 10 - free */ 19 | #define L_REMOTE 11 20 | #define L_DACP 12 21 | #define L_FFMPEG 13 22 | #define L_ART 14 23 | #define L_PLAYER 15 24 | #define L_RAOP 16 25 | #define L_LAUDIO 17 26 | #define L_DMAP 18 27 | #define L_DBPERF 19 28 | #define L_HTTP 20 29 | 30 | #define N_LOGDOMAINS 21 31 | 32 | /* Severities */ 33 | #define E_FATAL 0 34 | #define E_LOG 1 35 | #define E_WARN 2 36 | #define E_INFO 3 37 | #define E_DBG 4 38 | #define E_SPAM 5 39 | 40 | 41 | void 42 | DPRINTF(int severity, int domain, const char *fmt, ...) __attribute__((format(printf, 3, 4))); 43 | 44 | void 45 | logger_ffmpeg(void *ptr, int level, const char *fmt, va_list ap); 46 | 47 | #ifdef LAUDIO_USE_ALSA 48 | void 49 | logger_alsa(const char *file, int line, const char *function, int err, const char *fmt, ...); 50 | #endif 51 | 52 | void 53 | logger_reinit(void); 54 | 55 | void 56 | logger_domains(void); 57 | 58 | void 59 | logger_detach(void); 60 | 61 | int 62 | logger_start_dispatch(int sync); 63 | 64 | int 65 | logger_init(char *file, char *domains, int severity); 66 | 67 | void 68 | logger_deinit(void); 69 | 70 | 71 | #endif /* !__LOGGER_H__ */ 72 | -------------------------------------------------------------------------------- /src/dacp_prop.gperf: -------------------------------------------------------------------------------- 1 | %language=ANSI-C 2 | %readonly-tables 3 | %enum 4 | %switch=1 5 | %compare-lengths 6 | %define hash-function-name dacp_hash_prop 7 | %define lookup-function-name dacp_find_prop 8 | %define slot-name desc 9 | %struct-type 10 | %omit-struct-type 11 | struct dacp_prop_map; 12 | %% 13 | "dmcp.volume", dacp_propget_volume, dacp_propset_volume 14 | "dacp.playerstate", dacp_propget_playerstate, NULL 15 | "dacp.nowplaying", dacp_propget_nowplaying, NULL 16 | "dacp.playingtime", dacp_propget_playingtime, dacp_propset_playingtime 17 | "dacp.volumecontrollable", dacp_propget_volumecontrollable, NULL 18 | "dacp.availableshufflestates", dacp_propget_availableshufflestates, NULL 19 | "dacp.availablerepeatstates", dacp_propget_availablerepeatstates, NULL 20 | "dacp.shufflestate", dacp_propget_shufflestate, dacp_propset_shufflestate 21 | "dacp.repeatstate", dacp_propget_repeatstate, dacp_propset_repeatstate 22 | "dacp.userrating", NULL, dacp_propset_userrating 23 | "dacp.fullscreenenabled", dacp_propget_fullscreenenabled, NULL 24 | "dacp.fullscreen", dacp_propget_fullscreen, NULL 25 | "dacp.visualizerenabled", dacp_propget_visualizerenabled, NULL 26 | "dacp.visualizer", dacp_propget_visualizer, NULL 27 | "com.apple.itunes.itms-songid", dacp_propget_itms_songid, NULL 28 | "com.apple.itunes.has-chapter-data", dacp_propget_haschapterdata, NULL 29 | -------------------------------------------------------------------------------- /forked-daapd.conf: -------------------------------------------------------------------------------- 1 | 2 | general { 3 | # Username 4 | uid = "daapd" 5 | logfile = "/var/log/forked-daapd.log" 6 | # Database location 7 | # db_path = "/var/cache/forked-daapd/songs3.db" 8 | # Available levels: fatal, log, warning, info, debug, spam 9 | loglevel = log 10 | # Admin password for the non-existent web interface 11 | admin_password = "unused" 12 | # Enable/disable IPv6 13 | ipv6 = no 14 | } 15 | 16 | # Library configuration 17 | library { 18 | # Name of the library as displayed by the clients 19 | # %h: hostname, %v: version 20 | name = "My Music on %h" 21 | # TCP port to listen on. Default port is 3689 (daap) 22 | port = 3689 23 | # Password for the library. Optional. 24 | # password = "" 25 | 26 | # Directories to index 27 | directories = { "/srv/music" } 28 | # Directories containing compilations 29 | # Matches anywhere in the path (not a regexp, though) 30 | # compilations = { "/compilations/" } 31 | 32 | # Should iTunes metadata override ours? 33 | # itunes_overrides = true 34 | 35 | # Formats: mp4a, mp4v, mpeg, alac, flac, mpc, ogg, wma, wmal, wmav, aif, wav 36 | # Formats that should never be transcoded 37 | # no_transcode = { "alac", "mp4a" } 38 | # Formats that should always be transcoded 39 | # force_transcode = { "ogg", "flac" } 40 | } 41 | 42 | # Local audio output 43 | audio { 44 | # AirTunes name - used in the speaker list in Remote 45 | nickname = "Computer" 46 | # Audio device name for local audio output 47 | # card = "default" 48 | # Mixer channel to use for volume control - ALSA/Linux only 49 | # If not set, PCM will be used if available, otherwise Master. 50 | # mixer = "" 51 | } 52 | 53 | # Airport Express device 54 | #apex "ApEx" { 55 | # AirTunes password 56 | # password = "s1kr3t" 57 | #} 58 | -------------------------------------------------------------------------------- /forked-daapd.8: -------------------------------------------------------------------------------- 1 | .\" -*- nroff -*- 2 | .TH FORKED-DAAPD "8" "2010-07-18" "forked-daapd" "RSP & DAAP media server" 3 | .SH NAME 4 | mt\-daapd \- RSP & iTunes\-compatible DAAP server 5 | .SH SYNOPSIS 6 | .B forked-daapd 7 | [\fIoptions\fR] 8 | .SH DESCRIPTION 9 | \fBforked\-daapd\fP is an RSP (Roku Streaming Protocol) and DAAP (Digital 10 | Audio Access Protocol) server. It allows you to share your music 11 | collection over the local network using the RSP protocol used by 12 | devices from Roku and/or the DAAP protocol also used by Apple's 13 | iTunes. 14 | .SH OPTIONS 15 | .TP 16 | \fB\-d\fR \fIlevel\fP 17 | Log level (0\-5). 18 | .TP 19 | \fB\-D\fR \fIdom,..,dom\fP 20 | Debug domains; available domains are: \fIconfig\fP, \fIdaap\fP, 21 | \fIdb\fP, \fIhttpd\fP, \fImain\fP, \fImdns\fP, \fImisc\fP, 22 | \fIrsp\fP, \fIscan\fP, \fIxcode\fP, \fIevent\fP. 23 | .TP 24 | \fB\-s\fR 25 | Synchronous logging; logging is asynchronous by default. Use when 26 | debugging, especially if debugging a crash. 27 | .TP 28 | \fB\-c\fR \fIfile\fP 29 | Use \fIfile\fP as the configuration file. 30 | .TP 31 | \fB\-P\fR \fIfile\fP 32 | Write PID to \fIfile\fP. 33 | .TP 34 | \fB\-f\fR 35 | Run in the foreground. 36 | .TP 37 | \fB\-b\fR \fIffid\fP 38 | \fIffid\fP to be broadcast in mDNS records. 39 | .TP 40 | \fB\-v\fR 41 | Display version information. 42 | .SH FILES 43 | .nf 44 | \fI/etc/forked\-daapd.conf\fR 45 | \fI/var/cache/forked\-daapd\fR 46 | .fi 47 | .SH AUTHORS 48 | \fBforked\-daapd\fP was written by Julien BLACHE as a 49 | fork/rewrite of mt\-daapd. mt\-daapd was written by Ron Pedde 50 | . 51 | .PP 52 | This manual page was written by Rog\['e]rio Brito 53 | and Julien BLACHE 54 | for the Debian project (but may be used by others). 55 | -------------------------------------------------------------------------------- /src/network.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __NETWORK_H__ 3 | #define __NETWORK_H__ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "evbuffer/evbuffer.h" 13 | 14 | union sockaddr_all { 15 | struct sockaddr_storage ss; 16 | struct sockaddr sa; 17 | struct sockaddr_in sin; 18 | struct sockaddr_in6 sin6; 19 | }; 20 | 21 | #define SOCKADDR_LEN(x) ((x.ss.ss_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)) 22 | #define NCONN_ADDRSTRLEN (INET6_ADDRSTRLEN + IF_NAMESIZE + 2) 23 | 24 | struct nconn; 25 | 26 | typedef void (*nconn_write_cb)(struct nconn *n, void *data); 27 | typedef void (*nconn_read_cb)(struct nconn *n, int fd, size_t estimated, void *data); 28 | typedef void (*nconn_fail_cb)(void *data); 29 | 30 | 31 | void 32 | nconn_free(struct nconn *n); 33 | 34 | void 35 | nconn_close_and_free(struct nconn *n); 36 | 37 | int 38 | nconn_running(struct nconn *n); 39 | 40 | int 41 | nconn_get_local_addrstr(struct nconn *n, char *buf); 42 | 43 | int 44 | nconn_get_remote_addrstr(struct nconn *n, char *buf); 45 | 46 | struct nconn * 47 | nconn_outgoing_new(int ldomain, dispatch_group_t user_group, dispatch_queue_t user_queue, const char *address, unsigned short port); 48 | 49 | struct nconn * 50 | nconn_listen_new(int ldomain, dispatch_group_t user_group, dispatch_queue_t user_queue, const char *address, unsigned short port); 51 | 52 | struct nconn * 53 | nconn_incoming_new(struct nconn *passive, dispatch_group_t group, dispatch_queue_t queue); 54 | 55 | int 56 | nconn_start(struct nconn *n, void *data, nconn_read_cb read_cb, nconn_write_cb write_cb, nconn_fail_cb fail_cb); 57 | 58 | int 59 | nconn_write(struct nconn *n, struct evbuffer *evbuf); 60 | 61 | #endif /* !__NETWORK_H__ */ 62 | -------------------------------------------------------------------------------- /m4/libunistring.m4: -------------------------------------------------------------------------------- 1 | # libunistring.m4 serial 4 2 | dnl Copyright (C) 2009-2010 Free Software Foundation, Inc. 3 | dnl This file is free software; the Free Software Foundation 4 | dnl gives unlimited permission to copy and/or distribute it, 5 | dnl with or without modifications, as long as this notice is preserved. 6 | 7 | dnl gl_LIBUNISTRING 8 | dnl Searches for an installed libunistring. 9 | dnl If found, it sets and AC_SUBSTs HAVE_LIBUNISTRING=yes and the LIBUNISTRING 10 | dnl and LTLIBUNISTRING variables and augments the CPPFLAGS variable, and 11 | dnl #defines HAVE_LIBUNISTRING to 1. Otherwise, it sets and AC_SUBSTs 12 | dnl HAVE_LIBUNISTRING=no and LIBUNISTRING and LTLIBUNISTRING to empty. 13 | 14 | AC_DEFUN([gl_LIBUNISTRING], 15 | [ 16 | dnl First, try to link without -liconv. libunistring often depends on 17 | dnl libiconv, but we don't know (and often don't need to know) where 18 | dnl libiconv is installed. 19 | AC_LIB_HAVE_LINKFLAGS([unistring], [], 20 | [#include ], [u8_strconv_from_locale((char*)0);], 21 | [no, consider installing GNU libunistring]) 22 | if test "$ac_cv_libunistring" != yes; then 23 | dnl Second try, with -liconv. 24 | AC_REQUIRE([AM_ICONV]) 25 | if test -n "$LIBICONV"; then 26 | dnl We have to erase the cached result of the first AC_LIB_HAVE_LINKFLAGS 27 | dnl invocation, otherwise the second one will not be run. 28 | unset ac_cv_libunistring 29 | glus_save_LIBS="$LIBS" 30 | LIBS="$LIBS $LIBICONV" 31 | AC_LIB_HAVE_LINKFLAGS([unistring], [], 32 | [#include ], [u8_strconv_from_locale((char*)0);], 33 | [no, consider installing GNU libunistring]) 34 | if test -n "$LIBUNISTRING"; then 35 | LIBUNISTRING="$LIBUNISTRING $LIBICONV" 36 | LTLIBUNISTRING="$LTLIBUNISTRING $LTLIBICONV" 37 | fi 38 | LIBS="$glus_save_LIBS" 39 | fi 40 | fi 41 | ]) 42 | -------------------------------------------------------------------------------- /src/daap_query.gperf: -------------------------------------------------------------------------------- 1 | %language=ANSI-C 2 | %readonly-tables 3 | %enum 4 | %switch=1 5 | %compare-lengths 6 | %define hash-function-name daap_query_field_hash 7 | %define lookup-function-name daap_query_field_lookup 8 | %define slot-name dmap_field 9 | %struct-type 10 | %omit-struct-type 11 | struct dmap_query_field_map; 12 | %% 13 | "dmap.itemname", "f.title", 0 14 | "dmap.itemid", "f.id", 1 15 | "daap.songalbum", "f.album", 0 16 | "daap.songalbumid", "f.songalbumid", 1 17 | "daap.songartist", "f.artist", 0 18 | "daap.songalbumartist", "f.album_artist", 0 19 | "daap.songbitrate", "f.bitrate", 1 20 | "daap.songcomment", "f.comment", 0 21 | "daap.songcompilation", "f.compilation", 1 22 | "daap.songcomposer", "f.composer", 0 23 | "daap.songdatakind", "f.data_kind", 1 24 | "daap.songdataurl", "f.url", 0 25 | "daap.songdateadded", "f.time_added", 1 26 | "daap.songdatemodified", "f.time_modified", 1 27 | "daap.songdescription", "f.description", 0 28 | "daap.songdisccount", "f.total_discs", 1 29 | "daap.songdiscnumber", "f.disc", 1 30 | "daap.songformat", "f.type", 0 31 | "daap.songgenre", "f.genre", 0 32 | "daap.songsamplerate", "f.samplerate", 1 33 | "daap.songsize", "f.file_size", 1 34 | "daap.songstoptime", "f.song_length", 1 35 | "daap.songtime", "f.song_length", 1 36 | "daap.songtrackcount", "f.total_tracks", 1 37 | "daap.songtracknumber", "f.track", 1 38 | "daap.songyear", "f.year", 1 39 | "com.apple.itunes.mediakind", "f.media_kind", 1 40 | "com.apple.itunes.extended-media-kind", "f.media_kind", 1 41 | -------------------------------------------------------------------------------- /src/DAAP.g: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2010 Julien BLACHE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | grammar DAAP; 20 | 21 | options { 22 | output = AST; 23 | ASTLabelType = pANTLR3_BASE_TREE; 24 | language = C; 25 | } 26 | 27 | query : expr NEWLINE? EOF -> expr 28 | ; 29 | 30 | expr : aexpr (OPOR^ aexpr)* 31 | ; 32 | 33 | aexpr : crit (OPAND^ crit)* 34 | ; 35 | 36 | crit : LPAR expr RPAR -> expr 37 | | STR 38 | ; 39 | 40 | QUOTE : '\''; 41 | LPAR : '('; 42 | RPAR : ')'; 43 | 44 | OPAND : '+'; 45 | OPOR : ','; 46 | 47 | NEWLINE : '\r'? '\n'; 48 | 49 | /* 50 | Unescaping adapted from (ported to the C runtime) 51 | 52 | */ 53 | STR 54 | @init{ pANTLR3_STRING unesc = GETTEXT()->factory->newRaw(GETTEXT()->factory); } 55 | : QUOTE ( reg = ~('\\' | '\'') { unesc->addc(unesc, reg); } 56 | | esc = ESCAPED { unesc->appendS(unesc, GETTEXT()); } )+ QUOTE { SETTEXT(unesc); }; 57 | 58 | fragment 59 | ESCAPED : '\\' 60 | ( '\\' { SETTEXT(GETTEXT()->factory->newStr8(GETTEXT()->factory, (pANTLR3_UINT8)"\\")); } 61 | | '\'' { SETTEXT(GETTEXT()->factory->newStr8(GETTEXT()->factory, (pANTLR3_UINT8)"\'")); } 62 | ) 63 | ; 64 | -------------------------------------------------------------------------------- /m4/blocks.m4: -------------------------------------------------------------------------------- 1 | AC_DEFUN([DISPATCH_C_BLOCKS], [ 2 | # 3 | # Allow configure to be passed a path to the directory where it should look 4 | # for the Blocks runtime library, if any. 5 | # 6 | AC_ARG_WITH([blocks-runtime], 7 | [AS_HELP_STRING([--with-blocks-runtime], 8 | [Specify path to the blocks runtime])], 9 | [blocks_runtime=${withval} 10 | LIBS="$LIBS -L$blocks_runtime"] 11 | ) 12 | 13 | # 14 | # Detect compiler support for Blocks; perhaps someday -fblocks won't be 15 | # required, in which case we'll need to change this. 16 | # 17 | AC_CACHE_CHECK([for C Blocks support], [dispatch_cv_cblocks], [ 18 | saveCFLAGS="$CFLAGS" 19 | CFLAGS="$CFLAGS -fblocks" 20 | AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[(void)^{int i; i = 0; }();])], [ 21 | CFLAGS="$saveCFLAGS" 22 | dispatch_cv_cblocks="-fblocks" 23 | ], [ 24 | CFLAGS="$saveCFLAGS" 25 | dispatch_cv_cblocks="no" 26 | ]) 27 | ]) 28 | 29 | AS_IF([test "x$dispatch_cv_cblocks" != "xno"], [ 30 | CBLOCKS_FLAGS="$dispatch_cv_cblocks" 31 | 32 | # 33 | # It may be necessary to directly link the Blocks runtime on some 34 | # systems, so give it a try if we can't link a C program that uses 35 | # Blocks. We will want to remove this at somepoint, as really -fblocks 36 | # should force that linkage already. 37 | # 38 | saveCFLAGS="$CFLAGS" 39 | CFLAGS="$CFLAGS -fblocks -O0" 40 | AC_MSG_CHECKING([whether additional libraries are required for the Blocks runtime]) 41 | AC_TRY_LINK([], [ 42 | ^{ int j; j=0; }(); 43 | ], [ 44 | AC_MSG_RESULT([no]); 45 | ], [ 46 | saveLIBS="$LIBS" 47 | LIBS="$LIBS -lBlocksRuntime" 48 | AC_TRY_LINK([], [ 49 | ^{ int k; k=0; }(); 50 | ], [ 51 | AC_MSG_RESULT([-lBlocksRuntime]) 52 | ], [ 53 | AC_MSG_ERROR([can't find Blocks runtime]) 54 | ]) 55 | ]) 56 | CFLAGS="$saveCFLAGS" 57 | have_cblocks=true 58 | ], [ 59 | CBLOCKS_FLAGS="" 60 | have_cblocks=false 61 | ]) 62 | AM_CONDITIONAL(HAVE_CBLOCKS, $have_cblocks) 63 | AC_SUBST([CBLOCKS_FLAGS]) 64 | ]) 65 | -------------------------------------------------------------------------------- /src/rsp_query.gperf: -------------------------------------------------------------------------------- 1 | %language=ANSI-C 2 | %readonly-tables 3 | %enum 4 | %switch=1 5 | %compare-lengths 6 | %define hash-function-name rsp_query_field_hash 7 | %define lookup-function-name rsp_query_field_lookup 8 | %define slot-name rsp_field 9 | %struct-type 10 | %omit-struct-type 11 | struct rsp_query_field_map; 12 | %% 13 | "id", RSP_TYPE_INT 14 | "path", RSP_TYPE_STRING 15 | "fname", RSP_TYPE_STRING 16 | "title", RSP_TYPE_STRING 17 | "artist", RSP_TYPE_STRING 18 | "album", RSP_TYPE_STRING 19 | "genre", RSP_TYPE_STRING 20 | "comment", RSP_TYPE_STRING 21 | "type", RSP_TYPE_STRING 22 | "composer", RSP_TYPE_STRING 23 | "orchestra", RSP_TYPE_STRING 24 | "grouping", RSP_TYPE_STRING 25 | "url", RSP_TYPE_STRING 26 | "bitrate", RSP_TYPE_INT 27 | "samplerate", RSP_TYPE_INT 28 | "song_length", RSP_TYPE_INT 29 | "file_size", RSP_TYPE_INT 30 | "year", RSP_TYPE_INT 31 | "track", RSP_TYPE_INT 32 | "total_tracks", RSP_TYPE_INT 33 | "disc", RSP_TYPE_INT 34 | "total_discs", RSP_TYPE_INT 35 | "bpm", RSP_TYPE_INT 36 | "compilation", RSP_TYPE_INT 37 | "rating", RSP_TYPE_INT 38 | "play_count", RSP_TYPE_INT 39 | "data_kind", RSP_TYPE_INT 40 | "item_kind", RSP_TYPE_INT 41 | "description", RSP_TYPE_STRING 42 | "time_added", RSP_TYPE_DATE 43 | "time_modified", RSP_TYPE_DATE 44 | "time_played", RSP_TYPE_DATE 45 | "db_timestamp", RSP_TYPE_DATE 46 | "sample_count", RSP_TYPE_INT 47 | "codectype", RSP_TYPE_STRING 48 | "idx", RSP_TYPE_INT 49 | "has_video", RSP_TYPE_INT 50 | "contentrating", RSP_TYPE_INT 51 | "bits_per_sample", RSP_TYPE_INT 52 | "album_artist", RSP_TYPE_STRING 53 | -------------------------------------------------------------------------------- /src/dmap_common.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __DMAP_HELPERS_H__ 3 | #define __DMAP_HELPERS_H__ 4 | 5 | #include "evbuffer/evbuffer.h" 6 | 7 | #include "db.h" 8 | #include "httpd.h" 9 | 10 | enum dmap_type 11 | { 12 | DMAP_TYPE_UBYTE = 0x01, 13 | DMAP_TYPE_BYTE = 0x02, 14 | DMAP_TYPE_USHORT = 0x03, 15 | DMAP_TYPE_SHORT = 0x04, 16 | DMAP_TYPE_UINT = 0x05, 17 | DMAP_TYPE_INT = 0x06, 18 | DMAP_TYPE_ULONG = 0x07, 19 | DMAP_TYPE_LONG = 0x08, 20 | DMAP_TYPE_STRING = 0x09, 21 | DMAP_TYPE_DATE = 0x0a, 22 | DMAP_TYPE_VERSION = 0x0b, 23 | DMAP_TYPE_LIST = 0x0c, 24 | }; 25 | 26 | struct dmap_field_map { 27 | ssize_t mfi_offset; 28 | ssize_t pli_offset; 29 | ssize_t gri_offset; 30 | }; 31 | 32 | struct dmap_field { 33 | char *desc; 34 | char *tag; 35 | const struct dmap_field_map *dfm; 36 | enum dmap_type type; 37 | }; 38 | 39 | 40 | extern const struct dmap_field_map dfm_dmap_mimc; 41 | extern const struct dmap_field_map dfm_dmap_aeSP; 42 | 43 | 44 | const struct dmap_field * 45 | dmap_get_fields_table(int *nfields); 46 | 47 | /* From dmap_fields.gperf - keep in sync, don't alter */ 48 | const struct dmap_field * 49 | dmap_find_field (register const char *str, register unsigned int len); 50 | 51 | 52 | void 53 | dmap_add_container(struct evbuffer *evbuf, char *tag, int len); 54 | 55 | void 56 | dmap_add_long(struct evbuffer *evbuf, char *tag, int64_t val); 57 | 58 | void 59 | dmap_add_int(struct evbuffer *evbuf, char *tag, int val); 60 | 61 | void 62 | dmap_add_short(struct evbuffer *evbuf, char *tag, short val); 63 | 64 | void 65 | dmap_add_char(struct evbuffer *evbuf, char *tag, char val); 66 | 67 | void 68 | dmap_add_literal(struct evbuffer *evbuf, char *tag, char *str, int len); 69 | 70 | void 71 | dmap_add_string(struct evbuffer *evbuf, char *tag, const char *str); 72 | 73 | void 74 | dmap_add_field(struct evbuffer *evbuf, const struct dmap_field *df, char *strval, int32_t intval); 75 | 76 | 77 | int 78 | dmap_send_error(struct httpd_hdl *h, char *container, char *errmsg); 79 | 80 | 81 | int 82 | dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_media_file_info *dbmfi, const struct dmap_field **meta, int nmeta, int sort_tags, int force_wav); 83 | 84 | #endif /* !__DMAP_HELPERS_H__ */ 85 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | ChangeLog for forked-daapd 2 | -------------------------- 3 | 4 | version 0.19gcd: 5 | - no GCD-specific changes. 6 | 7 | version 0.19: 8 | - more libav 0.7 updates. 9 | - database speedups. 10 | - fix for iTunes 30-minute timeout. 11 | - fixes, big and small. 12 | 13 | version 0.18gcd: 14 | - build fix for FreeBSD, from Raivo Hool. 15 | 16 | version 0.18: 17 | - add config knob for ALSA mixer channel name. 18 | - do not elevate privileges for reopening the log file; log file 19 | will now be owned by the user forked-daapd runs as. 20 | - fixes, big and small. 21 | 22 | version 0.17gcd: 23 | - initial release of the GCD codebase. 24 | 25 | version 0.17: 26 | - support for libav 0.7 27 | - fixes, big and small. 28 | 29 | version 0.16: 30 | - fix issue with non-UTF-8 metadata while scanning. 31 | - use proper file size in HTTP streaming code. 32 | - fix DAAP songlist bug with sort tags. 33 | - small code fixes. 34 | 35 | version 0.15: 36 | - add support for sending metadata to AppleTV during AirTunes streaming. 37 | - support DOS-encoded Remote pairing files. 38 | - rework album_artist_sort handling. 39 | - enable RAOP to queue RTSP requests. 40 | - speedup DAAP & RSP filters processing. 41 | - speedup DAAP songlist generation. 42 | - artwork can handle and send out both PNG and JPEG. 43 | - fixes, big and small. 44 | 45 | version 0.14: 46 | - sort headers/tags handling improvements. 47 | - better handling of tags for TV shows. 48 | - better handling of DRM-afflicted files. 49 | - configurable IPv6 support. 50 | - fix scanning of URL files. 51 | - fixes, big and small. 52 | 53 | version 0.13: 54 | - add Remote v2 support; Remote v1 is not supported anymore. 55 | - add per-speaker volume support. 56 | - implement RAOP retransmission. 57 | - implement per-device quirks in RAOP. 58 | - improve compatibility with 802.11g AirPort Express. 59 | - improve mDNS address resolution, making IPv4 usable again. 60 | - fix Murmur Hash bug on 32bit platforms. 61 | - add support for JPEG artwork and alternative filenames. 62 | - disable session expiration that was causing issues. 63 | - FFmpeg 0.6 support. 64 | - fixes, big and small. 65 | 66 | version 0.12: 67 | - add AirTunes v2 streaming. 68 | - add Remote support. 69 | - add gzipped replies. 70 | - add IPv6 support. 71 | - check for UTF-8 correctness of metadata. 72 | - fixes, big and small. 73 | 74 | version 0.11: 75 | - support iTunes 9. 76 | - add iTunes XML playlist scanner. 77 | - add support for TV shows. 78 | - add FreeBSD and GNU/kFreeBSD support. 79 | - add support for DAAP groups. 80 | - add support for artwork. 81 | - rework metdata extraction, better support for ID3 tags. 82 | - database code rework. 83 | - preliminary support for Remote (pairing, browsing). 84 | - fixes, big and small. 85 | 86 | version 0.10: 87 | - initial release. 88 | -------------------------------------------------------------------------------- /src/avio_evbuffer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Julien BLACHE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | #ifdef HAVE_CONFIG_H 20 | # include 21 | #endif 22 | 23 | #include 24 | 25 | #include 26 | 27 | #include "evbuffer/evbuffer.h" 28 | #include "logger.h" 29 | #include "avio_evbuffer.h" 30 | 31 | /* 32 | * libav AVIO interface for evbuffers 33 | */ 34 | 35 | #define BUFFER_SIZE 4096 36 | 37 | struct avio_evbuffer { 38 | struct evbuffer *evbuf; 39 | uint8_t *buffer; 40 | }; 41 | 42 | 43 | static int 44 | avio_evbuffer_write(void *opaque, uint8_t *buf, int size) 45 | { 46 | struct avio_evbuffer *ae; 47 | int ret; 48 | 49 | ae = (struct avio_evbuffer *)opaque; 50 | 51 | ret = evbuffer_add(ae->evbuf, buf, size); 52 | 53 | return (ret == 0) ? size : -1; 54 | } 55 | 56 | AVIOContext * 57 | avio_evbuffer_open(struct evbuffer *evbuf) 58 | { 59 | struct avio_evbuffer *ae; 60 | AVIOContext *s; 61 | 62 | ae = (struct avio_evbuffer *)malloc(sizeof(struct avio_evbuffer)); 63 | if (!ae) 64 | { 65 | DPRINTF(E_LOG, L_FFMPEG, "Out of memory for avio_evbuffer\n"); 66 | 67 | return NULL; 68 | } 69 | 70 | ae->buffer = av_mallocz(BUFFER_SIZE); 71 | if (!ae->buffer) 72 | { 73 | DPRINTF(E_LOG, L_FFMPEG, "Out of memory for avio buffer\n"); 74 | 75 | free(ae); 76 | return NULL; 77 | } 78 | 79 | ae->evbuf = evbuf; 80 | 81 | s = avio_alloc_context(ae->buffer, BUFFER_SIZE, 1, ae, NULL, avio_evbuffer_write, NULL); 82 | if (!s) 83 | { 84 | DPRINTF(E_LOG, L_FFMPEG, "Could not allocate AVIOContext\n"); 85 | 86 | av_free(ae->buffer); 87 | free(ae); 88 | return NULL; 89 | } 90 | 91 | s->seekable = 0; 92 | 93 | return s; 94 | } 95 | 96 | void 97 | avio_evbuffer_close(AVIOContext *s) 98 | { 99 | struct avio_evbuffer *ae; 100 | 101 | ae = (struct avio_evbuffer *)s->opaque; 102 | 103 | avio_flush(s); 104 | 105 | av_free(ae->buffer); 106 | free(ae); 107 | 108 | av_free(s); 109 | } 110 | -------------------------------------------------------------------------------- /src/raop.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __RAOP_H__ 3 | #define __RAOP_H__ 4 | 5 | 6 | enum raop_devtype { 7 | RAOP_DEV_APEX_80211G, 8 | RAOP_DEV_APEX_80211N, 9 | RAOP_DEV_APPLETV, 10 | }; 11 | 12 | struct raop_session; 13 | 14 | struct raop_device 15 | { 16 | uint64_t id; 17 | char *name; 18 | 19 | char *v4_address; 20 | char *v6_address; 21 | short v4_port; 22 | short v6_port; 23 | 24 | enum raop_devtype devtype; 25 | 26 | unsigned selected:1; 27 | unsigned advertised:1; 28 | 29 | unsigned has_password:1; 30 | const char *password; 31 | 32 | int volume; 33 | int relvol; 34 | struct raop_session *session; 35 | 36 | struct raop_device *next; 37 | }; 38 | 39 | /* RAOP session state */ 40 | 41 | /* Session is starting up */ 42 | #define RAOP_F_STARTUP (1 << 15) 43 | 44 | /* Streaming is up (connection established) */ 45 | #define RAOP_F_CONNECTED (1 << 16) 46 | 47 | enum raop_session_state 48 | { 49 | RAOP_STOPPED = 0, 50 | 51 | /* Session startup */ 52 | RAOP_OPTIONS = RAOP_F_STARTUP | 0x01, 53 | RAOP_ANNOUNCE = RAOP_F_STARTUP | 0x02, 54 | RAOP_SETUP = RAOP_F_STARTUP | 0x03, 55 | RAOP_RECORD = RAOP_F_STARTUP | 0x04, 56 | 57 | /* Session established 58 | * - streaming ready (RECORD sent and acked, connection established) 59 | * - commands (SET_PARAMETER) are possible 60 | */ 61 | RAOP_CONNECTED = RAOP_F_CONNECTED, 62 | 63 | /* Audio data is being sent */ 64 | RAOP_STREAMING = RAOP_F_CONNECTED | 0x01, 65 | 66 | /* Session is failed, couldn't startup or error occurred */ 67 | RAOP_FAILED = -1, 68 | 69 | /* Password issue: unknown password or bad password */ 70 | RAOP_PASSWORD = -2, 71 | }; 72 | 73 | typedef void (*raop_status_cb)(struct raop_device *dev, struct raop_session *rs, enum raop_session_state status); 74 | 75 | 76 | void 77 | raop_metadata_purge(void); 78 | 79 | void 80 | raop_metadata_prune(uint64_t rtptime); 81 | 82 | 83 | int 84 | raop_device_probe(struct raop_device *rd, raop_status_cb cb); 85 | 86 | int 87 | raop_device_start(struct raop_device *rd, raop_status_cb cb, uint64_t rtptime); 88 | 89 | void 90 | raop_device_stop(struct raop_session *rs); 91 | 92 | void 93 | raop_playback_start(uint64_t next_pkt, struct timespec *ts); 94 | 95 | void 96 | raop_playback_stop(void); 97 | 98 | 99 | void 100 | raop_metadata_send(int id, uint64_t rtptime, uint64_t offset, int startup); 101 | 102 | int 103 | raop_set_volume_one(struct raop_session *rs, int volume, raop_status_cb cb); 104 | 105 | int 106 | raop_flush(raop_status_cb cb, uint64_t rtptime); 107 | 108 | 109 | void 110 | raop_set_status_cb(struct raop_session *rs, raop_status_cb cb); 111 | 112 | 113 | void 114 | raop_v2_write(uint8_t *buf, uint64_t rtptime); 115 | 116 | 117 | int 118 | raop_init(int *v6enabled); 119 | 120 | void 121 | raop_deinit(void); 122 | 123 | #endif /* !__RAOP_H__ */ 124 | -------------------------------------------------------------------------------- /src/player.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __PLAYER_H__ 3 | #define __PLAYER_H__ 4 | 5 | #include 6 | 7 | #if defined(__linux__) 8 | /* AirTunes v2 packet interval in ns */ 9 | # define AIRTUNES_V2_STREAM_PERIOD 7980000 10 | #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) 11 | /* AirTunes v2 packet interval in ms */ 12 | # define AIRTUNES_V2_STREAM_PERIOD 8 13 | #endif 14 | 15 | /* AirTunes v2 number of samples per packet */ 16 | #define AIRTUNES_V2_PACKET_SAMPLES 352 17 | 18 | 19 | /* Samples to bytes, bytes to samples */ 20 | #define STOB(s) ((s) * 4) 21 | #define BTOS(b) ((b) / 4) 22 | 23 | enum play_status { 24 | PLAY_STOPPED = 2, 25 | PLAY_PAUSED = 3, 26 | PLAY_PLAYING = 4, 27 | }; 28 | 29 | enum repeat_mode { 30 | REPEAT_OFF = 0, 31 | REPEAT_SONG = 1, 32 | REPEAT_ALL = 2, 33 | }; 34 | 35 | struct spk_flags { 36 | unsigned selected:1; 37 | unsigned has_password:1; 38 | 39 | unsigned has_video:1; 40 | }; 41 | 42 | struct player_status { 43 | enum play_status status; 44 | enum repeat_mode repeat; 45 | char shuffle; 46 | 47 | int volume; 48 | 49 | uint32_t plid; 50 | uint32_t id; 51 | uint32_t pos_ms; 52 | int pos_pl; 53 | }; 54 | 55 | typedef void (*spk_enum_cb)(uint64_t id, const char *name, int relvol, struct spk_flags flags, void *arg); 56 | typedef void (*player_status_handler)(void); 57 | 58 | struct player_source; 59 | 60 | 61 | int 62 | player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit); 63 | 64 | int 65 | player_get_status(struct player_status *status); 66 | 67 | int 68 | player_now_playing(uint32_t *id); 69 | 70 | 71 | void 72 | player_speaker_enumerate(spk_enum_cb cb, void *arg); 73 | 74 | int 75 | player_speaker_set(uint64_t *ids); 76 | 77 | int 78 | player_playback_start(uint32_t *idx_id); 79 | 80 | int 81 | player_playback_stop(void); 82 | 83 | int 84 | player_playback_pause(void); 85 | 86 | int 87 | player_playback_seek(int ms); 88 | 89 | int 90 | player_playback_next(void); 91 | 92 | int 93 | player_playback_prev(void); 94 | 95 | 96 | int 97 | player_volume_set(int vol); 98 | 99 | int 100 | player_volume_setrel_speaker(uint64_t id, int relvol); 101 | 102 | int 103 | player_volume_setabs_speaker(uint64_t id, int vol); 104 | 105 | int 106 | player_repeat_set(enum repeat_mode mode); 107 | 108 | int 109 | player_shuffle_set(int enable); 110 | 111 | 112 | struct player_source * 113 | player_queue_make_daap(const char *query, const char *sort); 114 | 115 | struct player_source * 116 | player_queue_make_pl(int plid, uint32_t *id); 117 | 118 | int 119 | player_queue_add(struct player_source *ps); 120 | 121 | void 122 | player_queue_clear(void); 123 | 124 | void 125 | player_queue_plid(uint32_t plid); 126 | 127 | 128 | void 129 | player_set_update_handler(player_status_handler handler); 130 | 131 | int 132 | player_init(void); 133 | 134 | void 135 | player_deinit(void); 136 | 137 | #endif /* !__PLAYER_H__ */ 138 | -------------------------------------------------------------------------------- /ffmpeg/ffmpeg-0.6.patch: -------------------------------------------------------------------------------- 1 | --- ffmpeg/libavformat/mov.c 2010-12-13 17:10:32.347224001 -0800 2 | +++ ffmpeg/libavformat/mov.c 2010-12-13 17:03:05.078969987 -0800 3 | @@ -78,19 +78,46 @@ 4 | 5 | static const MOVParseTableEntry mov_default_parse_table[]; 6 | 7 | -static int mov_metadata_trkn(MOVContext *c, ByteIOContext *pb, unsigned len) 8 | +static int mov_metadata_trkn(MOVContext *c, ByteIOContext *pb, unsigned len, const char *key) 9 | { 10 | char buf[16]; 11 | 12 | get_be16(pb); // unknown 13 | snprintf(buf, sizeof(buf), "%d", get_be16(pb)); 14 | - av_metadata_set2(&c->fc->metadata, "track", buf, 0); 15 | + av_metadata_set(&c->fc->metadata, key, buf); 16 | 17 | get_be16(pb); // total tracks 18 | 19 | return 0; 20 | } 21 | 22 | +static int mov_metadata_int8(MOVContext *c, ByteIOContext *pb, unsigned len, const char *key) 23 | +{ 24 | + char buf[16]; 25 | + 26 | + /* bypass padding bytes */ 27 | + get_byte(pb); 28 | + get_byte(pb); 29 | + get_byte(pb); 30 | + 31 | + snprintf(buf, sizeof(buf-1), "%hu", get_byte(pb)); 32 | + buf[sizeof(buf)-1] = 0; 33 | + av_metadata_set(&c->fc->metadata, key, buf); 34 | + 35 | + return 0; 36 | +} 37 | + 38 | +static int mov_metadata_stik(MOVContext *c, ByteIOContext *pb, unsigned len, const char *key) 39 | +{ 40 | + char buf[16]; 41 | + 42 | + snprintf(buf, sizeof(buf-1), "%hu", get_byte(pb)); 43 | + buf[sizeof(buf)-1] = 0; 44 | + av_metadata_set(&c->fc->metadata, key, buf); 45 | + 46 | + return 0; 47 | +} 48 | + 49 | static const uint32_t mac_to_unicode[128] = { 50 | 0x00C4,0x00C5,0x00C7,0x00C9,0x00D1,0x00D6,0x00DC,0x00E1, 51 | 0x00E0,0x00E2,0x00E4,0x00E3,0x00E5,0x00E7,0x00E9,0x00E8, 52 | @@ -137,7 +164,7 @@ 53 | const char *key = NULL; 54 | uint16_t str_size, langcode = 0; 55 | uint32_t data_type = 0; 56 | - int (*parse)(MOVContext*, ByteIOContext*, unsigned) = NULL; 57 | + int (*parse)(MOVContext*, ByteIOContext*, unsigned, const char *) = NULL; 58 | 59 | switch (atom.type) { 60 | case MKTAG(0xa9,'n','a','m'): key = "title"; break; 61 | @@ -159,6 +186,11 @@ 62 | case MKTAG( 't','v','s','h'): key = "show"; break; 63 | case MKTAG( 't','v','e','n'): key = "episode_id";break; 64 | case MKTAG( 't','v','n','n'): key = "network"; break; 65 | + case MKTAG( 't','v','e','s'): key = "episode_sort"; 66 | + case MKTAG( 't','v','s','n'): key = "season_number"; 67 | + parse = mov_metadata_int8; break; 68 | + case MKTAG( 's','t','i','k'): key = "stik"; 69 | + parse = mov_metadata_stik; break; 70 | case MKTAG( 't','r','k','n'): key = "track"; 71 | parse = mov_metadata_trkn; break; 72 | } 73 | @@ -195,7 +227,7 @@ 74 | str_size = FFMIN3(sizeof(str)-1, str_size, atom.size); 75 | 76 | if (parse) 77 | - parse(c, pb, str_size); 78 | + parse(c, pb, str_size, key); 79 | else { 80 | if (data_type == 3 || (data_type == 0 && langcode < 0x800)) { // MAC Encoded 81 | mov_read_mac_string(c, pb, str_size, str, sizeof(str)); 82 | -------------------------------------------------------------------------------- /ffmpeg/libav-0.7.patch: -------------------------------------------------------------------------------- 1 | --- libav/libavformat/mov.c 2011-08-09 14:03:47.622688230 -0700 2 | +++ libav/libavformat/mov.c 2011-08-09 14:09:33.181475099 -0700 3 | @@ -81,19 +81,46 @@ 4 | 5 | static const MOVParseTableEntry mov_default_parse_table[]; 6 | 7 | -static int mov_metadata_trkn(MOVContext *c, AVIOContext *pb, unsigned len) 8 | +static int mov_metadata_trkn(MOVContext *c, AVIOContext *pb, unsigned len, const char *key) 9 | { 10 | char buf[16]; 11 | 12 | avio_rb16(pb); // unknown 13 | snprintf(buf, sizeof(buf), "%d", avio_rb16(pb)); 14 | - av_dict_set(&c->fc->metadata, "track", buf, 0); 15 | + av_dict_set(&c->fc->metadata, key, buf, 0); 16 | 17 | avio_rb16(pb); // total tracks 18 | 19 | return 0; 20 | } 21 | 22 | +static int mov_metadata_int8(MOVContext *c, AVIOContext *pb, unsigned len, const char *key) 23 | +{ 24 | + char buf[16]; 25 | + 26 | + /* bypass padding bytes */ 27 | + get_byte(pb); 28 | + get_byte(pb); 29 | + get_byte(pb); 30 | + 31 | + snprintf(buf, sizeof(buf-1), "%hu", get_byte(pb)); 32 | + buf[sizeof(buf)-1] = 0; 33 | + av_metadata_set2(&c->fc->metadata, key, buf, 0); 34 | + 35 | + return 0; 36 | +} 37 | + 38 | +static int mov_metadata_stik(MOVContext *c, AVIOContext *pb, unsigned len, const char *key) 39 | +{ 40 | + char buf[16]; 41 | + 42 | + snprintf(buf, sizeof(buf-1), "%hu", get_byte(pb)); 43 | + buf[sizeof(buf)-1] = 0; 44 | + av_metadata_set2(&c->fc->metadata, key, buf, 0); 45 | + 46 | + return 0; 47 | +} 48 | + 49 | static const uint32_t mac_to_unicode[128] = { 50 | 0x00C4,0x00C5,0x00C7,0x00C9,0x00D1,0x00D6,0x00DC,0x00E1, 51 | 0x00E0,0x00E2,0x00E4,0x00E3,0x00E5,0x00E7,0x00E9,0x00E8, 52 | @@ -140,7 +167,7 @@ 53 | const char *key = NULL; 54 | uint16_t str_size, langcode = 0; 55 | uint32_t data_type = 0; 56 | - int (*parse)(MOVContext*, AVIOContext*, unsigned) = NULL; 57 | + int (*parse)(MOVContext*, AVIOContext*, unsigned, const char *) = NULL; 58 | 59 | switch (atom.type) { 60 | case MKTAG(0xa9,'n','a','m'): key = "title"; break; 61 | @@ -162,6 +189,11 @@ 62 | case MKTAG( 't','v','s','h'): key = "show"; break; 63 | case MKTAG( 't','v','e','n'): key = "episode_id";break; 64 | case MKTAG( 't','v','n','n'): key = "network"; break; 65 | + case MKTAG( 't','v','e','s'): key = "episode_sort"; 66 | + case MKTAG( 't','v','s','n'): key = "season_number"; 67 | + parse = mov_metadata_int8; break; 68 | + case MKTAG( 's','t','i','k'): key = "stik"; 69 | + parse = mov_metadata_stik; break; 70 | case MKTAG( 't','r','k','n'): key = "track"; 71 | parse = mov_metadata_trkn; break; 72 | } 73 | @@ -198,7 +230,7 @@ 74 | str_size = FFMIN3(sizeof(str)-1, str_size, atom.size); 75 | 76 | if (parse) 77 | - parse(c, pb, str_size); 78 | + parse(c, pb, str_size, key); 79 | else { 80 | if (data_type == 3 || (data_type == 0 && langcode < 0x800)) { // MAC Encoded 81 | mov_read_mac_string(c, pb, str_size, str, sizeof(str)); 82 | -------------------------------------------------------------------------------- /src/filescanner_urlfile.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2010 Julien BLACHE 3 | * 4 | * Rewritten from mt-daapd code: 5 | * Copyright (C) 2003 Ron Pedde (ron@pedde.com) 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | */ 21 | 22 | #ifdef HAVE_CONFIG_H 23 | # include 24 | #endif 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "logger.h" 33 | #include "db.h" 34 | #include "misc.h" 35 | #include "filescanner.h" 36 | 37 | 38 | int 39 | scan_url_file(char *file, struct media_file_info *mfi) 40 | { 41 | FILE *fp; 42 | char *head; 43 | char *tail; 44 | char buf[256]; 45 | size_t len; 46 | int ret; 47 | 48 | DPRINTF(E_DBG, L_SCAN, "Getting URL file info\n"); 49 | 50 | fp = fopen(file, "r"); 51 | if (!fp) 52 | { 53 | DPRINTF(E_WARN, L_SCAN, "Could not open '%s' for reading: %s\n", file, strerror(errno)); 54 | 55 | return -1; 56 | } 57 | 58 | head = fgets(buf, sizeof(buf), fp); 59 | fclose(fp); 60 | 61 | if (!head) 62 | { 63 | DPRINTF(E_WARN, L_SCAN, "Error reading from file '%s': %s", file, strerror(errno)); 64 | 65 | return -1; 66 | } 67 | 68 | len = strlen(buf); 69 | 70 | if (buf[len - 1] != '\n') 71 | { 72 | DPRINTF(E_WARN, L_SCAN, "URL info in file '%s' too large for buffer\n", file); 73 | 74 | return -1; 75 | } 76 | 77 | while (isspace(buf[len - 1])) 78 | { 79 | len--; 80 | buf[len] = '\0'; 81 | } 82 | 83 | tail = strchr(head, ','); 84 | if (!tail) 85 | { 86 | DPRINTF(E_LOG, L_SCAN, "Badly formatted .url file; expected format is bitrate,descr,url\n"); 87 | 88 | return -1; 89 | } 90 | 91 | head = tail + 1; 92 | tail = strchr(head, ','); 93 | if (!tail) 94 | { 95 | DPRINTF(E_LOG, L_SCAN, "Badly formatted .url file; expected format is bitrate,descr,url\n"); 96 | 97 | return -1; 98 | } 99 | *tail = '\0'; 100 | 101 | mfi->title = strdup(head); 102 | mfi->url = strdup(tail + 1); 103 | 104 | ret = safe_atou32(buf, &mfi->bitrate); 105 | if (ret < 0) 106 | { 107 | DPRINTF(E_WARN, L_SCAN, "Could not read bitrate\n"); 108 | 109 | return -1; 110 | } 111 | 112 | DPRINTF(E_DBG, L_SCAN," Title: %s\n", mfi->title); 113 | DPRINTF(E_DBG, L_SCAN," Bitrate: %d\n", mfi->bitrate); 114 | DPRINTF(E_DBG, L_SCAN," URL: %s\n", mfi->url); 115 | 116 | mfi->type = strdup("pls"); 117 | /* codectype = NULL */ 118 | mfi->description = strdup("Playlist URL"); 119 | 120 | return 0; 121 | } 122 | -------------------------------------------------------------------------------- /ffmpeg/ffmpeg-0.5.tv.patch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Patch for ffmpeg so forked-daapd can extract iTunes TV metadata from mp4 video files 4 | # Ace Jones 5 | # 6 | # Usage: chmod +x this file, and run it from the directory above where you want ffmpeg 7 | # 8 | svn checkout svn://svn.ffmpeg.org/ffmpeg/trunk ffmpeg 9 | patch -p0 < $0 10 | exit 11 | Index: ffmpeg/libavformat/mov.c 12 | =================================================================== 13 | --- ffmpeg/libavformat/mov.c (revision 20790) 14 | +++ ffmpeg/libavformat/mov.c (working copy) 15 | @@ -80,19 +80,45 @@ 16 | 17 | static const MOVParseTableEntry mov_default_parse_table[]; 18 | 19 | -static int mov_metadata_trkn(MOVContext *c, ByteIOContext *pb, unsigned len) 20 | +static int mov_metadata_trkn(MOVContext *c, ByteIOContext *pb, unsigned len, const char *key) 21 | { 22 | char buf[16]; 23 | 24 | get_be16(pb); // unknown 25 | snprintf(buf, sizeof(buf), "%d", get_be16(pb)); 26 | - av_metadata_set(&c->fc->metadata, "track", buf); 27 | + av_metadata_set(&c->fc->metadata, key, buf); 28 | 29 | get_be16(pb); // total tracks 30 | 31 | return 0; 32 | } 33 | 34 | +static int mov_metadata_int8(MOVContext *c, ByteIOContext *pb, unsigned len, const char *key) 35 | +{ 36 | + char buf[16]; 37 | + 38 | + /* bypass padding bytes */ 39 | + get_byte(pb); 40 | + get_byte(pb); 41 | + get_byte(pb); 42 | + 43 | + snprintf(buf, sizeof(buf-1), "%hu", get_byte(pb)); 44 | + buf[sizeof(buf)-1] = 0; 45 | + av_metadata_set(&c->fc->metadata, key, buf); 46 | + 47 | + return 0; 48 | +} 49 | + 50 | +static int mov_metadata_stik(MOVContext *c, ByteIOContext *pb, unsigned len, const char *key) 51 | +{ 52 | + char buf[16]; 53 | + 54 | + snprintf(buf, sizeof(buf-1), "%hu", get_byte(pb)); 55 | + buf[sizeof(buf)-1] = 0; 56 | + av_metadata_set(&c->fc->metadata, key, buf); 57 | + 58 | + return 0; 59 | +} 60 | static int mov_read_udta_string(MOVContext *c, ByteIOContext *pb, MOVAtom atom) 61 | { 62 | #ifdef MOV_EXPORT_ALL_METADATA 63 | @@ -101,7 +127,7 @@ 64 | char str[1024], key2[16], language[4] = {0}; 65 | const char *key = NULL; 66 | uint16_t str_size; 67 | - int (*parse)(MOVContext*, ByteIOContext*, unsigned) = NULL; 68 | + int (*parse)(MOVContext*, ByteIOContext*, unsigned, const char*) = NULL; 69 | 70 | switch (atom.type) { 71 | case MKTAG(0xa9,'n','a','m'): key = "title"; break; 72 | @@ -122,6 +148,12 @@ 73 | case MKTAG( 't','v','s','h'): key = "show"; break; 74 | case MKTAG( 't','v','e','n'): key = "episode_id";break; 75 | case MKTAG( 't','v','n','n'): key = "network"; break; 76 | + case MKTAG( 't','v','e','s'): key = "episode_sort"; 77 | + parse = mov_metadata_int8; break; 78 | + case MKTAG( 't','v','s','n'): key = "season_number"; 79 | + parse = mov_metadata_int8; break; 80 | + case MKTAG( 's','t','i','k'): key = "stik"; 81 | + parse = mov_metadata_stik; break; 82 | case MKTAG( 't','r','k','n'): key = "track"; 83 | parse = mov_metadata_trkn; break; 84 | } 85 | @@ -157,10 +189,11 @@ 86 | str_size = FFMIN3(sizeof(str)-1, str_size, atom.size); 87 | 88 | if (parse) 89 | - parse(c, pb, str_size); 90 | + parse(c, pb, str_size, key); 91 | else { 92 | get_buffer(pb, str, str_size); 93 | str[str_size] = 0; 94 | + 95 | av_metadata_set(&c->fc->metadata, key, str); 96 | if (*language && strcmp(language, "und")) { 97 | snprintf(key2, sizeof(key2), "%s-%s", key, language); 98 | -------------------------------------------------------------------------------- /src/ffmpeg_url_evbuffer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Julien BLACHE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | #ifdef HAVE_CONFIG_H 20 | # include 21 | #endif 22 | 23 | #include 24 | 25 | #include 26 | 27 | #include "evbuffer/evbuffer.h" 28 | #include "logger.h" 29 | #include "ffmpeg_url_evbuffer.h" 30 | 31 | /* 32 | * FFmpeg URL Protocol handler for evbuffers 33 | * 34 | * URL: evbuffer:0x03FB33DA ("evbuffer:%p") 35 | */ 36 | 37 | 38 | static int 39 | url_evbuffer_open(URLContext *h, const char *filename, int flags) 40 | { 41 | const char *p; 42 | char *end; 43 | unsigned long evbuffer_addr; 44 | 45 | if (flags != URL_WRONLY) 46 | { 47 | DPRINTF(E_LOG, L_FFMPEG, "Flags other than URL_WRONLY not supported while opening '%s'\n", filename); 48 | 49 | return AVERROR(EIO); 50 | } 51 | 52 | p = strchr(filename, ':'); 53 | if (!p) 54 | { 55 | DPRINTF(E_LOG, L_FFMPEG, "Malformed evbuffer URL: '%s'\n", filename); 56 | 57 | return AVERROR(EIO); 58 | } 59 | 60 | p++; 61 | 62 | errno = 0; 63 | evbuffer_addr = strtoul(p, &end, 16); 64 | if (((errno == ERANGE) && (evbuffer_addr == ULONG_MAX)) 65 | || ((errno != 0) && (evbuffer_addr == 0))) 66 | { 67 | DPRINTF(E_LOG, L_FFMPEG, "Invalid buffer address in URL: '%s'\n", filename); 68 | 69 | return AVERROR(EIO); 70 | } 71 | 72 | if (end == p) 73 | { 74 | DPRINTF(E_LOG, L_FFMPEG, "No buffer address found in URL: '%s'\n", filename); 75 | 76 | return AVERROR(EIO); 77 | } 78 | 79 | h->priv_data = (void *)evbuffer_addr; 80 | if (!h->priv_data) 81 | { 82 | DPRINTF(E_LOG, L_FFMPEG, "Got a NULL buffer address from URL '%s'\n", filename); 83 | 84 | return AVERROR(EIO); 85 | } 86 | 87 | /* Seek not supported */ 88 | h->is_streamed = 1; 89 | 90 | return 0; 91 | } 92 | 93 | static int 94 | url_evbuffer_close(URLContext *h) 95 | { 96 | h->priv_data = NULL; 97 | 98 | return 0; 99 | } 100 | 101 | static int 102 | url_evbuffer_write(URLContext *h, unsigned char *buf, int size) 103 | { 104 | struct evbuffer *evbuf; 105 | int ret; 106 | 107 | evbuf = (struct evbuffer *)h->priv_data; 108 | 109 | if (!evbuf) 110 | { 111 | DPRINTF(E_LOG, L_FFMPEG, "Write called on evbuffer URL with priv_data = NULL!\n"); 112 | 113 | return -1; 114 | } 115 | 116 | ret = evbuffer_add(evbuf, buf, size); 117 | 118 | return (ret == 0) ? size : -1; 119 | } 120 | 121 | URLProtocol evbuffer_protocol = { 122 | .name = "evbuffer", 123 | .url_open = url_evbuffer_open, 124 | .url_close = url_evbuffer_close, 125 | .url_write = url_evbuffer_write, 126 | }; 127 | 128 | int 129 | register_ffmpeg_evbuffer_url_protocol(void) 130 | { 131 | int ret; 132 | 133 | ret = av_register_protocol(&evbuffer_protocol); 134 | 135 | return ret; 136 | } 137 | -------------------------------------------------------------------------------- /src/RSP.g: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2010 Julien BLACHE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | grammar RSP; 20 | 21 | options { 22 | output = AST; 23 | ASTLabelType = pANTLR3_BASE_TREE; 24 | language = C; 25 | } 26 | 27 | query : expr NEWLINE? EOF -> expr 28 | ; 29 | 30 | expr : aexpr (OR^ aexpr)* 31 | ; 32 | 33 | aexpr : crit (AND^ crit)* 34 | ; 35 | 36 | crit : LPAR expr RPAR -> expr 37 | | strcrit 38 | | intcrit 39 | | datecrit 40 | ; 41 | 42 | strcrit : FIELD strop STR -> ^(strop FIELD STR) 43 | | FIELD NOT strop STR -> ^(NOT ^(strop FIELD STR)) 44 | ; 45 | 46 | strop : EQUAL 47 | | INCLUDES 48 | | STARTSW 49 | | ENDSW 50 | ; 51 | 52 | intcrit : FIELD intop INT -> ^(intop FIELD INT) 53 | | FIELD NOT intop INT -> ^(NOT ^(intop FIELD INT)) 54 | ; 55 | 56 | intop : EQUAL 57 | | LESS 58 | | GREATER 59 | | LTE 60 | | GTE 61 | ; 62 | 63 | datecrit: FIELD dateop datespec -> ^(dateop FIELD datespec) 64 | ; 65 | 66 | dateop : BEFORE 67 | | AFTER 68 | ; 69 | 70 | datespec: dateref 71 | | INT dateintval dateop dateref -> ^(dateop dateref INT dateintval) 72 | ; 73 | 74 | dateref : DATE 75 | | TODAY 76 | ; 77 | 78 | dateintval 79 | : DAY 80 | | WEEK 81 | | MONTH 82 | | YEAR 83 | ; 84 | 85 | QUOTE : '"'; 86 | LPAR : '('; 87 | RPAR : ')'; 88 | 89 | AND : 'and'; 90 | OR : 'or'; 91 | NOT : '!'; 92 | 93 | /* Both string & int */ 94 | EQUAL : '='; 95 | 96 | /* String */ 97 | INCLUDES: 'includes'; 98 | STARTSW : 'startswith'; 99 | ENDSW : 'endswith'; 100 | 101 | /* Int */ 102 | GREATER : '>'; 103 | LESS : '<'; 104 | GTE : '>='; 105 | LTE : '<='; 106 | 107 | /* Date */ 108 | BEFORE : 'before'; 109 | AFTER : 'after'; 110 | DAY : 'day' | 'days'; 111 | WEEK : 'week' | 'weeks'; 112 | MONTH : 'month' | 'months'; 113 | YEAR : 'year' | 'years'; 114 | TODAY : 'today'; 115 | 116 | NEWLINE : '\r'? '\n'; 117 | 118 | WS : (' ' | '\t') { $channel = HIDDEN; }; 119 | 120 | FIELD : 'a'..'z' ('a'..'z' | '_')* 'a'..'z'; 121 | 122 | INT : DIGIT19 DIGIT09*; 123 | 124 | /* YYYY-MM-DD */ 125 | DATE : DIGIT19 DIGIT09 DIGIT09 DIGIT09 '-' ('0' DIGIT19 | '1' '0'..'2') '-' ('0' DIGIT19 | '1'..'2' DIGIT09 | '3' '0'..'1'); 126 | 127 | /* 128 | Unescaping adapted from (ported to the C runtime) 129 | 130 | */ 131 | STR 132 | @init{ pANTLR3_STRING unesc = GETTEXT()->factory->newRaw(GETTEXT()->factory); } 133 | : QUOTE ( reg = ~('\\' | '"') { unesc->addc(unesc, reg); } 134 | | esc = ESCAPED { unesc->appendS(unesc, GETTEXT()); } )+ QUOTE { SETTEXT(unesc); } 135 | ; 136 | 137 | fragment 138 | ESCAPED : '\\' 139 | ( '\\' { SETTEXT(GETTEXT()->factory->newStr8(GETTEXT()->factory, (pANTLR3_UINT8)"\\")); } 140 | | '"' { SETTEXT(GETTEXT()->factory->newStr8(GETTEXT()->factory, (pANTLR3_UINT8)"\"")); } 141 | ) 142 | ; 143 | 144 | fragment 145 | DIGIT09 : '0'..'9'; 146 | 147 | fragment 148 | DIGIT19 : '1'..'9'; 149 | -------------------------------------------------------------------------------- /src/rng.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Julien BLACHE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | #include "rng.h" 30 | 31 | 32 | /* Park & Miller Minimal Standard PRNG 33 | * w/ Bays-Durham shuffle 34 | * From Numerical Recipes in C, 2nd ed. 35 | */ 36 | static int32_t 37 | rng_rand_internal(int32_t *seed) 38 | { 39 | int32_t hi; 40 | int32_t lo; 41 | int32_t res; 42 | 43 | hi = *seed / 127773; 44 | lo = *seed % 127773; 45 | 46 | res = 16807 * lo - 2836 * hi; 47 | 48 | if (res < 0) 49 | res += 0x7fffffffL; /* 2147483647 */ 50 | 51 | *seed = res; 52 | 53 | return res; 54 | } 55 | 56 | void 57 | rng_init(struct rng_ctx *ctx) 58 | { 59 | int32_t val; 60 | int i; 61 | 62 | gcry_randomize(&ctx->seed, sizeof(ctx->seed), GCRY_STRONG_RANDOM); 63 | 64 | /* Load the shuffle array - first 8 iterations discarded */ 65 | for (i = sizeof(ctx->iv) / sizeof(ctx->iv[0]) + 7; i >= 0; i--) 66 | { 67 | val = rng_rand_internal(&ctx->seed); 68 | 69 | if (i < sizeof(ctx->iv) / sizeof(ctx->iv[0])) 70 | ctx->iv[i] = val; 71 | } 72 | 73 | ctx->iy = ctx->iv[0]; 74 | } 75 | 76 | int32_t 77 | rng_rand(struct rng_ctx *ctx) 78 | { 79 | int i; 80 | 81 | /* Select return value */ 82 | i = ctx->iy / (1 + (0x7fffffffL - 1) / (sizeof(ctx->iv) / sizeof(ctx->iv[0]))); 83 | ctx->iy = ctx->iv[i]; 84 | 85 | /* Refill */ 86 | ctx->iv[i] = rng_rand_internal(&ctx->seed); 87 | 88 | return ctx->iy; 89 | } 90 | 91 | /* Integer in [min, max[ */ 92 | /* Taken from GLib 2.0 v2.25.3, g_rand_int_range(), GPLv2+ */ 93 | int32_t 94 | rng_rand_range(struct rng_ctx *ctx, int32_t min, int32_t max) 95 | { 96 | int32_t res; 97 | int32_t dist; 98 | uint32_t maxvalue; 99 | uint32_t leftover; 100 | 101 | dist = max - min; 102 | 103 | if (dist <= 0) 104 | return min; 105 | 106 | /* maxvalue is set to the predecessor of the greatest 107 | * multiple of dist less or equal 2^32. */ 108 | if (dist <= 0x80000000u) /* 2^31 */ 109 | { 110 | /* maxvalue = 2^32 - 1 - (2^32 % dist) */ 111 | leftover = (0x80000000u % dist) * 2; 112 | if (leftover >= dist) 113 | leftover -= dist; 114 | maxvalue = 0xffffffffu - leftover; 115 | } 116 | else 117 | maxvalue = dist - 1; 118 | do 119 | res = rng_rand(ctx); 120 | while (res > maxvalue); 121 | 122 | res %= dist; 123 | 124 | return min + res; 125 | } 126 | 127 | /* Fisher-Yates shuffling algorithm 128 | * Durstenfeld in-place shuffling variant 129 | */ 130 | void 131 | shuffle_ptr(struct rng_ctx *ctx, void **values, int len) 132 | { 133 | int i; 134 | int32_t j; 135 | void *tmp; 136 | 137 | for (i = len - 1; i > 0; i--) 138 | { 139 | j = rng_rand_range(ctx, 0, i + 1); 140 | 141 | tmp = values[i]; 142 | values[i] = values[j]; 143 | values[j] = tmp; 144 | } 145 | } 146 | 147 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | 2 | sbin_PROGRAMS = forked-daapd 3 | 4 | if COND_FLAC 5 | FLACSRC=scan-flac.c 6 | endif 7 | 8 | if COND_MUSEPACK 9 | MUSEPACKSRC=scan-mpc.c 10 | endif 11 | 12 | if COND_ITUNES 13 | ITUNESSRC=filescanner_itunes.c 14 | endif 15 | 16 | if COND_ALSA 17 | ALSASRC=laudio_alsa.c 18 | endif 19 | 20 | if COND_OSS4 21 | OSS4SRC=laudio_oss4.c 22 | endif 23 | 24 | if COND_AVIO 25 | AVIO_SRC=avio_evbuffer.c avio_evbuffer.h 26 | else 27 | FFURL_SRC=ffmpeg_url_evbuffer.c ffmpeg_url_evbuffer.h 28 | endif 29 | 30 | GPERF_FILES = \ 31 | daap_query.gperf \ 32 | rsp_query.gperf \ 33 | dacp_prop.gperf \ 34 | dmap_fields.gperf 35 | 36 | GPERF_PRODUCTS = \ 37 | daap_query_hash.c \ 38 | rsp_query_hash.c \ 39 | dacp_prop_hash.c \ 40 | dmap_fields_hash.c 41 | 42 | ANTLR_GRAMMARS = \ 43 | RSP.g RSP2SQL.g \ 44 | DAAP.g DAAP2SQL.g 45 | 46 | ANTLR_SOURCES = \ 47 | RSPLexer.c RSPLexer.h RSPParser.c RSPParser.h \ 48 | RSP2SQL.c RSP2SQL.h \ 49 | DAAPLexer.c DAAPLexer.h DAAPParser.c DAAPParser.h \ 50 | DAAP2SQL.c DAAP2SQL.h 51 | 52 | ANTLR_PRODUCTS = 53 | 54 | forked_daapd_CPPFLAGS = -D_GNU_SOURCE \ 55 | -DDATADIR="\"$(pkgdatadir)\"" -DCONFDIR="\"$(sysconfdir)\"" \ 56 | -DSTATEDIR="\"$(localstatedir)\"" -DPKGLIBDIR="\"$(pkglibdir)\"" \ 57 | @OSS4CPPFLAGS@ 58 | 59 | forked_daapd_CFLAGS = @CBLOCKS_FLAGS@ \ 60 | @ZLIB_CFLAGS@ @AVAHI_CFLAGS@ @SQLITE3_CFLAGS@ @LIBAV_CFLAGS@ \ 61 | @CONFUSE_CFLAGS@ @TAGLIB_CFLAGS@ @MINIXML_CFLAGS@ @LIBPLIST_CFLAGS@ \ 62 | @LIBGCRYPT_CFLAGS@ @GPG_ERROR_CFLAGS@ @ALSA_CFLAGS@ @TRE_CFLAGS@ 63 | 64 | forked_daapd_LDADD = -lrt \ 65 | @ZLIB_LIBS@ @AVAHI_LIBS@ @SQLITE3_LIBS@ @LIBAV_LIBS@ \ 66 | @CONFUSE_LIBS@ @FLAC_LIBS@ @TAGLIB_LIBS@ \ 67 | @LIBAVL_LIBS@ @MINIXML_LIBS@ @ANTLR3C_LIBS@ @LIBPLIST_LIBS@ \ 68 | @LIBGCRYPT_LIBS@ @GPG_ERROR_LIBS@ @ALSA_LIBS@ @LIBUNISTRING@ \ 69 | @LIBDISPATCH_LIBS@ @CBLOCKS_LIBS@ @TRE_LIBS@ 70 | 71 | forked_daapd_SOURCES = main.c \ 72 | db.c db.h \ 73 | logger.c logger.h \ 74 | conffile.c conffile.h \ 75 | filescanner.c filescanner.h \ 76 | filescanner_ffmpeg.c filescanner_urlfile.c filescanner_m3u.c $(ITUNESSRC) \ 77 | mdns_avahi.c mdns.h \ 78 | network.c network.h \ 79 | remote_pairing.c remote_pairing.h \ 80 | $(FFURL_SRC) $(AVIO_SRC) \ 81 | http.c http.h \ 82 | httpd.c httpd.h \ 83 | httpd_rsp.c httpd_rsp.h \ 84 | httpd_daap.c httpd_daap.h \ 85 | httpd_dacp.c httpd_dacp.h \ 86 | dmap_common.c dmap_common.h \ 87 | transcode.c transcode.h \ 88 | artwork.c artwork.h \ 89 | misc.c misc.h \ 90 | rng.c rng.h \ 91 | rsp_query.c rsp_query.h \ 92 | daap_query.c daap_query.h \ 93 | player.c player.h \ 94 | $(ALSASRC) $(OSS4SRC) laudio.h \ 95 | raop.c raop.h \ 96 | evbuffer/evbuffer.c evbuffer/evbuffer.h \ 97 | scan-wma.c \ 98 | $(FLACSRC) $(MUSEPACKSRC) 99 | 100 | nodist_forked_daapd_SOURCES = \ 101 | $(ANTLR_SOURCES) 102 | 103 | BUILT_SOURCES = \ 104 | $(GPERF_PRODUCTS) 105 | 106 | EXTRA_DIST = \ 107 | $(ANTLR_GRAMMARS) \ 108 | scan-mpc.c \ 109 | scan-flac.c 110 | 111 | CLEANFILES = \ 112 | $(GPERF_PRODUCTS) 113 | 114 | 115 | # gperf construction rules 116 | %_hash.c: %.gperf 117 | if $(GPERF) $< > $@.tmp; then \ 118 | mv $@.tmp $@; \ 119 | elif $(GPERF) --version >/dev/null 2>&1; then \ 120 | rm $@.tmp; \ 121 | exit 1; \ 122 | else \ 123 | rm $@.tmp; \ 124 | touch $@; \ 125 | fi 126 | 127 | # Support for building the parsers when ANTLR3 is available 128 | if COND_ANTLR 129 | SUFFIXES = .g .u 130 | 131 | %.tokens %.c %Lexer.c %Parser.c %Lexer.h %Parser.h %.h: %.g 132 | $(ANTLR) $(ANTLR_OPTIONS) $< 133 | 134 | %.u: %.g 135 | $(ANTLR) -depend $< > $@ 136 | @echo -n "ANTLR_PRODUCTS += " > $@.tmp 137 | @grep : $@ | cut -d : -f 1 | tr -d ' ' | { while read f; do test "$$f" != "$<" && echo -n "$$f "; done } >> $@.tmp 138 | @cat $@.tmp >> $@ 139 | @rm $@.tmp 140 | 141 | BUILT_SOURCES += $(ANTLR_SOURCES) 142 | 143 | CLEANFILES += \ 144 | $(ANTLR_PRODUCTS) \ 145 | $(ANTLR_GRAMMARS:.g=.u) 146 | 147 | else !COND_ANTLR 148 | DISTCLEANFILES = \ 149 | $(ANTLR_PRODUCTS) \ 150 | $(ANTLR_GRAMMARS:.g=.u) 151 | 152 | endif 153 | 154 | -include $(ANTLR_GRAMMARS:.g=.u) 155 | -------------------------------------------------------------------------------- /src/scan-mpc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Musepack tag parsing routines. 3 | * 4 | * Copyright (C) 2005 Sebastian Dröge 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | */ 20 | 21 | #ifdef HAVE_CONFIG_H 22 | # include 23 | #endif 24 | 25 | #ifdef HAVE_STDINT_H 26 | #include 27 | #endif 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "logger.h" 34 | #include "db.h" 35 | 36 | #define TRUE ((1 == 1)) 37 | #define FALSE (!TRUE) 38 | 39 | typedef struct media_file_info MP3FILE; 40 | 41 | /** 42 | * scan a musepack file for metainfo. 43 | * 44 | * @param filename file to read metainfo for 45 | * @param pmp3 MP3FILE structure to fill 46 | * @returns TRUE if file should be added to DB, FALSE otherwise 47 | */ 48 | int scan_get_mpcinfo(char *filename, MP3FILE *pmp3) { 49 | TagLib_File *file; 50 | TagLib_Tag *tag; 51 | const TagLib_AudioProperties *properties; 52 | char *val; 53 | int len; 54 | unsigned int i; 55 | 56 | /* open file with taglib */ 57 | if ((file = taglib_file_new_type(filename, TagLib_File_MPC)) == NULL) { 58 | DPRINTF(E_WARN,L_SCAN,"Could not open %s with taglib\n", filename); 59 | return FALSE; 60 | } 61 | 62 | /* retrieve all tags */ 63 | if ((tag = taglib_file_tag(file)) == NULL) { 64 | DPRINTF(E_WARN,L_SCAN,"Could not retrieve tags of %s\n", filename); 65 | taglib_file_free(file); 66 | 67 | return FALSE; 68 | } 69 | 70 | /* fill the MP3FILE structure with the tags */ 71 | if ((val = taglib_tag_title(tag)) != NULL) { 72 | len = strlen(val); 73 | if ((pmp3->title = calloc(len + 1, 1)) != NULL) 74 | strncpy(pmp3->title, val, len); 75 | taglib_tag_free_strings(); 76 | } 77 | if ((val = taglib_tag_artist(tag)) != NULL) { 78 | len = strlen(val); 79 | if ((pmp3->artist = calloc(len + 1, 1)) != NULL) 80 | strncpy(pmp3->artist, val, len); 81 | taglib_tag_free_strings(); 82 | } 83 | if ((val = taglib_tag_album(tag)) != NULL) { 84 | len = strlen(val); 85 | if ((pmp3->album = calloc(len + 1, 1)) != NULL) 86 | strncpy(pmp3->album, val, len); 87 | taglib_tag_free_strings(); 88 | } 89 | if ((val = taglib_tag_comment(tag)) != NULL) { 90 | len = strlen(val); 91 | if ((pmp3->comment = calloc(len + 1, 1)) != NULL) 92 | strncpy(pmp3->comment, val, len); 93 | taglib_tag_free_strings(); 94 | } 95 | if ((val = taglib_tag_genre(tag)) != NULL) { 96 | len = strlen(val); 97 | if ((pmp3->genre = calloc(len + 1, 1)) != NULL) 98 | strncpy(pmp3->genre, val, len); 99 | taglib_tag_free_strings(); 100 | } 101 | 102 | if ((i = taglib_tag_year(tag)) != 0) 103 | pmp3->year = i; 104 | if ((i = taglib_tag_track(tag)) != 0) 105 | pmp3->track = i; 106 | 107 | /* load the properties (like bitrate) from the file */ 108 | if ((properties = taglib_file_audioproperties(file)) == NULL) { 109 | DPRINTF(E_WARN,L_SCAN,"Could not retrieve properties of %s\n", filename); 110 | return FALSE; 111 | } 112 | 113 | /* fill the properties in the MP3FILE structure */ 114 | pmp3->song_length = taglib_audioproperties_length(properties) * 1000; 115 | pmp3->bitrate = taglib_audioproperties_bitrate(properties); 116 | pmp3->samplerate = taglib_audioproperties_samplerate(properties); 117 | 118 | taglib_file_free(file); 119 | 120 | return TRUE; 121 | } 122 | -------------------------------------------------------------------------------- /src/rsp_query.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2011 Julien BLACHE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | #ifdef HAVE_CONFIG_H 20 | # include 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "logger.h" 29 | #include "misc.h" 30 | #include "rsp_query.h" 31 | 32 | #include "RSPLexer.h" 33 | #include "RSPParser.h" 34 | #include "RSP2SQL.h" 35 | 36 | 37 | char * 38 | rsp_query_parse_sql(const char *rsp_query) 39 | { 40 | /* Input RSP query, fed to the lexer */ 41 | pANTLR3_INPUT_STREAM query; 42 | 43 | /* Lexer and the resulting token stream, fed to the parser */ 44 | pRSPLexer lxr; 45 | pANTLR3_COMMON_TOKEN_STREAM tkstream; 46 | 47 | /* Parser and the resulting AST, fed to the tree parser */ 48 | pRSPParser psr; 49 | RSPParser_query_return qtree; 50 | pANTLR3_COMMON_TREE_NODE_STREAM nodes; 51 | 52 | /* Tree parser and the resulting SQL query string */ 53 | pRSP2SQL sqlconv; 54 | pANTLR3_STRING sql; 55 | 56 | char *ret = NULL; 57 | 58 | DPRINTF(E_DBG, L_RSP, "Trying RSP query -%s-\n", rsp_query); 59 | 60 | #if ANTLR3C_NEW_INPUT 61 | query = antlr3StringStreamNew ((pANTLR3_UINT8)rsp_query, ANTLR3_ENC_8BIT, (ANTLR3_UINT64)strlen(rsp_query), (pANTLR3_UINT8)"RSP query"); 62 | #else 63 | query = antlr3NewAsciiStringInPlaceStream ((pANTLR3_UINT8)rsp_query, (ANTLR3_UINT64)strlen(rsp_query), (pANTLR3_UINT8)"RSP query"); 64 | #endif 65 | if (!query) 66 | { 67 | DPRINTF(E_DBG, L_RSP, "Could not create input stream\n"); 68 | return NULL; 69 | } 70 | 71 | lxr = RSPLexerNew(query); 72 | if (!lxr) 73 | { 74 | DPRINTF(E_DBG, L_RSP, "Could not create RSP lexer\n"); 75 | goto lxr_fail; 76 | } 77 | 78 | tkstream = antlr3CommonTokenStreamSourceNew(ANTLR3_SIZE_HINT, TOKENSOURCE(lxr)); 79 | if (!tkstream) 80 | { 81 | DPRINTF(E_DBG, L_RSP, "Could not create RSP token stream\n"); 82 | goto tkstream_fail; 83 | } 84 | 85 | psr = RSPParserNew(tkstream); 86 | if (!psr) 87 | { 88 | DPRINTF(E_DBG, L_RSP, "Could not create RSP parser\n"); 89 | goto psr_fail; 90 | } 91 | 92 | qtree = psr->query(psr); 93 | 94 | /* Check for parser errors */ 95 | if (psr->pParser->rec->state->errorCount > 0) 96 | { 97 | DPRINTF(E_LOG, L_RSP, "RSP query parser terminated with %d errors\n", psr->pParser->rec->state->errorCount); 98 | goto psr_error; 99 | } 100 | 101 | DPRINTF(E_SPAM, L_RSP, "RSP query AST:\n\t%s\n", qtree.tree->toStringTree(qtree.tree)->chars); 102 | 103 | nodes = antlr3CommonTreeNodeStreamNewTree(qtree.tree, ANTLR3_SIZE_HINT); 104 | if (!nodes) 105 | { 106 | DPRINTF(E_DBG, L_RSP, "Could not create node stream\n"); 107 | goto psr_error; 108 | } 109 | 110 | sqlconv = RSP2SQLNew(nodes); 111 | if (!sqlconv) 112 | { 113 | DPRINTF(E_DBG, L_RSP, "Could not create SQL converter\n"); 114 | goto sql_fail; 115 | } 116 | 117 | sql = sqlconv->query(sqlconv); 118 | 119 | /* Check for tree parser errors */ 120 | if (sqlconv->pTreeParser->rec->state->errorCount > 0) 121 | { 122 | DPRINTF(E_LOG, L_RSP, "RSP query tree parser terminated with %d errors\n", sqlconv->pTreeParser->rec->state->errorCount); 123 | goto sql_error; 124 | } 125 | 126 | if (sql) 127 | { 128 | DPRINTF(E_DBG, L_RSP, "RSP SQL query: -%s-\n", sql->chars); 129 | ret = strdup((char *)sql->chars); 130 | } 131 | else 132 | { 133 | DPRINTF(E_LOG, L_RSP, "Invalid RSP query\n"); 134 | ret = NULL; 135 | } 136 | 137 | sql_error: 138 | sqlconv->free(sqlconv); 139 | sql_fail: 140 | nodes->free(nodes); 141 | psr_error: 142 | psr->free(psr); 143 | psr_fail: 144 | tkstream->free(tkstream); 145 | tkstream_fail: 146 | lxr->free(lxr); 147 | lxr_fail: 148 | query->close(query); 149 | 150 | return ret; 151 | } 152 | -------------------------------------------------------------------------------- /src/daap_query.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2011 Julien BLACHE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | #ifdef HAVE_CONFIG_H 20 | # include 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "logger.h" 29 | #include "misc.h" 30 | #include "daap_query.h" 31 | 32 | #include "DAAPLexer.h" 33 | #include "DAAPParser.h" 34 | #include "DAAP2SQL.h" 35 | 36 | 37 | char * 38 | daap_query_parse_sql(const char *daap_query) 39 | { 40 | /* Input DAAP query, fed to the lexer */ 41 | pANTLR3_INPUT_STREAM query; 42 | 43 | /* Lexer and the resulting token stream, fed to the parser */ 44 | pDAAPLexer lxr; 45 | pANTLR3_COMMON_TOKEN_STREAM tkstream; 46 | 47 | /* Parser and the resulting AST, fed to the tree parser */ 48 | pDAAPParser psr; 49 | DAAPParser_query_return qtree; 50 | pANTLR3_COMMON_TREE_NODE_STREAM nodes; 51 | 52 | /* Tree parser and the resulting SQL query string */ 53 | pDAAP2SQL sqlconv; 54 | pANTLR3_STRING sql; 55 | 56 | char *ret = NULL; 57 | 58 | DPRINTF(E_DBG, L_DAAP, "Trying DAAP query -%s-\n", daap_query); 59 | 60 | #if ANTLR3C_NEW_INPUT 61 | query = antlr3StringStreamNew ((pANTLR3_UINT8)daap_query, ANTLR3_ENC_8BIT, (ANTLR3_UINT64)strlen(daap_query), (pANTLR3_UINT8)"DAAP query"); 62 | #else 63 | query = antlr3NewAsciiStringInPlaceStream ((pANTLR3_UINT8)daap_query, (ANTLR3_UINT64)strlen(daap_query), (pANTLR3_UINT8)"DAAP query"); 64 | #endif 65 | if (!query) 66 | { 67 | DPRINTF(E_DBG, L_DAAP, "Could not create input stream\n"); 68 | return NULL; 69 | } 70 | 71 | lxr = DAAPLexerNew(query); 72 | if (!lxr) 73 | { 74 | DPRINTF(E_DBG, L_DAAP, "Could not create DAAP lexer\n"); 75 | goto lxr_fail; 76 | } 77 | 78 | tkstream = antlr3CommonTokenStreamSourceNew(ANTLR3_SIZE_HINT, TOKENSOURCE(lxr)); 79 | if (!tkstream) 80 | { 81 | DPRINTF(E_DBG, L_DAAP, "Could not create DAAP token stream\n"); 82 | goto tkstream_fail; 83 | } 84 | 85 | psr = DAAPParserNew(tkstream); 86 | if (!psr) 87 | { 88 | DPRINTF(E_DBG, L_DAAP, "Could not create DAAP parser\n"); 89 | goto psr_fail; 90 | } 91 | 92 | qtree = psr->query(psr); 93 | 94 | /* Check for parser errors */ 95 | if (psr->pParser->rec->state->errorCount > 0) 96 | { 97 | DPRINTF(E_LOG, L_DAAP, "DAAP query parser terminated with %d errors\n", psr->pParser->rec->state->errorCount); 98 | goto psr_error; 99 | } 100 | 101 | DPRINTF(E_SPAM, L_DAAP, "DAAP query AST:\n\t%s\n", qtree.tree->toStringTree(qtree.tree)->chars); 102 | 103 | nodes = antlr3CommonTreeNodeStreamNewTree(qtree.tree, ANTLR3_SIZE_HINT); 104 | if (!nodes) 105 | { 106 | DPRINTF(E_DBG, L_DAAP, "Could not create node stream\n"); 107 | goto psr_error; 108 | } 109 | 110 | sqlconv = DAAP2SQLNew(nodes); 111 | if (!sqlconv) 112 | { 113 | DPRINTF(E_DBG, L_DAAP, "Could not create SQL converter\n"); 114 | goto sql_fail; 115 | } 116 | 117 | sql = sqlconv->query(sqlconv); 118 | 119 | /* Check for tree parser errors */ 120 | if (sqlconv->pTreeParser->rec->state->errorCount > 0) 121 | { 122 | DPRINTF(E_LOG, L_DAAP, "DAAP query tree parser terminated with %d errors\n", sqlconv->pTreeParser->rec->state->errorCount); 123 | goto sql_error; 124 | } 125 | 126 | if (sql) 127 | { 128 | DPRINTF(E_DBG, L_DAAP, "DAAP SQL query: -%s-\n", sql->chars); 129 | ret = strdup((char *)sql->chars); 130 | } 131 | else 132 | { 133 | DPRINTF(E_LOG, L_DAAP, "Invalid DAAP query\n"); 134 | ret = NULL; 135 | } 136 | 137 | sql_error: 138 | sqlconv->free(sqlconv); 139 | sql_fail: 140 | nodes->free(nodes); 141 | psr_error: 142 | psr->free(psr); 143 | psr_fail: 144 | tkstream->free(tkstream); 145 | tkstream_fail: 146 | lxr->free(lxr); 147 | lxr_fail: 148 | query->close(query); 149 | 150 | return ret; 151 | } 152 | -------------------------------------------------------------------------------- /src/filescanner_m3u.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2010 Julien BLACHE 3 | * 4 | * Rewritten from mt-daapd code: 5 | * Copyright (C) 2003 Ron Pedde (ron@pedde.com) 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | */ 21 | 22 | #ifdef HAVE_CONFIG_H 23 | # include 24 | #endif 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include "logger.h" 38 | #include "db.h" 39 | #include "filescanner.h" 40 | #include "misc.h" 41 | 42 | 43 | void 44 | scan_m3u_playlist(char *file) 45 | { 46 | FILE *fp; 47 | struct playlist_info *pli; 48 | struct stat sb; 49 | char buf[PATH_MAX]; 50 | char rel_entry[PATH_MAX]; 51 | char *pl_base; 52 | char *entry; 53 | char *filename; 54 | char *ptr; 55 | size_t len; 56 | int pl_id; 57 | int ret; 58 | 59 | DPRINTF(E_INFO, L_SCAN, "Processing static playlist: %s\n", file); 60 | 61 | ret = stat(file, &sb); 62 | if (ret < 0) 63 | { 64 | DPRINTF(E_LOG, L_SCAN, "Could not stat() '%s': %s\n", file, strerror(errno)); 65 | 66 | return; 67 | } 68 | 69 | filename = strrchr(file, '/'); 70 | if (!filename) 71 | filename = file; 72 | else 73 | filename++; 74 | 75 | pli = db_pl_fetch_bypath(file); 76 | 77 | if (pli) 78 | { 79 | DPRINTF(E_DBG, L_SCAN, "Playlist found, updating\n"); 80 | 81 | pl_id = pli->id; 82 | 83 | free_pli(pli, 0); 84 | 85 | db_pl_ping(pl_id); 86 | db_pl_clear_items(pl_id); 87 | } 88 | else 89 | pl_id = 0; 90 | 91 | fp = fopen(file, "r"); 92 | if (!fp) 93 | { 94 | DPRINTF(E_WARN, L_SCAN, "Could not open playlist '%s': %s\n", file, strerror(errno)); 95 | 96 | return; 97 | } 98 | 99 | if (pl_id == 0) 100 | { 101 | /* Get only the basename, to be used as the playlist name */ 102 | ptr = strrchr(filename, '.'); 103 | if (ptr) 104 | *ptr = '\0'; 105 | 106 | /* Safe: filename is a subset of file which is <= PATH_MAX already */ 107 | strncpy(buf, filename, sizeof(buf)); 108 | 109 | /* Restore the full filename */ 110 | if (ptr) 111 | *ptr = '.'; 112 | 113 | ret = db_pl_add(buf, file, &pl_id); 114 | if (ret < 0) 115 | { 116 | DPRINTF(E_LOG, L_SCAN, "Error adding m3u playlist '%s'\n", file); 117 | 118 | return; 119 | } 120 | 121 | DPRINTF(E_INFO, L_SCAN, "Added playlist as id %d\n", pl_id); 122 | } 123 | 124 | ptr = strrchr(file, '/'); 125 | if (!ptr) 126 | { 127 | DPRINTF(E_WARN, L_SCAN, "Could not determine playlist base path\n"); 128 | 129 | return; 130 | } 131 | 132 | *ptr = '\0'; 133 | pl_base = strdup(file); 134 | *ptr = '/'; 135 | 136 | if (!pl_base) 137 | { 138 | DPRINTF(E_WARN, L_SCAN, "Out of memory\n"); 139 | 140 | return; 141 | } 142 | 143 | while (fgets(buf, sizeof(buf), fp) != NULL) 144 | { 145 | len = strlen(buf); 146 | if (buf[len - 1] != '\n') 147 | { 148 | DPRINTF(E_WARN, L_SCAN, "Entry exceeds PATH_MAX, discarding\n"); 149 | 150 | while (fgets(buf, sizeof(buf), fp) != NULL) 151 | { 152 | if (buf[strlen(buf) - 1] == '\n') 153 | break; 154 | } 155 | 156 | continue; 157 | } 158 | 159 | if ((buf[0] == ';') || (buf[0] == '#') || (buf[0] == '\n')) 160 | continue; 161 | 162 | while (isspace(buf[len - 1])) 163 | { 164 | len--; 165 | buf[len] = '\0'; 166 | } 167 | 168 | /* Absolute vs. relative path */ 169 | if (buf[0] == '/') 170 | { 171 | entry = buf; 172 | } 173 | else 174 | { 175 | ret = snprintf(rel_entry, sizeof(rel_entry),"%s/%s", pl_base, buf); 176 | if ((ret < 0) || (ret >= sizeof(rel_entry))) 177 | { 178 | DPRINTF(E_WARN, L_SCAN, "Skipping entry, PATH_MAX exceeded\n"); 179 | 180 | continue; 181 | } 182 | 183 | entry = rel_entry; 184 | } 185 | 186 | filename = m_realpath(entry); 187 | if (!filename) 188 | { 189 | DPRINTF(E_WARN, L_SCAN, "Could not determine real path for '%s': %s\n", entry, strerror(errno)); 190 | 191 | continue; 192 | } 193 | 194 | ret = db_pl_add_item_bypath(pl_id, filename); 195 | if (ret < 0) 196 | DPRINTF(E_WARN, L_SCAN, "Could not add %s to playlist\n", filename); 197 | 198 | free(filename); 199 | } 200 | 201 | free(pl_base); 202 | 203 | if (!feof(fp)) 204 | { 205 | DPRINTF(E_LOG, L_SCAN, "Error reading playlist '%s': %s\n", file, strerror(errno)); 206 | 207 | fclose(fp); 208 | return; 209 | } 210 | 211 | fclose(fp); 212 | 213 | DPRINTF(E_INFO, L_SCAN, "Done processing playlist\n"); 214 | } 215 | -------------------------------------------------------------------------------- /src/http.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __HTTP_H__ 3 | #define __HTTP_H__ 4 | 5 | #include 6 | 7 | #include "evbuffer/evbuffer.h" 8 | #include "misc.h" 9 | 10 | 11 | /* Some HTTP codes */ 12 | #define HTTP_CONTINUE 100 13 | #define HTTP_OK 200 14 | #define HTTP_NO_CONTENT 204 15 | #define HTTP_PARTIAL_CONTENT 206 16 | #define HTTP_MOVE_TEMP 302 17 | #define HTTP_BAD_REQUEST 400 18 | #define HTTP_UNAUTHORIZED 401 19 | #define HTTP_FORBIDDEN 403 20 | #define HTTP_NOT_FOUND 404 21 | #define HTTP_INTERNAL_ERROR 500 22 | #define HTTP_UNAVAILABLE 503 23 | 24 | 25 | enum p_version { 26 | P_VER_1_0, 27 | P_VER_1_1, 28 | }; 29 | 30 | #define METHOD_HAS_BODY (1 << 5) 31 | #define HTTP_METHOD (1 << 6) 32 | #define RTSP_METHOD (1 << 7) 33 | #define METHOD_MASK 0xe0 34 | 35 | enum request_method { 36 | HTTP_GET = 0 | HTTP_METHOD, 37 | HTTP_POST = 1 | HTTP_METHOD | METHOD_HAS_BODY, 38 | 39 | RTSP_ANNOUNCE = 2 | RTSP_METHOD | METHOD_HAS_BODY, 40 | RTSP_OPTIONS = 3 | RTSP_METHOD, 41 | RTSP_SETUP = 4 | RTSP_METHOD, 42 | RTSP_RECORD = 5 | RTSP_METHOD, 43 | RTSP_PAUSE = 6 | RTSP_METHOD, 44 | RTSP_GET_PARAMETER = 7 | RTSP_METHOD | METHOD_HAS_BODY, 45 | RTSP_SET_PARAMETER = 8 | RTSP_METHOD | METHOD_HAS_BODY, 46 | RTSP_FLUSH = 9 | RTSP_METHOD, 47 | RTSP_TEARDOWN = 10 | RTSP_METHOD, 48 | }; 49 | 50 | enum uri_decode_mode { 51 | URI_DECODE_NORMAL, 52 | URI_DECODE_PLUS_ALWAYS, 53 | URI_DECODE_PLUS_NEVER, 54 | }; 55 | 56 | struct http_request; 57 | struct http_response; 58 | 59 | struct http_connection; 60 | 61 | struct http_server; 62 | 63 | typedef int (*http_cb)(struct http_connection *c, struct http_request *req, struct http_response *resp, void *data); 64 | typedef struct evbuffer *(*http_chunk_cb)(struct http_connection *c, struct http_response *resp, void *data); 65 | typedef void (*http_server_close_cb)(struct http_server *srv, void *data); 66 | typedef void (*http_close_cb)(struct http_connection *c, void *data); 67 | typedef void (*http_free_cb)(void *data); 68 | 69 | 70 | /* Utilities */ 71 | void 72 | http_decode_uri(char *uri, enum uri_decode_mode mode); 73 | 74 | int 75 | http_parse_query_string(const char *uri, struct keyval *kv); 76 | 77 | const char * 78 | http_method(enum request_method method); 79 | 80 | 81 | /* HTTP connection */ 82 | int 83 | http_connection_get_local_addr(struct http_connection *c, char *buf); 84 | 85 | int 86 | http_connection_get_remote_addr(struct http_connection *c, char *buf); 87 | 88 | 89 | /* HTTP request */ 90 | void 91 | http_request_free(struct http_request *req); 92 | 93 | const char * 94 | http_request_get_uri(struct http_request *req); 95 | 96 | int 97 | http_request_set_body(struct http_request *req, struct evbuffer *evbuf); 98 | 99 | void 100 | http_request_remove_header(struct http_request *req, const char *name); 101 | 102 | int 103 | http_request_add_header(struct http_request *req, const char *name, const char *value); 104 | 105 | const char * 106 | http_request_get_header(struct http_request *req, const char *name); 107 | 108 | 109 | /* HTTP response */ 110 | void 111 | http_response_free(struct http_response *r); 112 | 113 | struct evbuffer * 114 | http_response_get_body(struct http_response *r); 115 | 116 | void 117 | http_response_set_body(struct http_response *r, struct evbuffer *evbuf); 118 | 119 | void 120 | http_response_remove_header(struct http_response *r, const char *name); 121 | 122 | int 123 | http_response_add_header(struct http_response *r, const char *name, const char *value); 124 | 125 | const char * 126 | http_response_get_header(struct http_response *r, const char *name); 127 | 128 | int 129 | http_response_get_status(struct http_response *r, const char **reason); 130 | 131 | int 132 | http_response_set_status(struct http_response *r, int status_code, const char *reason); 133 | 134 | 135 | /* HTTP client */ 136 | struct http_connection * 137 | http_client_new(int ldomain, const char *address, short port, http_close_cb close_cb, http_free_cb free_cb, void *data); 138 | 139 | void 140 | http_client_free(struct http_connection *c); 141 | 142 | struct http_request * 143 | http_client_request_new(enum request_method method, enum p_version version, const char *uri, http_cb cb); 144 | 145 | int 146 | http_client_request_run(struct http_connection *c, struct http_request *req); 147 | 148 | 149 | /* HTTP server */ 150 | struct http_server * 151 | http_server_new(int ldomain, dispatch_group_t user_group, const char *address, short port, http_cb cb, http_server_close_cb close_cb); 152 | 153 | void 154 | http_server_free(struct http_server *srv); 155 | 156 | int 157 | http_server_start(struct http_server *srv); 158 | 159 | int 160 | http_server_response_run(struct http_connection *c, struct http_response *r); 161 | 162 | void 163 | http_server_response_freeze(struct http_connection *c, struct http_response *r, http_free_cb free_cb, void *data); 164 | 165 | int 166 | http_server_response_thaw_and_run(struct http_connection *c, struct http_response *r); 167 | 168 | int 169 | http_server_response_run_chunked(struct http_connection *c, struct http_response *r, struct evbuffer *chunk, http_chunk_cb chunk_cb, http_free_cb free_cb, void *data); 170 | 171 | int 172 | http_server_response_end_chunked(struct http_connection *c, struct http_response *r); 173 | 174 | int 175 | http_server_error_run(struct http_connection *c, struct http_response *r, int status_code, char *reason); 176 | 177 | void 178 | http_server_kill_connection(struct http_connection *c); 179 | 180 | #endif /* !__HTTP_H__ */ 181 | -------------------------------------------------------------------------------- /sqlext/sqlext.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2010 Julien BLACHE 3 | * Copyright (C) 2010 Kai Elwert 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #ifdef HAVE_CONFIG_H 21 | # include 22 | #endif 23 | 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | SQLITE_EXTENSION_INIT1 33 | 34 | 35 | /* 36 | * MurmurHash2, 64-bit versions, by Austin Appleby 37 | * 38 | * Code released under the public domain, as per 39 | * 40 | * as of 2010-01-03. 41 | */ 42 | 43 | #if SIZEOF_VOID_P == 8 /* 64bit platforms */ 44 | 45 | static uint64_t 46 | murmur_hash64(const void *key, int len, uint32_t seed) 47 | { 48 | const int r = 47; 49 | const uint64_t m = 0xc6a4a7935bd1e995; 50 | 51 | const uint64_t *data; 52 | const uint64_t *end; 53 | const unsigned char *data_tail; 54 | uint64_t h; 55 | uint64_t k; 56 | 57 | h = seed ^ (len * m); 58 | data = (const uint64_t *)key; 59 | end = data + (len / 8); 60 | 61 | while (data != end) 62 | { 63 | k = *data++; 64 | 65 | k *= m; 66 | k ^= k >> r; 67 | k *= m; 68 | 69 | h ^= k; 70 | h *= m; 71 | } 72 | 73 | data_tail = (const unsigned char *)data; 74 | 75 | switch (len & 7) 76 | { 77 | case 7: 78 | h ^= (uint64_t)(data_tail[6]) << 48; 79 | case 6: 80 | h ^= (uint64_t)(data_tail[5]) << 40; 81 | case 5: 82 | h ^= (uint64_t)(data_tail[4]) << 32; 83 | case 4: 84 | h ^= (uint64_t)(data_tail[3]) << 24; 85 | case 3: 86 | h ^= (uint64_t)(data_tail[2]) << 16; 87 | case 2: 88 | h ^= (uint64_t)(data_tail[1]) << 8; 89 | case 1: 90 | h ^= (uint64_t)(data_tail[0]); 91 | h *= m; 92 | } 93 | 94 | h ^= h >> r; 95 | h *= m; 96 | h ^= h >> r; 97 | 98 | return h; 99 | } 100 | 101 | #elif SIZEOF_VOID_P == 4 /* 32bit platforms */ 102 | 103 | static uint64_t 104 | murmur_hash64(const void *key, int len, uint32_t seed) 105 | { 106 | const int r = 24; 107 | const uint32_t m = 0x5bd1e995; 108 | 109 | const uint32_t *data; 110 | const unsigned char *data_tail; 111 | uint32_t k1; 112 | uint32_t h1; 113 | uint32_t k2; 114 | uint32_t h2; 115 | 116 | uint64_t h; 117 | 118 | h1 = seed ^ len; 119 | h2 = 0; 120 | 121 | data = (const uint32_t *)key; 122 | 123 | while (len >= 8) 124 | { 125 | k1 = *data++; 126 | k1 *= m; k1 ^= k1 >> r; k1 *= m; 127 | h1 *= m; h1 ^= k1; 128 | 129 | k2 = *data++; 130 | k2 *= m; k2 ^= k2 >> r; k2 *= m; 131 | h2 *= m; h2 ^= k2; 132 | 133 | len -= 8; 134 | } 135 | 136 | if (len >= 4) 137 | { 138 | k1 = *data++; 139 | k1 *= m; k1 ^= k1 >> r; k1 *= m; 140 | h1 *= m; h1 ^= k1; 141 | len -= 4; 142 | } 143 | 144 | data_tail = (const unsigned char *)data; 145 | 146 | switch(len) 147 | { 148 | case 3: 149 | h2 ^= (uint32_t)(data_tail[2]) << 16; 150 | case 2: 151 | h2 ^= (uint32_t)(data_tail[1]) << 8; 152 | case 1: 153 | h2 ^= (uint32_t)(data_tail[0]); 154 | h2 *= m; 155 | }; 156 | 157 | h1 ^= h2 >> 18; h1 *= m; 158 | h2 ^= h1 >> 22; h2 *= m; 159 | h1 ^= h2 >> 17; h1 *= m; 160 | h2 ^= h1 >> 19; h2 *= m; 161 | 162 | h = h1; 163 | h = (h << 32) | h2; 164 | 165 | return h; 166 | } 167 | 168 | #else 169 | # error Platform not supported 170 | #endif 171 | 172 | static void 173 | sqlext_daap_songalbumid_xfunc(sqlite3_context *pv, int n, sqlite3_value **ppv) 174 | { 175 | const char *album_artist; 176 | const char *album; 177 | char *hashbuf; 178 | sqlite3_int64 result; 179 | 180 | if (n != 2) 181 | { 182 | sqlite3_result_error(pv, "daap_songalbumid() requires 2 parameters, album_artist and album", -1); 183 | return; 184 | } 185 | 186 | if ((sqlite3_value_type(ppv[0]) != SQLITE_TEXT) 187 | || (sqlite3_value_type(ppv[1]) != SQLITE_TEXT)) 188 | { 189 | sqlite3_result_error(pv, "daap_songalbumid() requires 2 text parameters", -1); 190 | return; 191 | } 192 | 193 | album_artist = (const char *)sqlite3_value_text(ppv[0]); 194 | album = (const char *)sqlite3_value_text(ppv[1]); 195 | 196 | hashbuf = sqlite3_mprintf("%s==%s", (album_artist) ? album_artist : "", (album) ? album : ""); 197 | if (!hashbuf) 198 | { 199 | sqlite3_result_error(pv, "daap_songalbumid() out of memory for hashbuf", -1); 200 | return; 201 | } 202 | 203 | /* Limit hash length to 63 bits, due to signed type in sqlite */ 204 | result = murmur_hash64(hashbuf, strlen(hashbuf), 0) >> 1; 205 | 206 | sqlite3_free(hashbuf); 207 | 208 | sqlite3_result_int64(pv, result); 209 | } 210 | 211 | static int 212 | sqlext_daap_unicode_xcollation(void *notused, int llen, const void *left, int rlen, const void *right) 213 | { 214 | ucs4_t lch; 215 | ucs4_t rch; 216 | int lalpha; 217 | int ralpha; 218 | int rpp; 219 | int ret; 220 | 221 | /* Extract first utf-8 character */ 222 | ret = u8_mbtoucr(&lch, (const uint8_t *)left, llen); 223 | if (ret < 0) 224 | return 0; 225 | 226 | ret = u8_mbtoucr(&rch, (const uint8_t *)right, rlen); 227 | if (ret < 0) 228 | return 0; 229 | 230 | /* Ensure digits and other non-alphanum sort to tail */ 231 | lalpha = uc_is_alpha(lch); 232 | ralpha = uc_is_alpha(rch); 233 | 234 | if (!lalpha && ralpha) 235 | return 1; 236 | else if (lalpha && !ralpha) 237 | return -1; 238 | 239 | /* Compare case and normalization insensitive */ 240 | ret = u8_casecmp((const uint8_t *)left, llen, (const uint8_t*)right, rlen, NULL, UNINORM_NFD, &rpp); 241 | if (ret < 0) 242 | return 0; 243 | 244 | return rpp; 245 | } 246 | 247 | 248 | int 249 | sqlite3_extension_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi) 250 | { 251 | SQLITE_EXTENSION_INIT2(pApi); 252 | int ret; 253 | 254 | ret = sqlite3_create_function(db, "daap_songalbumid", 2, SQLITE_UTF8, NULL, sqlext_daap_songalbumid_xfunc, NULL, NULL); 255 | if (ret != SQLITE_OK) 256 | { 257 | if (pzErrMsg) 258 | *pzErrMsg = sqlite3_mprintf("Could not create daap_songalbumid function: %s\n", sqlite3_errmsg(db)); 259 | 260 | return -1; 261 | } 262 | 263 | ret = sqlite3_create_collation(db, "DAAP", SQLITE_UTF8, NULL, sqlext_daap_unicode_xcollation); 264 | if (ret != SQLITE_OK) 265 | { 266 | if (pzErrMsg) 267 | *pzErrMsg = sqlite3_mprintf("Could not create sqlite3 custom collation DAAP: %s\n", sqlite3_errmsg(db)); 268 | 269 | return -1; 270 | } 271 | 272 | return 0; 273 | } 274 | -------------------------------------------------------------------------------- /src/evbuffer/evbuffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * evbuffer imported from libevent 1.4.13 (event.h) 3 | * Adapted for forked-daapd 4 | * 5 | * Copyright (c) 2000-2007 Niels Provos 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * 3. The name of the author may not be used to endorse or promote products 17 | * derived from this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #ifndef __EVBUFFER_H__ 31 | #define __EVBUFFER_H__ 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | struct evbuffer { 38 | u_char *buffer; 39 | u_char *orig_buffer; 40 | 41 | size_t misalign; 42 | size_t totallen; 43 | size_t off; 44 | 45 | void (*cb)(struct evbuffer *, size_t, size_t, void *); 46 | void *cbarg; 47 | }; 48 | 49 | 50 | #define EVBUFFER_LENGTH(x) (x)->off 51 | #define EVBUFFER_DATA(x) (x)->buffer 52 | #define EVBUFFER_INPUT(x) (x)->input 53 | #define EVBUFFER_OUTPUT(x) (x)->output 54 | 55 | 56 | /** 57 | Allocate storage for a new evbuffer. 58 | 59 | @return a pointer to a newly allocated evbuffer struct, or NULL if an error 60 | occurred 61 | */ 62 | struct evbuffer *evbuffer_new(void); 63 | 64 | 65 | /** 66 | Deallocate storage for an evbuffer. 67 | 68 | @param pointer to the evbuffer to be freed 69 | */ 70 | void evbuffer_free(struct evbuffer *); 71 | 72 | 73 | /** 74 | Expands the available space in an event buffer. 75 | 76 | Expands the available space in the event buffer to at least datlen 77 | 78 | @param buf the event buffer to be expanded 79 | @param datlen the new minimum length requirement 80 | @return 0 if successful, or -1 if an error occurred 81 | */ 82 | int evbuffer_expand(struct evbuffer *, size_t); 83 | 84 | 85 | /** 86 | Append data to the end of an evbuffer. 87 | 88 | @param buf the event buffer to be appended to 89 | @param data pointer to the beginning of the data buffer 90 | @param datlen the number of bytes to be copied from the data buffer 91 | */ 92 | int evbuffer_add(struct evbuffer *, const void *, size_t); 93 | 94 | 95 | 96 | /** 97 | Read data from an event buffer and drain the bytes read. 98 | 99 | @param buf the event buffer to be read from 100 | @param data the destination buffer to store the result 101 | @param datlen the maximum size of the destination buffer 102 | @return the number of bytes read 103 | */ 104 | int evbuffer_remove(struct evbuffer *, void *, size_t); 105 | 106 | 107 | /** 108 | * Read a single line from an event buffer. 109 | * 110 | * Reads a line terminated by either '\r\n', '\n\r' or '\r' or '\n'. 111 | * The returned buffer needs to be freed by the caller. 112 | * 113 | * @param buffer the evbuffer to read from 114 | * @return pointer to a single line, or NULL if an error occurred 115 | */ 116 | char *evbuffer_readline(struct evbuffer *); 117 | 118 | 119 | /** 120 | Move data from one evbuffer into another evbuffer. 121 | 122 | This is a destructive add. The data from one buffer moves into 123 | the other buffer. The destination buffer is expanded as needed. 124 | 125 | @param outbuf the output buffer 126 | @param inbuf the input buffer 127 | @return 0 if successful, or -1 if an error occurred 128 | */ 129 | int evbuffer_add_buffer(struct evbuffer *, struct evbuffer *); 130 | 131 | 132 | /** 133 | Append a formatted string to the end of an evbuffer. 134 | 135 | @param buf the evbuffer that will be appended to 136 | @param fmt a format string 137 | @param ... arguments that will be passed to printf(3) 138 | @return The number of bytes added if successful, or -1 if an error occurred. 139 | */ 140 | int evbuffer_add_printf(struct evbuffer *, const char *fmt, ...) 141 | #ifdef __GNUC__ 142 | __attribute__((format(printf, 2, 3))) 143 | #endif 144 | ; 145 | 146 | 147 | /** 148 | Append a va_list formatted string to the end of an evbuffer. 149 | 150 | @param buf the evbuffer that will be appended to 151 | @param fmt a format string 152 | @param ap a varargs va_list argument array that will be passed to vprintf(3) 153 | @return The number of bytes added if successful, or -1 if an error occurred. 154 | */ 155 | int evbuffer_add_vprintf(struct evbuffer *, const char *fmt, va_list ap); 156 | 157 | 158 | /** 159 | Remove a specified number of bytes data from the beginning of an evbuffer. 160 | 161 | @param buf the evbuffer to be drained 162 | @param len the number of bytes to drain from the beginning of the buffer 163 | */ 164 | void evbuffer_drain(struct evbuffer *, size_t); 165 | 166 | 167 | /** 168 | Write the contents of an evbuffer to a file descriptor. 169 | 170 | The evbuffer will be drained after the bytes have been successfully written. 171 | 172 | @param buffer the evbuffer to be written and drained 173 | @param fd the file descriptor to be written to 174 | @return the number of bytes written, or -1 if an error occurred 175 | @see evbuffer_read() 176 | */ 177 | int evbuffer_write(struct evbuffer *, int); 178 | 179 | 180 | /** 181 | Read from a file descriptor and store the result in an evbuffer. 182 | 183 | @param buf the evbuffer to store the result 184 | @param fd the file descriptor to read from 185 | @param howmuch the number of bytes to be read 186 | @return the number of bytes read, or -1 if an error occurred 187 | @see evbuffer_write() 188 | */ 189 | int evbuffer_read(struct evbuffer *, int, int); 190 | 191 | 192 | /** 193 | Find a string within an evbuffer. 194 | 195 | @param buffer the evbuffer to be searched 196 | @param what the string to be searched for 197 | @param len the length of the search string 198 | @return a pointer to the beginning of the search string, or NULL if the search failed. 199 | */ 200 | u_char *evbuffer_find(struct evbuffer *, const u_char *, size_t); 201 | 202 | /** 203 | Set a callback to invoke when the evbuffer is modified. 204 | 205 | @param buffer the evbuffer to be monitored 206 | @param cb the callback function to invoke when the evbuffer is modified 207 | @param cbarg an argument to be provided to the callback function 208 | */ 209 | void evbuffer_setcb(struct evbuffer *, void (*)(struct evbuffer *, size_t, size_t, void *), void *); 210 | 211 | #endif /* __EVBUFFER_H__ */ 212 | -------------------------------------------------------------------------------- /src/conffile.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2011 Julien BLACHE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | #ifdef HAVE_CONFIG_H 20 | # include 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | 34 | #include 35 | 36 | #include "logger.h" 37 | #include "misc.h" 38 | #include "conffile.h" 39 | 40 | 41 | /* Forward */ 42 | static int cb_loglevel(cfg_t *cfg, cfg_opt_t *opt, const char *value, void *result); 43 | 44 | /* general section structure */ 45 | static cfg_opt_t sec_general[] = 46 | { 47 | CFG_STR("uid", "nobody", CFGF_NONE), 48 | CFG_STR("admin_password", NULL, CFGF_NONE), 49 | CFG_STR("logfile", STATEDIR "/log/" PACKAGE ".log", CFGF_NONE), 50 | CFG_STR("db_path", STATEDIR "/cache/" PACKAGE "/songs3.db", CFGF_NONE), 51 | CFG_INT_CB("loglevel", E_LOG, CFGF_NONE, &cb_loglevel), 52 | CFG_BOOL("ipv6", cfg_true, CFGF_NONE), 53 | CFG_END() 54 | }; 55 | 56 | /* library section structure */ 57 | static cfg_opt_t sec_library[] = 58 | { 59 | CFG_STR("name", "My Music on %h", CFGF_NONE), 60 | CFG_INT("port", 3689, CFGF_NONE), 61 | CFG_STR("password", NULL, CFGF_NONE), 62 | CFG_STR_LIST("directories", NULL, CFGF_NONE), 63 | CFG_STR_LIST("compilations", NULL, CFGF_NONE), 64 | CFG_BOOL("itunes_overrides", cfg_false, CFGF_NONE), 65 | CFG_STR_LIST("no_transcode", NULL, CFGF_NONE), 66 | CFG_STR_LIST("force_transcode", NULL, CFGF_NONE), 67 | CFG_END() 68 | }; 69 | 70 | /* local audio section structure */ 71 | static cfg_opt_t sec_audio[] = 72 | { 73 | CFG_STR("nickname", "Computer", CFGF_NONE), 74 | #ifdef __linux__ 75 | CFG_STR("card", "default", CFGF_NONE), 76 | #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) 77 | CFG_STR("card", "/dev/dsp", CFGF_NONE), 78 | #endif 79 | CFG_STR("mixer", NULL, CFGF_NONE), 80 | CFG_END() 81 | }; 82 | 83 | /* ApEx device section structure */ 84 | static cfg_opt_t sec_apex[] = 85 | { 86 | CFG_STR("password", NULL, CFGF_NONE), 87 | CFG_END() 88 | }; 89 | 90 | /* Config file structure */ 91 | static cfg_opt_t toplvl_cfg[] = 92 | { 93 | CFG_SEC("general", sec_general, CFGF_NONE), 94 | CFG_SEC("library", sec_library, CFGF_NONE), 95 | CFG_SEC("audio", sec_audio, CFGF_NONE), 96 | CFG_SEC("apex", sec_apex, CFGF_MULTI | CFGF_TITLE), 97 | CFG_END() 98 | }; 99 | 100 | cfg_t *cfg; 101 | uint64_t libhash; 102 | uid_t runas_uid; 103 | gid_t runas_gid; 104 | 105 | 106 | static int 107 | cb_loglevel(cfg_t *cfg, cfg_opt_t *opt, const char *value, void *result) 108 | { 109 | if (strcasecmp(value, "fatal") == 0) 110 | *(long int *)result = E_FATAL; 111 | else if (strcasecmp(value, "log") == 0) 112 | *(long int *)result = E_LOG; 113 | else if (strcasecmp(value, "warning") == 0) 114 | *(long int *)result = E_WARN; 115 | else if (strcasecmp(value, "info") == 0) 116 | *(long int *)result = E_INFO; 117 | else if (strcasecmp(value, "debug") == 0) 118 | *(long int *)result = E_DBG; 119 | else if (strcasecmp(value, "spam") == 0) 120 | *(long int *)result = E_SPAM; 121 | else 122 | { 123 | DPRINTF(E_WARN, L_CONF, "Unrecognised loglevel '%s'\n", value); 124 | /* Default to warning */ 125 | *(long int *)result = 1; 126 | } 127 | 128 | return 0; 129 | } 130 | 131 | static int 132 | conffile_expand_libname(cfg_t *lib) 133 | { 134 | char *libname; 135 | char *hostname; 136 | char *s; 137 | char *d; 138 | char *expanded; 139 | struct utsname sysinfo; 140 | size_t len; 141 | size_t olen; 142 | size_t hostlen; 143 | size_t verlen; 144 | int ret; 145 | 146 | libname = cfg_getstr(lib, "name"); 147 | olen = strlen(libname); 148 | 149 | /* Fast path */ 150 | s = strchr(libname, '%'); 151 | if (!s) 152 | { 153 | libhash = murmur_hash64(libname, olen, 0); 154 | return 0; 155 | } 156 | 157 | /* Grab what we need */ 158 | ret = uname(&sysinfo); 159 | if (ret != 0) 160 | { 161 | DPRINTF(E_WARN, L_CONF, "Could not get system name: %s\n", strerror(errno)); 162 | hostname = "Unknown host"; 163 | } 164 | else 165 | hostname = sysinfo.nodename; 166 | 167 | hostlen = strlen(hostname); 168 | verlen = strlen(VERSION); 169 | 170 | /* Compute expanded size */ 171 | len = olen; 172 | s = libname; 173 | while (*s) 174 | { 175 | if (*s == '%') 176 | { 177 | s++; 178 | 179 | switch (*s) 180 | { 181 | case 'h': 182 | len += hostlen; 183 | break; 184 | 185 | case 'v': 186 | len += verlen; 187 | break; 188 | } 189 | } 190 | s++; 191 | } 192 | 193 | expanded = (char *)malloc(len + 1); 194 | if (!expanded) 195 | { 196 | DPRINTF(E_FATAL, L_CONF, "Out of memory\n"); 197 | 198 | return -1; 199 | } 200 | memset(expanded, 0, len + 1); 201 | 202 | /* Do the actual expansion */ 203 | s = libname; 204 | d = expanded; 205 | while (*s) 206 | { 207 | if (*s == '%') 208 | { 209 | s++; 210 | 211 | switch (*s) 212 | { 213 | case 'h': 214 | strcat(d, hostname); 215 | d += hostlen; 216 | break; 217 | 218 | case 'v': 219 | strcat(d, VERSION); 220 | d += verlen; 221 | break; 222 | } 223 | 224 | s++; 225 | } 226 | else 227 | { 228 | *d = *s; 229 | 230 | s++; 231 | d++; 232 | } 233 | } 234 | 235 | cfg_setstr(lib, "name", expanded); 236 | 237 | libhash = murmur_hash64(expanded, strlen(expanded), 0); 238 | 239 | free(expanded); 240 | 241 | return 0; 242 | } 243 | 244 | 245 | int 246 | conffile_load(char *file) 247 | { 248 | cfg_t *lib; 249 | struct passwd *pw; 250 | char *runas; 251 | int ret; 252 | 253 | cfg = cfg_init(toplvl_cfg, CFGF_NONE); 254 | 255 | ret = cfg_parse(cfg, file); 256 | 257 | if (ret == CFG_FILE_ERROR) 258 | { 259 | DPRINTF(E_FATAL, L_CONF, "Could not open config file %s\n", file); 260 | 261 | goto out_fail; 262 | } 263 | else if (ret == CFG_PARSE_ERROR) 264 | { 265 | DPRINTF(E_FATAL, L_CONF, "Parse error in config file %s\n", file); 266 | 267 | goto out_fail; 268 | } 269 | 270 | /* Resolve runas username */ 271 | runas = cfg_getstr(cfg_getsec(cfg, "general"), "uid"); 272 | pw = getpwnam(runas); 273 | if (!pw) 274 | { 275 | DPRINTF(E_FATAL, L_CONF, "Could not lookup user %s: %s\n", runas, strerror(errno)); 276 | 277 | goto out_fail; 278 | } 279 | 280 | runas_uid = pw->pw_uid; 281 | runas_gid = pw->pw_gid; 282 | 283 | lib = cfg_getsec(cfg, "library"); 284 | 285 | if (cfg_size(lib, "directories") == 0) 286 | { 287 | DPRINTF(E_FATAL, L_CONF, "No directories specified for library\n"); 288 | 289 | goto out_fail; 290 | } 291 | 292 | /* Do keyword expansion on library names */ 293 | ret = conffile_expand_libname(lib); 294 | if (ret != 0) 295 | { 296 | DPRINTF(E_FATAL, L_CONF, "Could not expand library name\n"); 297 | 298 | goto out_fail; 299 | } 300 | 301 | return 0; 302 | 303 | out_fail: 304 | cfg_free(cfg); 305 | 306 | return -1; 307 | } 308 | 309 | void 310 | conffile_unload(void) 311 | { 312 | cfg_free(cfg); 313 | } 314 | -------------------------------------------------------------------------------- /configure.in: -------------------------------------------------------------------------------- 1 | dnl Process this file with autoconf to produce a configure script. 2 | 3 | AC_INIT(config.h.in) 4 | AC_CONFIG_MACRO_DIR([m4]) 5 | AC_CONFIG_HEADER(config.h) 6 | AM_INIT_AUTOMAKE(forked-daapd, 0.19gcd) 7 | 8 | dnl Checks for programs. 9 | AC_PROG_CC([clang llvm-gcc]) 10 | AM_PROG_CC_C_O 11 | LT_INIT([disable-static]) 12 | 13 | AC_CHECK_PROG(GPERF, [gperf], [gperf]) 14 | if test "x$GPERF" = x; then 15 | AC_MSG_ERROR([GNU gperf not found, please install it]) 16 | fi 17 | AC_SUBST(GPERF) 18 | 19 | AC_CHECK_PROG(ANTLR, [antlr3], [antlr3]) 20 | if test "x$ANTLR" = x; then 21 | if test -d $srcdir/src/pregen; then 22 | for f in $srcdir/src/pregen/*; do 23 | bf=`basename $f` 24 | ln -sf pregen/$bf $srcdir/src/$bf 25 | done 26 | AC_MSG_NOTICE([antlr3 wrapper not found, using pre-generated files]) 27 | else 28 | AC_MSG_ERROR([antlr3 wrapper not found and pre-generated files not available]) 29 | fi 30 | fi 31 | AC_SUBST(ANTLR) 32 | AM_CONDITIONAL(COND_ANTLR, test "x$ANTLR" != x) 33 | 34 | CFLAGS="$CFLAGS -Wall -D_LARGEFILE_SOURCE" 35 | 36 | AC_CHECK_HEADERS([sys/wait.h]) 37 | AC_CHECK_HEADERS([sys/param.h]) 38 | AC_CHECK_HEADERS([sys/select.h]) 39 | AC_CHECK_HEADERS([dirent.h]) 40 | AC_CHECK_FUNCS(posix_fadvise) 41 | AC_CHECK_FUNCS(strptime) 42 | AC_CHECK_FUNCS(strtok_r) 43 | AC_CHECK_FUNCS(timegm) 44 | 45 | dnl Large File Support (LFS) 46 | AC_SYS_LARGEFILE 47 | AC_TYPE_OFF_T 48 | 49 | AC_ARG_ENABLE(flac, AC_HELP_STRING([--enable-flac], [Enable FLAC support]), 50 | use_flac=true; 51 | CPPFLAGS="${CPPFLAGS} -DFLAC") 52 | 53 | AC_ARG_ENABLE(musepack, AC_HELP_STRING([--enable-musepack], [Enable Musepack support]), 54 | use_musepack=true; 55 | CPPFLAGS="${CPPFLAGS} -DMUSEPACK") 56 | 57 | AC_ARG_ENABLE(itunes, AC_HELP_STRING([--enable-itunes], [Enable iTunes library support]), 58 | use_itunes=true; 59 | CPPFLAGS="${CPPFLAGS} -DITUNES") 60 | 61 | AM_CONDITIONAL(COND_FLAC, test x$use_flac = xtrue) 62 | AM_CONDITIONAL(COND_MUSEPACK, test x$use_musepack = xtrue) 63 | AM_CONDITIONAL(COND_ITUNES, test x$use_itunes = xtrue) 64 | 65 | AC_ARG_WITH(oss4, AC_HELP_STRING([--with-oss4=includedir], [Use OSS4 with soundcard.h in includedir (default /usr/lib/oss/include/sys)]), 66 | [ case "$withval" in 67 | no) 68 | case "$host" in 69 | *-*-linux-*) 70 | use_oss4=false 71 | ;; 72 | *-*-kfreebsd*-*|*-*-freebsd-*) 73 | AC_MSG_ERROR([OSS4 must be enabled on FreeBSD systems]) 74 | ;; 75 | esac 76 | ;; 77 | yes) 78 | use_oss4=true 79 | oss4_includedir=/usr/lib/oss/include/sys 80 | ;; 81 | [[\\/$]]* | ?:[[\\/]]* ) 82 | use_oss4=true 83 | oss4_includedir="$withval" 84 | ;; 85 | *) 86 | AC_MSG_ERROR([expected an absolute directory name for --with-oss4: $withval]) 87 | ;; 88 | esac ], 89 | [ case "$host" in 90 | *-*-linux-*) 91 | use_oss4=false 92 | ;; 93 | *-*-kfreebsd*-*|*-*-freebsd-*) 94 | use_oss4=true 95 | oss4_includedir=/usr/lib/oss/include/sys 96 | ;; 97 | esac ]) 98 | 99 | dnl Checks for libraries. 100 | gl_LIBUNISTRING 101 | 102 | if test x$HAVE_LIBUNISTRING != xyes; then 103 | AC_MSG_ERROR([GNU libunistring is required]) 104 | fi 105 | 106 | PKG_CHECK_MODULES(ZLIB, [ zlib ]) 107 | PKG_CHECK_MODULES(TRE, [ tre ]) 108 | PKG_CHECK_MODULES(CONFUSE, [ libconfuse ]) 109 | PKG_CHECK_MODULES(AVAHI, [ avahi-client >= 0.6.24 ]) 110 | PKG_CHECK_MODULES(SQLITE3, [ sqlite3 >= 3.5.0 ]) 111 | 112 | save_LIBS="$LIBS" 113 | LIBS="$SQLITE3_LIBS" 114 | dnl Check that SQLite3 has the unlock notify API built-in 115 | AC_CHECK_LIB([sqlite3], [sqlite3_unlock_notify], [], AC_MSG_ERROR([SQLite3 was built without unlock notify support])) 116 | dnl Check that SQLite3 has been built with threadsafe operations 117 | AC_MSG_CHECKING([if SQLite3 was built with threadsafe operations support]) 118 | AC_LANG_PUSH([C]) 119 | AC_RUN_IFELSE( 120 | [AC_LANG_PROGRAM([dnl 121 | #include 122 | ], [dnl 123 | int ret = sqlite3_config(SQLITE_CONFIG_MULTITHREAD); 124 | if (ret != SQLITE_OK) 125 | return 1; 126 | return 0;])], 127 | [AC_MSG_RESULT([yes])], [AC_MSG_ERROR([SQLite3 was not built with threadsafe operations support])], 128 | [AC_MSG_RESULT([runtime will tell])]) 129 | AC_LANG_POP([C]) 130 | LIBS="$save_LIBS" 131 | 132 | PKG_CHECK_MODULES(LIBAV, [ libavcodec libavformat libswscale libavutil ]) 133 | dnl Check for av_lock_manager (ffmpeg >= 0.5.1) 134 | save_LIBS="$LIBS" 135 | AC_CHECK_LIB([avcodec], [av_lockmgr_register], , AC_MSG_ERROR([libav (ffmpeg) >= 0.5.1 required])) 136 | LIBS="$save_LIBS" 137 | 138 | # Check for libavformat >= 53; url -> avio switch 139 | _PKG_CONFIG([libavformat_VERSION], [atleast-version=53], [libavformat]) 140 | if test $pkg_failed = yes; then 141 | AM_CONDITIONAL(COND_AVIO, false) 142 | else 143 | AM_CONDITIONAL(COND_AVIO, true) 144 | fi 145 | 146 | PKG_CHECK_MODULES(MINIXML, [ mxml ]) 147 | 148 | AC_CHECK_HEADER(avl.h, , AC_MSG_ERROR([avl.h not found])) 149 | AC_CHECK_LIB([avl], [avl_alloc_tree], [LIBAVL_LIBS="-lavl"], AC_MSG_ERROR([libavl not found])) 150 | AC_SUBST(LIBAVL_LIBS) 151 | 152 | AC_CHECK_HEADER(antlr3.h, , AC_MSG_ERROR([antlr3.h not found])) 153 | AC_CHECK_LIB([antlr3c], [antlr3BaseRecognizerNew], [ANTLR3C_LIBS="-lantlr3c"], AC_MSG_ERROR([ANTLR3 C runtime (libantlr3c) not found])) 154 | AC_CHECK_LIB([antlr3c], [antlr3NewAsciiStringInPlaceStream], 155 | AC_DEFINE(ANTLR3C_NEW_INPUT, 0, [define if antlr3 C runtime uses new input routines]), 156 | AC_DEFINE(ANTLR3C_NEW_INPUT, 1, [define if antlr3 C runtime uses new input routines])) 157 | AC_SUBST(ANTLR3C_LIBS) 158 | 159 | AM_PATH_LIBGCRYPT([1:1.2.0], , AC_MSG_ERROR([libgcrypt not found])) 160 | AM_PATH_GPG_ERROR([1.6], , AC_MSG_ERROR([libgpg-error not found])) 161 | 162 | if test x$use_flac = xtrue; then 163 | PKG_CHECK_MODULES(FLAC, [ flac ]) 164 | fi 165 | 166 | if test x$use_musepack = xtrue; then 167 | PKG_CHECK_MODULES(TAGLIB, [ taglib_c ]) 168 | fi 169 | 170 | if test x$use_itunes = xtrue; then 171 | PKG_CHECK_MODULES(LIBPLIST, [ libplist >= 0.16 ]) 172 | fi 173 | 174 | case "$host" in 175 | *-*-linux-*) 176 | if test x$use_oss4 != xtrue; then 177 | use_alsa=true 178 | PKG_CHECK_MODULES(ALSA, [ alsa ]) 179 | AC_DEFINE(LAUDIO_USE_ALSA, 1, [define if local audio output uses ALSA]) 180 | fi 181 | 182 | AC_CHECK_HEADERS([sys/eventfd.h]) 183 | AC_CHECK_FUNCS(eventfd) 184 | 185 | AC_CHECK_HEADER(sys/timerfd.h, , AC_MSG_ERROR([timerfd required; glibc 2.8+ recommended])) 186 | AC_CHECK_FUNC(timerfd_create, , AC_MSG_ERROR([timerfd required; glibc 2.8+ recommended])) 187 | ;; 188 | esac 189 | 190 | if test x$use_oss4 = xtrue; then 191 | OSS4CPPFLAGS="-I$oss4_includedir" 192 | 193 | save_CPPFLAGS="$CPPFLAGS" 194 | CPPFLAGS="$OSS4CPPFLAGS" 195 | 196 | AC_CHECK_HEADER(soundcard.h, , AC_MSG_ERROR([soundcard.h not found in $oss4_includedir])) 197 | 198 | CPPFLAGS="$save_CPPFLAGS" 199 | fi 200 | 201 | AM_CONDITIONAL(COND_ALSA, test x$use_alsa = xtrue) 202 | AM_CONDITIONAL(COND_OSS4, test x$use_oss4 = xtrue) 203 | AC_SUBST(OSS4CPPFLAGS) 204 | 205 | AC_CHECK_SIZEOF(void *) 206 | 207 | AC_CHECK_HEADERS(getopt.h,,) 208 | AC_CHECK_HEADERS(stdint.h,,) 209 | 210 | dnl Checks for header files. 211 | AC_HEADER_STDC 212 | AC_HEADER_SYS_WAIT 213 | 214 | dnl Check for libdispatch, C Blocks support in libdispatch and C Blocks support in the compiler 215 | AC_CHECK_HEADER(dispatch/dispatch.h, , AC_MSG_ERROR([dispatch/dispatch.h not found])) 216 | AC_CHECK_LIB([dispatch], [dispatch_main], [LIBDISPATCH_LIBS="-ldispatch"], AC_MSG_ERROR([libdispatch not found])) 217 | AC_CHECK_LIB([dispatch], [dispatch_after], [LIBDISPATCH_LIBS="-ldispatch"], AC_MSG_ERROR([libdispatch built without Blocks support])) 218 | AC_SUBST(LIBDISPATCH_LIBS) 219 | 220 | save_LIBS="$LIBS" 221 | DISPATCH_C_BLOCKS 222 | if test "x$have_cblocks" != xtrue; then 223 | AC_MSG_ERROR([C compiler doesn't support C Blocks extension]) 224 | fi 225 | CBLOCKS_LIBS="$LIBS" 226 | AC_SUBST(CBLOCKS_LIBS) 227 | LIBS="$save_LIBS" 228 | 229 | AC_OUTPUT(src/Makefile sqlext/Makefile Makefile) 230 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | Installation instructions for forked-daapd 2 | ------------------------------------------ 3 | 4 | There are two ways to install forked-daapd: from a tarball or from the git 5 | tree. The tarball contains a working build system and pre-generated ANTLR3 6 | parsers; the git tree doesn't and requires more tools to generate the build 7 | system and the ANTLR3 parsers. 8 | 9 | In both cases the installation procedure is the traditional ./configure; 10 | make; make install. Please read this file carefully before proceeding. 11 | 12 | 13 | System-specific requirements: 14 | - Linux: 15 | + glibc 2.13+ (bugfix: process-wide setgroups(), glibc BZ#10563) 16 | + libasound (ALSA sound support - or you can use OSS4) 17 | - FreeBSD: 18 | + OSS4 sound support 19 | + libiconv 20 | 21 | Tools: 22 | - The clang C compiler from the LLVM project. forked-daapd uses Blocks, 23 | an extension to the C language that is not supported by gcc. Along with 24 | clang, you'll also need the Blocks runtime, libblocksruntime. 25 | 26 | - pkg-config 27 | - gperf 3.x 28 | from 29 | 30 | Libraries: 31 | - libantlr3c (ANTLR3 C runtime, version 3.2 for tarball builds) 32 | from 33 | - Avahi client libraries (avahi-client), 0.6.24 minimum 34 | from 35 | - sqlite3 3.5.0+ with unlock notify API enabled (read below) 36 | from 37 | - libav 0.6+/0.7+ (or ffmpeg 0.5.1+) 38 | from 39 | - libconfuse 40 | from 41 | - libdispatch 42 | /!\ Read below 43 | - libtre 44 | from 45 | - libavl 46 | /!\ Read below 47 | - MiniXML (aka mxml or libmxml) 48 | from 49 | - gcrypt 1.2.0+ 50 | from 51 | - zlib 52 | from 53 | - libunistring 0.9.3+ 54 | from 55 | - libflac (optional - FLAC support) 56 | from 57 | - taglib (optional - Musepack support) 58 | from 59 | - libplist 0.16+ (optional - iTunes XML support) 60 | from 61 | 62 | If using binary packages, remember that you need the development packages to 63 | build forked-daapd (usually named -dev or -devel). 64 | 65 | libdispatch for Linux and its dependencies can be found in the Debian archive; 66 | you need at least libdispatch from SVN rev 197 + Debian patches (Linux support), 67 | libkqueue 0.9.2 and libpthread_workqueue 0.7. At this time, it's probably 68 | easiest to use whatever versions happen to be in Debian unstable. 69 | 70 | libavl is not the GNU libavl. There doesn't seem to be an upstream website 71 | anymore, but you'll find the source tarball alongside the forked-daapd 72 | release tarballs (see below for the URL). Alternatively, you can fetch it from 73 | any Debian mirror, too (it'll be in /debian/pool/main/liba/libavl). 74 | 75 | sqlite3 needs to be built with support for the unlock notify API; this isn't 76 | always the case in binary packages, so you may need to rebuild sqlite3 to 77 | enable the unlock notify API (you can check for the presence of the 78 | sqlite3_unlock_notify symbol in the sqlite3 library). Refer to the sqlite3 79 | documentation, look for SQLITE_ENABLE_UNLOCK_NOTIFY. 80 | 81 | 82 | Note about libav (ffmpeg) 83 | ------------------------- 84 | 85 | libav (ffmpeg) is a central piece of forked-daapd and most other FLOSS 86 | multimedia applications. The version of libav you use will potentially have a 87 | great influence on your experience with forked-daapd. 88 | 89 | The following versions of libav (ffmpeg) are supported and known to work: 90 | - ffmpeg 0.5.x: has issues with metadata (tags) extraction, notably with 91 | MP3 files and ID3 tags in general; 92 | - libav 0.6.x: known to work better with regard to metadata extraction; 93 | - libav 0.7.x: better yet 94 | 95 | Note that forked-daapd uses libav since the ffmpeg/libav fork during the 96 | 0.6.x series. 97 | 98 | 99 | Building from the git tree 100 | -------------------------- 101 | 102 | Gitweb: 103 | Git tree: 104 | 105 | Required tools: 106 | - ANTLR v3 is required to build forked-daapd, along with its C runtime 107 | (libantlr3c). Use at least version 3.1.3 of ANTLR v3 and the matching 108 | C runtime version. 109 | 110 | - Java runtime: ANTLR is written in Java and as such a JRE is required to 111 | run the tool. The JRE is enough, you don't need a full JDK. 112 | 113 | - autotools: autoconf 2.63+, automake 1.10+, libtool 2.2. Run autoreconf -i 114 | at the top of the source tree to generate the build system. 115 | 116 | - gettext: libunistring requires iconv and gettext provides the autotools 117 | macro definitions for iconv. 118 | 119 | Start by generating the build system by running autoreconf -i. This will 120 | generate the configure script and Makefile.in. 121 | 122 | The configure script will look for a wrapper called antlr3 in the PATH to 123 | invoke ANTLR3. If your installation of ANTLR3 does not come with such a 124 | wrapper, create one as follows: 125 | 126 | #!/bin/sh 127 | CLASSPATH=... 128 | exec /path/to/java -cp $CLASSPATH org.antlr.Tool "@" 129 | 130 | Adjust the CLASSPATH as needed so that Java will find all the jars needed 131 | by ANTLR3. 132 | 133 | The parsers will be generated during the build, no manual intervention is 134 | needed. 135 | 136 | 137 | Building from the tarball 138 | ------------------------- 139 | 140 | Download URL: 141 | 142 | When building forked-daapd from a release tarball, the usual ./configure; 143 | make; make install procedure applies. 144 | 145 | FLAC and Musepack support are optional. If not enabled, metadata extraction 146 | will fail on these files. 147 | 148 | Support for iTunes Music Library XML format is optional. Use --enable-itunes 149 | to enable this feature. 150 | 151 | Recommended build settings: 152 | ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --enable-flac --enable-musepack 153 | 154 | After installation, edit the configuration file, /etc/forked-daapd.conf and 155 | adjust the values at your convenience. 156 | 157 | forked-daapd will drop privileges to any user you'll specify in the 158 | configuration file if it's started as root. It's recommended to create a 159 | dedicated user without login privileges. 160 | 161 | This user must have read permission on your library (you can create a group for 162 | this and make the user a member of the group, for instance) and read/write 163 | permissions on the database location ($localstatedir/cache/forked-daapd by 164 | default). 165 | 166 | You'll need an init script if you want to start forked-daapd at boot. A simple 167 | init script will do, forked-daapd daemonizes all by itself and creates a 168 | pidfile under /var/run. Different distributions have different standards for 169 | init scripts and some do not use init scripts anymore; check the documentation 170 | for your distribution. 171 | 172 | For dependency-based boot systems, here are the forked-daapd dependencies: 173 | - local filesystems 174 | - network filesystems, if needed in your setup (library on NFS, ...) 175 | - networking 176 | - NTP 177 | - Avahi daemon 178 | 179 | The LSB header below sums it up: 180 | 181 | ### BEGIN INIT INFO 182 | # Provides: forked-daapd 183 | # Required-Start: $local_fs $remote_fs $network $time avahi 184 | # Required-Stop: $local_fs $remote_fs $network $time 185 | # Default-Start: 2 3 4 5 186 | # Default-Stop: 0 1 6 187 | # Short-Description: media server with support for RSP, DAAP, DACP and AirTunes 188 | # Description: forked-daapd is an iTunes-compatible media server for 189 | # sharing your music library over the local network with RSP 190 | # clients like the SoundBridge from Roku and DAAP clients like 191 | # iTunes. It can also stream music to AirTunes devices. 192 | ### END INIT INFO 193 | -------------------------------------------------------------------------------- /src/logger.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2011 Julien BLACHE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | #ifdef HAVE_CONFIG_H 20 | # include 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | 33 | #include 34 | 35 | #include "conffile.h" 36 | #include "logger.h" 37 | 38 | 39 | static dispatch_queue_t logger_sq; 40 | static int logsync; 41 | static int logdomains; 42 | static int threshold; 43 | static int console; 44 | static char *logfilename; 45 | static FILE *logfile; 46 | static char *labels[] = { "config", "daap", "db", "httpd", "main", "mdns", "misc", "rsp", "scan", "xcode", "-free-", "remote", "dacp", "ffmpeg", "artwork", "player", "raop", "laudio", "dmap", "dbperf", "http" }; 47 | 48 | 49 | static int 50 | set_logdomains(char *domains) 51 | { 52 | char *ptr; 53 | char *d; 54 | int i; 55 | 56 | logdomains = 0; 57 | 58 | while ((d = strtok_r(domains, " ,", &ptr))) 59 | { 60 | domains = NULL; 61 | 62 | for (i = 0; i < N_LOGDOMAINS; i++) 63 | { 64 | if (strcmp(d, labels[i]) == 0) 65 | { 66 | logdomains |= (1 << i); 67 | break; 68 | } 69 | } 70 | 71 | if (i == N_LOGDOMAINS) 72 | { 73 | fprintf(stderr, "Error: unknown log domain '%s'\n", d); 74 | return -1; 75 | } 76 | } 77 | 78 | return 0; 79 | } 80 | 81 | static void 82 | vlogger(int severity, int domain, const char *fmt, va_list args) 83 | { 84 | va_list ap; 85 | char *msg; 86 | time_t t; 87 | int ret; 88 | dispatch_block_t logblock; 89 | 90 | if (!((1 << domain) & logdomains) || (severity > threshold)) 91 | return; 92 | 93 | t = time(NULL); 94 | 95 | va_copy(ap, args); 96 | ret = vsnprintf(NULL, 0, fmt, ap); 97 | va_end(ap); 98 | 99 | if (ret <= 0) 100 | return; 101 | 102 | msg = (char *)malloc(ret + 1); 103 | if (!msg) 104 | return; 105 | 106 | va_copy(ap, args); 107 | ret = vsnprintf(msg, ret + 1, fmt, ap); 108 | va_end(ap); 109 | 110 | if (ret < 0) 111 | { 112 | free(msg); 113 | return; 114 | } 115 | 116 | 117 | logblock = ^{ 118 | char stamp[32]; 119 | int ret; 120 | 121 | if (!logfile && !console) 122 | { 123 | free(msg); 124 | return; 125 | } 126 | 127 | if (logfile) 128 | { 129 | ret = strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M:%S", localtime(&t)); 130 | if (ret == 0) 131 | stamp[0] = '\0'; 132 | 133 | fprintf(logfile, "[%s] %8s: %s", stamp, labels[domain], msg); 134 | 135 | fflush(logfile); 136 | } 137 | 138 | if (console) 139 | fprintf(stderr, "%8s: %s", labels[domain], msg); 140 | 141 | free(msg); 142 | }; 143 | 144 | if (logsync) 145 | dispatch_sync(logger_sq, logblock); 146 | else 147 | dispatch_async(logger_sq, logblock); 148 | } 149 | 150 | static void 151 | vlogger_early(int severity, int domain, const char *fmt, va_list args) 152 | { 153 | va_list ap; 154 | char stamp[32]; 155 | time_t t; 156 | int ret; 157 | 158 | if (!((1 << domain) & logdomains) || (severity > threshold)) 159 | return; 160 | 161 | if (!logfile && !console) 162 | return; 163 | 164 | if (logfile) 165 | { 166 | t = time(NULL); 167 | ret = strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M:%S", localtime(&t)); 168 | if (ret == 0) 169 | stamp[0] = '\0'; 170 | 171 | fprintf(logfile, "[%s] %8s: ", stamp, labels[domain]); 172 | 173 | va_copy(ap, args); 174 | vfprintf(logfile, fmt, ap); 175 | va_end(ap); 176 | 177 | fflush(logfile); 178 | } 179 | 180 | if (console) 181 | { 182 | fprintf(stderr, "%8s: ", labels[domain]); 183 | 184 | va_copy(ap, args); 185 | vfprintf(stderr, fmt, ap); 186 | va_end(ap); 187 | } 188 | } 189 | 190 | void 191 | DPRINTF(int severity, int domain, const char *fmt, ...) 192 | { 193 | va_list ap; 194 | 195 | va_start(ap, fmt); 196 | 197 | if (!logger_sq) 198 | vlogger_early(severity, domain, fmt, ap); 199 | else 200 | vlogger(severity, domain, fmt, ap); 201 | 202 | va_end(ap); 203 | } 204 | 205 | void 206 | logger_ffmpeg(void *ptr, int level, const char *fmt, va_list ap) 207 | { 208 | int severity; 209 | 210 | /* Can't use a switch() because some definitions have the same value */ 211 | if ((level == AV_LOG_FATAL) || (level == AV_LOG_ERROR)) 212 | severity = E_LOG; 213 | else if ((level == AV_LOG_WARNING) || (level == AV_LOG_INFO) || (level == AV_LOG_VERBOSE)) 214 | severity = E_WARN; 215 | else if (level == AV_LOG_DEBUG) 216 | severity = E_DBG; 217 | else if (level == AV_LOG_QUIET) 218 | severity = E_SPAM; 219 | else 220 | severity = E_LOG; 221 | 222 | vlogger(severity, L_FFMPEG, fmt, ap); 223 | } 224 | 225 | #ifdef LAUDIO_USE_ALSA 226 | void 227 | logger_alsa(const char *file, int line, const char *function, int err, const char *fmt, ...) 228 | { 229 | va_list ap; 230 | 231 | va_start(ap, fmt); 232 | vlogger(E_LOG, L_LAUDIO, fmt, ap); 233 | va_end(ap); 234 | } 235 | #endif /* LAUDIO_USE_ALSA */ 236 | 237 | /* Queue: logger_sq */ 238 | static void 239 | logger_reinit_task(void *arg) 240 | { 241 | FILE *fp; 242 | 243 | if (!logfile) 244 | return; 245 | 246 | fp = fopen(logfilename, "a"); 247 | if (!fp) 248 | { 249 | DPRINTF(E_LOG, L_MAIN, "Could not reopen logfile: %s\n", strerror(errno)); 250 | return; 251 | } 252 | 253 | fclose(logfile); 254 | logfile = fp; 255 | } 256 | 257 | void 258 | logger_reinit(void) 259 | { 260 | if (!logger_sq) 261 | return; 262 | 263 | dispatch_sync_f(logger_sq, NULL, logger_reinit_task); 264 | } 265 | 266 | 267 | /* The functions below are used at init time before switching to dispatch mode */ 268 | void 269 | logger_domains(void) 270 | { 271 | int i; 272 | 273 | fprintf(stdout, "%s", labels[0]); 274 | 275 | for (i = 1; i < N_LOGDOMAINS; i++) 276 | fprintf(stdout, ", %s", labels[i]); 277 | 278 | fprintf(stdout, "\n"); 279 | } 280 | 281 | void 282 | logger_detach(void) 283 | { 284 | console = 0; 285 | } 286 | 287 | int 288 | logger_start_dispatch(int sync) 289 | { 290 | logger_sq = dispatch_queue_create("org.forked-daapd.logger", NULL); 291 | if (!logger_sq) 292 | return -1; 293 | 294 | logsync = sync; 295 | 296 | return 0; 297 | } 298 | 299 | int 300 | logger_init(char *file, char *domains, int severity) 301 | { 302 | int ret; 303 | 304 | if ((sizeof(labels) / sizeof(labels[0])) != N_LOGDOMAINS) 305 | { 306 | fprintf(stderr, "WARNING: log domains do not match\n"); 307 | 308 | return -1; 309 | } 310 | 311 | logger_sq = NULL; 312 | console = 1; 313 | threshold = severity; 314 | 315 | if (domains) 316 | { 317 | ret = set_logdomains(domains); 318 | if (ret < 0) 319 | return ret; 320 | } 321 | else 322 | logdomains = ~0; 323 | 324 | if (!file) 325 | return 0; 326 | 327 | logfile = fopen(file, "a"); 328 | if (!logfile) 329 | { 330 | fprintf(stderr, "Could not open logfile %s: %s\n", file, strerror(errno)); 331 | 332 | return -1; 333 | } 334 | 335 | ret = fchown(fileno(logfile), runas_uid, 0); 336 | if (ret < 0) 337 | fprintf(stderr, "Failed to set ownership on logfile: %s\n", strerror(errno)); 338 | 339 | ret = fchmod(fileno(logfile), 0644); 340 | if (ret < 0) 341 | fprintf(stderr, "Failed to set permissions on logfile: %s\n", strerror(errno)); 342 | 343 | logfilename = file; 344 | 345 | return 0; 346 | } 347 | 348 | void 349 | logger_deinit(void) 350 | { 351 | if (logger_sq) 352 | { 353 | dispatch_sync(logger_sq, 354 | ^{ 355 | if (logfile) 356 | fclose(logfile); 357 | }); 358 | 359 | dispatch_release(logger_sq); 360 | logger_sq = NULL; 361 | } 362 | else 363 | { 364 | if (logfile) 365 | fclose(logfile); 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /src/scan-flac.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Implementation file for server side format conversion. 3 | * 4 | * Copyright (C) 2005 Timo J. Rinne (tri@iki.fi) 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | */ 20 | 21 | #ifdef HAVE_CONFIG_H 22 | # include 23 | #endif 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #ifdef HAVE_STDINT_H 30 | #include 31 | #endif 32 | #include 33 | #include 34 | #include 35 | #ifdef HAVE_DIRENT_H 36 | # include /* why here? For osx 10.2, of course! */ 37 | #endif 38 | #include /* htons and friends */ 39 | 40 | #include 41 | 42 | #include "logger.h" 43 | #include "db.h" 44 | 45 | #include 46 | 47 | #define TRUE ((1 == 1)) 48 | #define FALSE (!TRUE) 49 | 50 | typedef struct media_file_info MP3FILE; 51 | 52 | #define GET_VORBIS_COMMENT(comment, name, len) (char*) \ 53 | (((strncasecmp(name, (char*)(comment).entry, strlen(name)) == 0) && \ 54 | ((comment).entry[strlen(name)] == '=')) ? \ 55 | ((*(len) = (comment).length - (strlen(name) + 1)), \ 56 | (&((comment).entry[strlen(name) + 1]))) : \ 57 | NULL) 58 | 59 | /** 60 | * scan a flac file for metainfo. 61 | * 62 | * @param filename file to read metainfo for 63 | * @param pmp3 MP3FILE structure to fill 64 | * @returns TRUE if file should be added to DB, FALSE otherwise 65 | */ 66 | int scan_get_flacinfo(char *filename, MP3FILE *pmp3) { 67 | FLAC__Metadata_Chain *chain; 68 | FLAC__Metadata_Iterator *iterator; 69 | FLAC__StreamMetadata *block; 70 | int found=0; 71 | unsigned int sec, ms; 72 | int i; 73 | char *val; 74 | size_t len; 75 | char tmp; 76 | 77 | chain = FLAC__metadata_chain_new(); 78 | if (! chain) { 79 | DPRINTF(E_WARN,L_SCAN,"Cannot allocate FLAC metadata chain\n"); 80 | return FALSE; 81 | } 82 | if (! FLAC__metadata_chain_read(chain, filename)) { 83 | DPRINTF(E_WARN,L_SCAN,"Cannot read FLAC metadata from %s\n", filename); 84 | FLAC__metadata_chain_delete(chain); 85 | return FALSE; 86 | } 87 | 88 | iterator = FLAC__metadata_iterator_new(); 89 | if (! iterator) { 90 | DPRINTF(E_WARN,L_SCAN,"Cannot allocate FLAC metadata iterator\n"); 91 | FLAC__metadata_chain_delete(chain); 92 | return FALSE; 93 | } 94 | 95 | FLAC__metadata_iterator_init(iterator, chain); 96 | do { 97 | block = FLAC__metadata_iterator_get_block(iterator); 98 | 99 | if (block->type == FLAC__METADATA_TYPE_STREAMINFO) { 100 | sec = (unsigned int)(block->data.stream_info.total_samples / 101 | block->data.stream_info.sample_rate); 102 | ms = (unsigned int)(((block->data.stream_info.total_samples % 103 | block->data.stream_info.sample_rate) * 1000) / 104 | block->data.stream_info.sample_rate); 105 | if ((sec == 0) && (ms == 0)) 106 | break; /* Info is crap, escape div-by-zero. */ 107 | pmp3->song_length = (sec * 1000) + ms; 108 | pmp3->bitrate = (uint32_t)((pmp3->file_size) / (((sec * 1000) + ms) / 8)); 109 | pmp3->samplerate = block->data.stream_info.sample_rate; 110 | pmp3->bits_per_sample = block->data.stream_info.bits_per_sample; 111 | pmp3->sample_count = block->data.stream_info.total_samples; 112 | 113 | found |= 1; 114 | if(found == 3) 115 | break; 116 | } 117 | 118 | if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) { 119 | for (i = 0; i < (int)block->data.vorbis_comment.num_comments; i++) { 120 | if ((val = GET_VORBIS_COMMENT(block->data.vorbis_comment.comments[i], 121 | "ARTIST", &len))) { 122 | if ((pmp3->artist = calloc(len + 1, 1)) != NULL) 123 | strncpy(pmp3->artist, val, len); 124 | } else if ((val = GET_VORBIS_COMMENT(block->data.vorbis_comment.comments[i], 125 | "TITLE", &len))) { 126 | if ((pmp3->title = calloc(len + 1, 1)) != NULL) 127 | strncpy(pmp3->title, val, len); 128 | } else if ((val = GET_VORBIS_COMMENT(block->data.vorbis_comment.comments[i], 129 | "ALBUMARTIST", &len))) { 130 | if ((pmp3->album_artist = calloc(len + 1, 1)) != NULL) 131 | strncpy(pmp3->album_artist, val, len); 132 | } else if ((val = GET_VORBIS_COMMENT(block->data.vorbis_comment.comments[i], 133 | "ALBUM", &len))) { 134 | if ((pmp3->album = calloc(len + 1, 1)) != NULL) 135 | strncpy(pmp3->album, val, len); 136 | } else if ((val = GET_VORBIS_COMMENT(block->data.vorbis_comment.comments[i], 137 | "GENRE", &len))) { 138 | if ((pmp3->genre = calloc(len + 1, 1)) != NULL) 139 | strncpy(pmp3->genre, val, len); 140 | } else if ((val = GET_VORBIS_COMMENT(block->data.vorbis_comment.comments[i], 141 | "COMPOSER", &len))) { 142 | if ((pmp3->composer = calloc(len + 1, 1)) != NULL) 143 | strncpy(pmp3->composer, val, len); 144 | } else if ((val = GET_VORBIS_COMMENT(block->data.vorbis_comment.comments[i], 145 | "COMMENT", &len))) { 146 | if(pmp3->comment) 147 | free(pmp3->comment); /* was description */ 148 | if ((pmp3->comment = calloc(len + 1, 1)) != NULL) 149 | strncpy(pmp3->comment, val, len); 150 | } else if ((val = GET_VORBIS_COMMENT(block->data.vorbis_comment.comments[i], 151 | "DESCRIPTION", &len))) { 152 | if(!pmp3->comment) { 153 | if ((pmp3->comment = calloc(len + 1, 1)) != NULL) 154 | strncpy(pmp3->comment, val, len); 155 | } 156 | } else if ((val = GET_VORBIS_COMMENT(block->data.vorbis_comment.comments[i], 157 | "TRACKNUMBER", &len))) { 158 | tmp = *(val + len); 159 | *(val + len) = '\0'; 160 | pmp3->track = atoi(val); 161 | *(val + len) = tmp; 162 | } else if ((val = GET_VORBIS_COMMENT(block->data.vorbis_comment.comments[i], 163 | "DISCNUMBER", &len))) { 164 | tmp = *(val + len); 165 | *(val + len) = '\0'; 166 | pmp3->disc = atoi(val); 167 | *(val + len) = tmp; 168 | } else if ((val = GET_VORBIS_COMMENT(block->data.vorbis_comment.comments[i], 169 | "YEAR", &len))) { 170 | tmp = *(val + len); 171 | *(val + len) = '\0'; 172 | pmp3->year = atoi(val); 173 | *(val + len) = tmp; 174 | } else if ((val = GET_VORBIS_COMMENT(block->data.vorbis_comment.comments[i], 175 | "DATE", &len))) { 176 | tmp = *(val + len); 177 | *(val + len) = '\0'; 178 | pmp3->year = atoi(val); 179 | *(val + len) = tmp; 180 | } 181 | } 182 | found |= 2; 183 | if(found == 3) 184 | break; 185 | } 186 | } while (FLAC__metadata_iterator_next(iterator)); 187 | 188 | if (!found) { 189 | DPRINTF(E_WARN,L_SCAN,"Cannot find FLAC metadata in %s\n", filename); 190 | } 191 | 192 | FLAC__metadata_iterator_delete(iterator); 193 | FLAC__metadata_chain_delete(chain); 194 | return TRUE; 195 | } 196 | -------------------------------------------------------------------------------- /src/DAAP2SQL.g: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2011 Julien BLACHE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | tree grammar DAAP2SQL; 20 | 21 | options { 22 | tokenVocab = DAAP; 23 | ASTLabelType = pANTLR3_BASE_TREE; 24 | language = C; 25 | } 26 | 27 | @header { 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "logger.h" 35 | #include "db.h" 36 | #include "daap_query.h" 37 | } 38 | 39 | @members { 40 | struct dmap_query_field_map { 41 | char *dmap_field; 42 | char *db_col; 43 | int as_int; 44 | }; 45 | 46 | /* gperf static hash, daap_query.gperf */ 47 | #include "daap_query_hash.c" 48 | } 49 | 50 | query returns [ pANTLR3_STRING result ] 51 | @init { $result = NULL; } 52 | : e = expr 53 | { 54 | if (!$e.valid) 55 | { 56 | $result = NULL; 57 | } 58 | else 59 | { 60 | $result = $e.result->factory->newRaw($e.result->factory); 61 | $result->append8($result, "("); 62 | $result->appendS($result, $e.result); 63 | $result->append8($result, ")"); 64 | } 65 | } 66 | ; 67 | 68 | expr returns [ pANTLR3_STRING result, int valid ] 69 | @init { $result = NULL; $valid = 1; } 70 | : ^(OPAND a = expr b = expr) 71 | { 72 | if (!$a.valid || !$b.valid) 73 | { 74 | $valid = 0; 75 | } 76 | else 77 | { 78 | $result = $a.result->factory->newRaw($a.result->factory); 79 | $result->append8($result, "("); 80 | $result->appendS($result, $a.result); 81 | $result->append8($result, " AND "); 82 | $result->appendS($result, $b.result); 83 | $result->append8($result, ")"); 84 | } 85 | } 86 | | ^(OPOR a = expr b = expr) 87 | { 88 | if (!$a.valid || !$b.valid) 89 | { 90 | $valid = 0; 91 | } 92 | else 93 | { 94 | $result = $a.result->factory->newRaw($a.result->factory); 95 | $result->append8($result, "("); 96 | $result->appendS($result, $a.result); 97 | $result->append8($result, " OR "); 98 | $result->appendS($result, $b.result); 99 | $result->append8($result, ")"); 100 | } 101 | } 102 | | STR 103 | { 104 | pANTLR3_STRING str; 105 | pANTLR3_UINT8 field; 106 | pANTLR3_UINT8 val; 107 | pANTLR3_UINT8 escaped; 108 | ANTLR3_UINT8 op; 109 | int neg_op; 110 | const struct dmap_query_field_map *dqfm; 111 | char *end; 112 | long long llval; 113 | 114 | escaped = NULL; 115 | 116 | $result = $STR.text->factory->newRaw($STR.text->factory); 117 | 118 | str = $STR.text->toUTF8($STR.text); 119 | 120 | /* NOTE: the lexer delivers the string without quotes 121 | which may not be obvious from the grammar due to embedded code 122 | */ 123 | 124 | /* Make daap.songalbumid:0 a no-op */ 125 | if (strcmp((char *)str->chars, "daap.songalbumid:0") == 0) 126 | { 127 | $result->append8($result, "1 = 1"); 128 | 129 | goto STR_out; 130 | } 131 | 132 | field = str->chars; 133 | 134 | val = field; 135 | while ((*val != '\0') && ((*val == '.') 136 | || (*val == '-') 137 | || ((*val >= 'a') && (*val <= 'z')) 138 | || ((*val >= 'A') && (*val <= 'Z')) 139 | || ((*val >= '0') && (*val <= '9')))) 140 | { 141 | val++; 142 | } 143 | 144 | if (*field == '\0') 145 | { 146 | DPRINTF(E_LOG, L_DAAP, "No field name found in clause '\%s'\n", field); 147 | $valid = 0; 148 | goto STR_result_valid_0; /* ABORT */ 149 | } 150 | 151 | if (*val == '\0') 152 | { 153 | DPRINTF(E_LOG, L_DAAP, "No operator found in clause '\%s'\n", field); 154 | $valid = 0; 155 | goto STR_result_valid_0; /* ABORT */ 156 | } 157 | 158 | op = *val; 159 | *val = '\0'; 160 | val++; 161 | 162 | if (op == '!') 163 | { 164 | if (*val == '\0') 165 | { 166 | DPRINTF(E_LOG, L_DAAP, "Negation found but operator missing in clause '\%s\%c'\n", field, op); 167 | $valid = 0; 168 | goto STR_result_valid_0; /* ABORT */ 169 | } 170 | 171 | neg_op = 1; 172 | op = *val; 173 | val++; 174 | } 175 | else 176 | neg_op = 0; 177 | 178 | /* Lookup DMAP field in the query field map */ 179 | dqfm = daap_query_field_lookup((char *)field, strlen((char *)field)); 180 | if (!dqfm) 181 | { 182 | DPRINTF(E_LOG, L_DAAP, "DMAP field '\%s' is not a valid field in queries\n", field); 183 | $valid = 0; 184 | goto STR_result_valid_0; /* ABORT */ 185 | } 186 | 187 | /* Empty values OK for string fields, NOK for integer */ 188 | if (*val == '\0') 189 | { 190 | if (dqfm->as_int) 191 | { 192 | DPRINTF(E_LOG, L_DAAP, "No value given in clause '\%s\%s\%c'\n", field, (neg_op) ? "!" : "", op); 193 | $valid = 0; 194 | goto STR_result_valid_0; /* ABORT */ 195 | } 196 | 197 | /* Need to check against NULL too */ 198 | if (op == ':') 199 | $result->append8($result, "("); 200 | } 201 | 202 | $result->append8($result, dqfm->db_col); 203 | 204 | /* Int field: check integer conversion */ 205 | if (dqfm->as_int) 206 | { 207 | errno = 0; 208 | llval = strtoll((const char *)val, &end, 10); 209 | 210 | if (((errno == ERANGE) && ((llval == LLONG_MAX) || (llval == LLONG_MIN))) 211 | || ((errno != 0) && (llval == 0))) 212 | { 213 | DPRINTF(E_LOG, L_DAAP, "Value '\%s' in clause '\%s\%s\%c\%s' does not convert to an integer type\n", 214 | val, field, (neg_op) ? "!" : "", op, val); 215 | $valid = 0; 216 | goto STR_result_valid_0; /* ABORT */ 217 | } 218 | 219 | if (end == (char *)val) 220 | { 221 | DPRINTF(E_LOG, L_DAAP, "Value '\%s' in clause '\%s\%s\%c\%s' does not represent an integer value\n", 222 | val, field, (neg_op) ? "!" : "", op, val); 223 | $valid = 0; 224 | goto STR_result_valid_0; /* ABORT */ 225 | } 226 | 227 | *end = '\0'; /* Cut out potential garbage - we're being kind */ 228 | } 229 | /* String field: escape string, check for '*' */ 230 | else 231 | { 232 | if (op != ':') 233 | { 234 | DPRINTF(E_LOG, L_DAAP, "Operation '\%c' not valid for string values\n", op); 235 | $valid = 0; 236 | goto STR_result_valid_0; /* ABORT */ 237 | } 238 | 239 | escaped = (pANTLR3_UINT8)db_escape_string((char *)val); 240 | if (!escaped) 241 | { 242 | DPRINTF(E_LOG, L_DAAP, "Could not escape value\n"); 243 | $valid = 0; 244 | goto STR_result_valid_0; /* ABORT */ 245 | } 246 | 247 | val = escaped; 248 | 249 | if (val[0] == '*') 250 | { 251 | op = '\%'; 252 | val[0] = '\%'; 253 | } 254 | 255 | if (val[strlen((char *)val) - 1] == '*') 256 | { 257 | op = '\%'; 258 | val[strlen((char *)val) - 1] = '\%'; 259 | } 260 | } 261 | 262 | switch(op) 263 | { 264 | case ':': 265 | if (neg_op) 266 | $result->append8($result, " <> "); 267 | else 268 | $result->append8($result, " = "); 269 | break; 270 | 271 | case '+': 272 | if (neg_op) 273 | $result->append8($result, " <= "); 274 | else 275 | $result->append8($result, " > "); 276 | break; 277 | 278 | case '-': 279 | if (neg_op) 280 | $result->append8($result, " >= "); 281 | else 282 | $result->append8($result, " < "); 283 | break; 284 | 285 | case '\%': 286 | $result->append8($result, " LIKE "); 287 | break; 288 | 289 | default: 290 | if (neg_op) 291 | DPRINTF(E_LOG, L_DAAP, "Missing or unknown operator '\%c' in clause '\%s!\%c\%s'\n", op, field, op, val); 292 | else 293 | DPRINTF(E_LOG, L_DAAP, "Unknown operator '\%c' in clause '\%s\%c\%s'\n", op, field, op, val); 294 | $valid = 0; 295 | goto STR_result_valid_0; /* ABORT */ 296 | break; 297 | } 298 | 299 | if (!dqfm->as_int) 300 | $result->append8($result, "'"); 301 | 302 | $result->append8($result, (const char *)val); 303 | 304 | if (!dqfm->as_int) 305 | $result->append8($result, "'"); 306 | 307 | /* For empty string value, we need to check against NULL too */ 308 | if ((*val == '\0') && (op == ':')) 309 | { 310 | if (neg_op) 311 | $result->append8($result, " AND "); 312 | else 313 | $result->append8($result, " OR "); 314 | 315 | $result->append8($result, dqfm->db_col); 316 | 317 | if (neg_op) 318 | $result->append8($result, " IS NOT NULL"); 319 | else 320 | $result->append8($result, " IS NULL"); 321 | 322 | $result->append8($result, ")"); 323 | } 324 | 325 | STR_result_valid_0: /* bail out label */ 326 | ; 327 | 328 | if (escaped) 329 | free(escaped); 330 | 331 | STR_out: /* get out of here */ 332 | ; 333 | } 334 | ; 335 | -------------------------------------------------------------------------------- /src/laudio_oss4.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Julien BLACHE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | #ifdef HAVE_CONFIG_H 20 | # include 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | 37 | #include "conffile.h" 38 | #include "logger.h" 39 | #include "player.h" 40 | #include "laudio.h" 41 | 42 | 43 | struct pcm_packet 44 | { 45 | uint8_t samples[STOB(AIRTUNES_V2_PACKET_SAMPLES)]; 46 | 47 | uint64_t rtptime; 48 | 49 | size_t offset; 50 | 51 | struct pcm_packet *next; 52 | }; 53 | 54 | static uint64_t pcm_pos; 55 | static uint64_t pcm_start_pos; 56 | static int pcm_buf_threshold; 57 | static int pcm_retry; 58 | 59 | static struct pcm_packet *pcm_pkt_head; 60 | static struct pcm_packet *pcm_pkt_tail; 61 | 62 | static char *card_name; 63 | static int oss_fd; 64 | 65 | static enum laudio_state pcm_status; 66 | static laudio_status_cb status_cb; 67 | 68 | 69 | static void 70 | update_status(enum laudio_state status) 71 | { 72 | pcm_status = status; 73 | status_cb(status); 74 | } 75 | 76 | void 77 | laudio_write(uint8_t *buf, uint64_t rtptime) 78 | { 79 | struct pcm_packet *pkt; 80 | int scratch; 81 | int nsamp; 82 | int ret; 83 | 84 | pkt = (struct pcm_packet *)malloc(sizeof(struct pcm_packet)); 85 | if (!pkt) 86 | { 87 | DPRINTF(E_LOG, L_LAUDIO, "Out of memory for PCM pkt\n"); 88 | 89 | update_status(LAUDIO_FAILED); 90 | return; 91 | } 92 | 93 | memcpy(pkt->samples, buf, sizeof(pkt->samples)); 94 | 95 | pkt->rtptime = rtptime; 96 | pkt->offset = 0; 97 | pkt->next = NULL; 98 | 99 | if (pcm_pkt_tail) 100 | { 101 | pcm_pkt_tail->next = pkt; 102 | pcm_pkt_tail = pkt; 103 | } 104 | else 105 | { 106 | pcm_pkt_head = pkt; 107 | pcm_pkt_tail = pkt; 108 | } 109 | 110 | if (pcm_pos < pcm_pkt_head->rtptime) 111 | { 112 | pcm_pos += AIRTUNES_V2_PACKET_SAMPLES; 113 | 114 | return; 115 | } 116 | else if ((pcm_status != LAUDIO_RUNNING) && (pcm_pos >= pcm_start_pos)) 117 | { 118 | /* Start audio output */ 119 | scratch = PCM_ENABLE_OUTPUT; 120 | ret = ioctl(oss_fd, SNDCTL_DSP_SETTRIGGER, &scratch); 121 | if (ret < 0) 122 | { 123 | DPRINTF(E_LOG, L_LAUDIO, "Could not enable output: %s\n", strerror(errno)); 124 | 125 | update_status(LAUDIO_FAILED); 126 | return; 127 | } 128 | 129 | update_status(LAUDIO_RUNNING); 130 | } 131 | 132 | pkt = pcm_pkt_head; 133 | 134 | while (pkt) 135 | { 136 | nsamp = write(oss_fd, pkt->samples + pkt->offset, sizeof(pkt->samples) - pkt->offset); 137 | if (nsamp < 0) 138 | { 139 | if (errno == EAGAIN) 140 | { 141 | pcm_retry++; 142 | 143 | if (pcm_retry < 10) 144 | return; 145 | } 146 | 147 | DPRINTF(E_LOG, L_LAUDIO, "Write error: %s\n", strerror(errno)); 148 | 149 | update_status(LAUDIO_FAILED); 150 | return; 151 | } 152 | 153 | pcm_retry = 0; 154 | 155 | pkt->offset += nsamp; 156 | 157 | nsamp = BTOS(nsamp); 158 | pcm_pos += nsamp; 159 | 160 | if (pkt->offset == sizeof(pkt->samples)) 161 | { 162 | pcm_pkt_head = pkt->next; 163 | 164 | if (pkt == pcm_pkt_tail) 165 | pcm_pkt_tail = NULL; 166 | 167 | free(pkt); 168 | 169 | pkt = pcm_pkt_head; 170 | } 171 | 172 | /* Don't let the buffer fill up too much */ 173 | if (nsamp == AIRTUNES_V2_PACKET_SAMPLES) 174 | break; 175 | } 176 | } 177 | 178 | uint64_t 179 | laudio_get_pos(void) 180 | { 181 | int delay; 182 | int ret; 183 | 184 | ret = ioctl(oss_fd, SNDCTL_DSP_GETODELAY, &delay); 185 | if (ret < 0) 186 | { 187 | DPRINTF(E_LOG, L_LAUDIO, "Could not obtain output delay: %s\n", strerror(errno)); 188 | 189 | return pcm_pos; 190 | } 191 | 192 | return pcm_pos - BTOS(delay); 193 | } 194 | 195 | void 196 | laudio_set_volume(int vol) 197 | { 198 | int oss_vol; 199 | int ret; 200 | 201 | vol = vol & 0xff; 202 | oss_vol = vol | (vol << 8); 203 | 204 | ret = ioctl(oss_fd, SNDCTL_DSP_SETPLAYVOL, &oss_vol); 205 | if (ret < 0) 206 | { 207 | DPRINTF(E_LOG, L_LAUDIO, "Could not set volume: %s\n", strerror(errno)); 208 | 209 | return; 210 | } 211 | 212 | DPRINTF(E_DBG, L_LAUDIO, "Setting PCM volume to %d (real: %d)\n", vol, (oss_vol & 0xff)); 213 | } 214 | 215 | int 216 | laudio_start(uint64_t cur_pos, uint64_t next_pkt) 217 | { 218 | int scratch; 219 | int ret; 220 | 221 | DPRINTF(E_DBG, L_LAUDIO, "PCM will start after %d samples (%d packets)\n", pcm_buf_threshold, pcm_buf_threshold / AIRTUNES_V2_PACKET_SAMPLES); 222 | 223 | /* Make pcm_pos the rtptime of the packet containing cur_pos */ 224 | pcm_pos = next_pkt; 225 | while (pcm_pos > cur_pos) 226 | pcm_pos -= AIRTUNES_V2_PACKET_SAMPLES; 227 | 228 | pcm_start_pos = next_pkt + pcm_buf_threshold; 229 | 230 | /* FIXME check for OSS - Compensate threshold, as it's taken into account by snd_pcm_delay() */ 231 | pcm_pos += pcm_buf_threshold; 232 | 233 | DPRINTF(E_DBG, L_LAUDIO, "PCM pos %" PRIu64 ", start pos %" PRIu64 "\n", pcm_pos, pcm_start_pos); 234 | 235 | pcm_pkt_head = NULL; 236 | pcm_pkt_tail = NULL; 237 | 238 | pcm_retry = 0; 239 | 240 | scratch = 0; 241 | ret = ioctl(oss_fd, SNDCTL_DSP_SETTRIGGER, &scratch); 242 | if (ret < 0) 243 | { 244 | DPRINTF(E_LOG, L_LAUDIO, "Could not set trigger: %s\n", strerror(errno)); 245 | 246 | return -1; 247 | } 248 | 249 | update_status(LAUDIO_STARTED); 250 | 251 | return 0; 252 | } 253 | 254 | void 255 | laudio_stop(void) 256 | { 257 | struct pcm_packet *pkt; 258 | int ret; 259 | 260 | update_status(LAUDIO_STOPPING); 261 | 262 | ret = ioctl(oss_fd, SNDCTL_DSP_HALT_OUTPUT, NULL); 263 | if (ret < 0) 264 | DPRINTF(E_LOG, L_LAUDIO, "Failed to halt output: %s\n", strerror(errno)); 265 | 266 | for (pkt = pcm_pkt_head; pcm_pkt_head; pkt = pcm_pkt_head) 267 | { 268 | pcm_pkt_head = pkt->next; 269 | 270 | free(pkt); 271 | } 272 | 273 | pcm_pkt_head = NULL; 274 | pcm_pkt_tail = NULL; 275 | 276 | update_status(LAUDIO_OPEN); 277 | } 278 | 279 | int 280 | laudio_open(void) 281 | { 282 | audio_buf_info bi; 283 | int scratch; 284 | int ret; 285 | 286 | oss_fd = open(card_name, O_RDWR | O_NONBLOCK); 287 | if (oss_fd < 0) 288 | { 289 | DPRINTF(E_LOG, L_LAUDIO, "Could not open sound device: %s\n", strerror(errno)); 290 | 291 | return -1; 292 | } 293 | 294 | scratch = 0; 295 | ret = ioctl(oss_fd, SNDCTL_DSP_SETTRIGGER, &scratch); 296 | if (ret < 0) 297 | { 298 | DPRINTF(E_LOG, L_LAUDIO, "Could not set trigger: %s\n", strerror(errno)); 299 | 300 | goto out_fail; 301 | } 302 | 303 | scratch = AFMT_S16_LE; 304 | errno = 0; 305 | ret = ioctl(oss_fd, SNDCTL_DSP_SETFMT, &scratch); 306 | if ((ret < 0) || (scratch != AFMT_S16_LE)) 307 | { 308 | if (errno) 309 | DPRINTF(E_LOG, L_LAUDIO, "Could not set sample format (S16 LE): %s\n", strerror(errno)); 310 | else 311 | DPRINTF(E_LOG, L_LAUDIO, "Sample format S16 LE not supported\n"); 312 | 313 | goto out_fail; 314 | } 315 | 316 | scratch = 2; 317 | errno = 0; 318 | ret = ioctl(oss_fd, SNDCTL_DSP_CHANNELS, &scratch); 319 | if ((ret < 0) || (scratch != 2)) 320 | { 321 | if (errno) 322 | DPRINTF(E_LOG, L_LAUDIO, "Could not set stereo: %s\n", strerror(errno)); 323 | else 324 | DPRINTF(E_LOG, L_LAUDIO, "Stereo not supported\n"); 325 | 326 | goto out_fail; 327 | } 328 | 329 | scratch = 44100; 330 | errno = 0; 331 | ret = ioctl(oss_fd, SNDCTL_DSP_SPEED, &scratch); 332 | if ((ret < 0) || (scratch != 44100)) 333 | { 334 | if (errno) 335 | DPRINTF(E_LOG, L_LAUDIO, "Could not set speed (44100): %s\n", strerror(errno)); 336 | else 337 | DPRINTF(E_LOG, L_LAUDIO, "Sample rate 44100 not supported\n"); 338 | 339 | goto out_fail; 340 | } 341 | 342 | ret = ioctl(oss_fd, SNDCTL_DSP_GETOSPACE, &bi); 343 | if (ret < 0) 344 | { 345 | DPRINTF(E_LOG, L_LAUDIO, "Couldn't get output buffer status: %s\n", strerror(errno)); 346 | 347 | goto out_fail; 348 | } 349 | 350 | pcm_buf_threshold = (BTOS(bi.bytes) / AIRTUNES_V2_PACKET_SAMPLES) * AIRTUNES_V2_PACKET_SAMPLES; 351 | 352 | update_status(LAUDIO_OPEN); 353 | 354 | return 0; 355 | 356 | out_fail: 357 | close(oss_fd); 358 | oss_fd = -1; 359 | 360 | return -1; 361 | } 362 | 363 | void 364 | laudio_close(void) 365 | { 366 | struct pcm_packet *pkt; 367 | int ret; 368 | 369 | ret = ioctl(oss_fd, SNDCTL_DSP_HALT_OUTPUT, NULL); 370 | if (ret < 0) 371 | DPRINTF(E_LOG, L_LAUDIO, "Failed to halt output: %s\n", strerror(errno)); 372 | 373 | close(oss_fd); 374 | oss_fd = -1; 375 | 376 | for (pkt = pcm_pkt_head; pcm_pkt_head; pkt = pcm_pkt_head) 377 | { 378 | pcm_pkt_head = pkt->next; 379 | 380 | free(pkt); 381 | } 382 | 383 | pcm_pkt_head = NULL; 384 | pcm_pkt_tail = NULL; 385 | 386 | update_status(LAUDIO_CLOSED); 387 | } 388 | 389 | 390 | int 391 | laudio_init(laudio_status_cb cb) 392 | { 393 | oss_sysinfo si; 394 | int ret; 395 | 396 | status_cb = cb; 397 | pcm_status = LAUDIO_CLOSED; 398 | 399 | card_name = cfg_getstr(cfg_getsec(cfg, "audio"), "card"); 400 | 401 | oss_fd = open(card_name, O_RDWR); 402 | if (oss_fd < 0) 403 | { 404 | DPRINTF(E_FATAL, L_LAUDIO, "Could not open sound device: %s\n", strerror(errno)); 405 | 406 | return -1; 407 | } 408 | 409 | ret = ioctl(oss_fd, SNDCTL_SYSINFO, &si); 410 | 411 | close(oss_fd); 412 | oss_fd = -1; 413 | 414 | if (ret < 0) 415 | { 416 | DPRINTF(E_FATAL, L_LAUDIO, "Could not check OSS version: %s\n", strerror(errno)); 417 | 418 | return -1; 419 | } 420 | 421 | if (si.versionnum < 0x040000) 422 | { 423 | DPRINTF(E_FATAL, L_LAUDIO, "Your OSS version (%s) is too old; version 4.0.0+ is required\n", si.version); 424 | 425 | return -1; 426 | } 427 | 428 | return 0; 429 | } 430 | 431 | void 432 | laudio_deinit(void) 433 | { 434 | /* EMPTY */ 435 | } 436 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | forked-daapd 2 | ------------ 3 | 4 | forked-daapd is a DAAP and RSP media server, with support for Linux and 5 | FreeBSD. It is a complete rewrite of mt-daapd (Firefly Media Server). 6 | 7 | DAAP stands for Digital Audio Access Protocol, and is the protocol used 8 | by iTunes and friends to share/stream media libraries over the network. 9 | 10 | RSP is Roku's own media sharing protocol. Roku are the makers of the 11 | SoundBridge devices. See . 12 | 13 | forked-daapd is a working title that will eventually change in the future. 14 | 15 | The forked-daapd git tree can be found at: 16 | 17 | Release tarballs can be downloaded at: 18 | 19 | 20 | 21 | Supported clients 22 | ----------------- 23 | 24 | forked-daapd supports iTunes clients as well as a number of devices similar 25 | to the SoundBridge. 26 | 27 | It should be able to serve your media library to any client supporting DAAP 28 | or RSP. 29 | 30 | A single forked-daapd instance can handle several clients concurrently, 31 | regardless of the protocol. 32 | 33 | 34 | Using Remote 35 | ------------ 36 | 37 | If you plan to use Remote with forked-daapd, read the following sections 38 | carefully. 39 | 40 | Pairing with Remote on iPod/iPhone 41 | ---------------------------------- 42 | 43 | forked-daapd can be paired with Apple's Remote application for iPod/iPhone; 44 | this is how the pairing process works: 45 | - start forked-daapd 46 | - start Remote, go to Choose Library, Add Library 47 | - prepare a text file with a filename ending with .remote; the filename 48 | doesn't matter, only the .remote ending does. This file must contain 49 | two lines: the first line is the name of your iPod/iPhone, the second 50 | is the 4-digit pairing code displayed by Remote. 51 | 52 | If your iPod/iPhone is named "Foobar" and Remote gives you the pairing 53 | code 5387, the file content will be: 54 | 55 | Foobar 56 | 5387 57 | 58 | - move this file somewhere in your library 59 | 60 | At this point, you should be done with the pairing process and Remote should 61 | display the name of your forked-daapd library. You can delete the .remote file 62 | once the pairing process is done. 63 | 64 | If Remote doesn't display the name of your forked-daapd library at this point, 65 | the pairing process failed. 66 | 67 | This will usually be because the .remote file did not contain the correct name 68 | or pairing code. Start over the pairing process and try again. 69 | 70 | If in doubt, enable a more verbose level of logging and check that forked-daapd 71 | receives the mDNS announcement from your iPod/iPhone when the pairing code is 72 | displayed by Remote (you can also use avahi-browse for this purpose, see below). 73 | If not, you have a network issue and mDNS doesn't work properly on your network. 74 | 75 | If you are unsure about your iPod/iPhone's name, here's how you can check for 76 | the correct value: 77 | - in a terminal, run avahi-browse -r -k _touch-remote._tcp 78 | - start Remote, goto Choose Library, Add Library 79 | - after a couple seconds at most, you should get something similar to this: 80 | 81 | + ath0 IPv4 59eff13ea2f98dbbef6c162f9df71b784a3ef9a3 _touch-remote._tcp local 82 | = ath0 IPv4 59eff13ea2f98dbbef6c162f9df71b784a3ef9a3 _touch-remote._tcp local 83 | hostname = [Foobar.local] 84 | address = [192.168.1.1] 85 | port = [49160] 86 | txt = ["DvTy=iPod touch" "RemN=Remote" "txtvers=1" "RemV=10000" "Pair=FAEA410630AEC05E" "DvNm=Foobar"] 87 | 88 | The name of your iPod/iPhone is the value of the DvNm field above. In this 89 | example, the correct value is Foobar. 90 | 91 | Watch out for fancy characters; for instance, the name of your device may 92 | include Unicode characters that aren't visually different from plain ASCII 93 | characters (like the single quote if your device name follows the default 94 | scheme of "Foo's iPhone"). If unsure, change the name of your device or 95 | capture the output in a file to extract the real, correct name. 96 | 97 | Hit Ctrl-C to terminate avahi-browse. 98 | 99 | Selecting output devices 100 | ------------------------ 101 | 102 | Remote gets a list of output devices from the server; this list includes any 103 | and all devices on the network we know of that advertise AirTunes: AirPort 104 | Express, Apple TV, ... It also includes the local audio output, that is, the 105 | sound card on the server (even if there is no soundcard). 106 | 107 | By default, if no output is selected when playback starts, the local output 108 | device will be used, following the principle of least surprise (ie not 109 | selecting an output device at random that can be anywhere in the house or 110 | elsewhere). forked-daapd will error out if the local output is not usable 111 | (no soundcard, bad permissions, ...). 112 | 113 | There are two ways to select the output devices in Remote: 114 | - in the settings panel; 115 | - in the AirPlay panel during playback. 116 | 117 | Be aware that the settings panel doesn't show any output device when there 118 | is only one; the one output device that is available is automatically used. 119 | 120 | forked-daapd remembers your selection and the individual volume for each 121 | output device; selected devices will be automatically re-selected at the next 122 | server startup, provided they appear in the 5 minutes following the startup 123 | and no playback has occured yet. Again, principle of least surprise. 124 | 125 | 126 | AirTunes devices 127 | ---------------- 128 | 129 | forked-daapd will discover the AirTunes devices available on your network. For 130 | devices that are password-protected, the device's AirTunes name and password 131 | must be given in the configuration file. See the sample configuration file 132 | for the syntax. 133 | 134 | 135 | Local audio output 136 | ------------------ 137 | 138 | The audio section of the configuration file supports 2 parameters for the local 139 | audio device: 140 | - nickname: this is the name that will be used in the speakers list in Remote 141 | - card: this is the name/device string (ALSA) or device node (OSS4) to be used 142 | as the local audio device. Defaults to "default" for ALSA and "/dev/dsp" for 143 | OSS4. 144 | 145 | 146 | Supported formats 147 | ----------------- 148 | 149 | forked-daapd should support pretty much all media formats. It relies on libav 150 | (ffmpeg) to extract metadata and decode the files on the fly when the client 151 | doesn't support the format. 152 | 153 | However, libav is not necessarily very good at extracting metadata, so some 154 | formats may cause problems. FLAC, Musepack and WMA use custom metadata 155 | extractors to work around that. 156 | 157 | Formats are attributed a code, so any new format will need to be explicitely 158 | added. Currently supported: 159 | - MPEG4: mp4a, mp4v 160 | - AAC: alac 161 | - MP3 (and friends): mpeg 162 | - FLAC: flac 163 | - OGG VORBIS: ogg 164 | - Musepack: mpc 165 | - WMA: wma (WMA Pro), wmal (WMA Lossless), wmav (WMA video) 166 | - AIFF: aif 167 | - WAV: wav 168 | 169 | 170 | Streaming MPEG4 171 | --------------- 172 | 173 | Depending on the client application, you may need to optimize your MPEG4 files 174 | for streaming. Stream-optimized MPEG4 files have their metadata at the beginning 175 | of the file, whereas non-optimized files have them at the end. 176 | 177 | Not all clients need this; if you're having trouble playing your MPEG4 files, 178 | this is the most probable cause. iTunes, in particular, doesn't handle files 179 | that aren't optimized, though FrontRow does. 180 | 181 | Files produced by iTunes are always optimized by default. Files produced by 182 | FAAC and a lot of other encoders are not, though some encoders have an option 183 | for that. 184 | 185 | The mp4creator tool from the mpeg4ip suite can be used to optimize MPEG4 files, 186 | with the -optimize option: 187 | $ mp4creator -optimize foo.m4a 188 | 189 | Don't forget to make a backup copy of your file, just in case. 190 | 191 | Note that not all tag/metadata editors know about stream optimization and will 192 | happily write the metadata back at the end of the file after you've modified 193 | them. Watch out for that. 194 | 195 | 196 | Playlists 197 | --------- 198 | 199 | forked-daapd supports M3U playlists. Just drop your playlist somewhere in 200 | your library with an .m3u extension and it will pick it up. 201 | 202 | Support for iTunes Music Library XML format is available as a compile-time 203 | option. By default, metadata from our parsers is preferred over what's in 204 | the iTunes DB; use itunes_overrides = true if you prefer iTunes' metadata. 205 | 206 | Smart playlists are not supported at the moment. 207 | 208 | 209 | Artwork 210 | ------- 211 | 212 | forked-daapd has /some/ support for artwork, with a number of limitations. 213 | 214 | Embedded artwork is not supported; libav (ffmpeg) doesn't support this yet, if 215 | and when this is added to libav, forked-daapd will support it. 216 | 217 | Your artwork must be in PNG or JPEG format, dimensions do not matter; 218 | forked-daapd scales down (never up) the artwork on-the-fly to match the 219 | constraints given by the client. Note, however, that the bigger the picture, 220 | the more time and resources it takes to perform the scaling operation. 221 | 222 | As for the naming convention, it is quite simple; consider your foo.mp3 song, 223 | residing at /bar/foo.mp3: 224 | - if /bar/foo.{png,jpg} exists, this will be used as the artwork for this file; 225 | - failing that, if /bar/{artwork,cover}.{png,jpg} exists, it will be used. 226 | 227 | For "groups" (same album name and album artist), the situation is a bit 228 | different: 229 | - if a file {artwork,cover}.{png,jpg} is found in one of the directories 230 | containing files that are part of the group, it is used as the artwork. The 231 | first file found is used, ordering is not guaranteed; 232 | - failing that, individual files are examined and the first artwork found is 233 | used. Here again, ordering is not guaranteed. 234 | 235 | You can use symlinks for the artwork files; the artwork is not scanned/indexed 236 | in any way in the database and there is no caching on forked-daapd's side. 237 | 238 | 239 | Library 240 | ------- 241 | 242 | The library is scanned in bulk mode at startup, but the server will be 243 | available even while this scan is in progress. Of course, if files have gone 244 | missing while the server was not running a request for these files will 245 | produce an error until the scan has completed and the file is no longer 246 | offered. Similarly, new files added while the server was not running won't 247 | be offered until they've been scanned. 248 | 249 | Changes to the library are reflected in real time after the initial scan. The 250 | directories are monitored for changes and rescanned on the fly. 251 | 252 | Symlinks are supported and dereferenced. This does interact in tricky ways 253 | with the above monitoring and rescanning, so you've been warned. Changes to 254 | symlinks themselves won't be taken into account, or not the way you'd expect. 255 | 256 | If you use symlinks, do not move around the target of the symlink. Avoid 257 | linking files, as files themselves aren't monitored for changes individually, 258 | so changes won't be noticed unless the file happens to be in a directory that 259 | is monitored. 260 | 261 | Bottom line: symlinks are for directories only. 262 | -------------------------------------------------------------------------------- /src/evbuffer/evbuffer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * evbuffer imported from libevent 1.4.13 (buffer.c) 3 | * Adapted for forked-daapd 4 | * 5 | * Copyright (c) 2002, 2003 Niels Provos 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * 3. The name of the author may not be used to endorse or promote products 17 | * derived from this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include "evbuffer/evbuffer.h" 44 | 45 | struct evbuffer * 46 | evbuffer_new(void) 47 | { 48 | struct evbuffer *buffer; 49 | 50 | buffer = calloc(1, sizeof(struct evbuffer)); 51 | 52 | return (buffer); 53 | } 54 | 55 | void 56 | evbuffer_free(struct evbuffer *buffer) 57 | { 58 | if (buffer->orig_buffer != NULL) 59 | free(buffer->orig_buffer); 60 | free(buffer); 61 | } 62 | 63 | /* 64 | * This is a destructive add. The data from one buffer moves into 65 | * the other buffer. 66 | */ 67 | 68 | #define SWAP(x,y) do { \ 69 | (x)->buffer = (y)->buffer; \ 70 | (x)->orig_buffer = (y)->orig_buffer; \ 71 | (x)->misalign = (y)->misalign; \ 72 | (x)->totallen = (y)->totallen; \ 73 | (x)->off = (y)->off; \ 74 | } while (0) 75 | 76 | int 77 | evbuffer_add_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf) 78 | { 79 | int res; 80 | 81 | /* Short cut for better performance */ 82 | if (outbuf->off == 0) { 83 | struct evbuffer tmp; 84 | size_t oldoff = inbuf->off; 85 | 86 | /* Swap them directly */ 87 | SWAP(&tmp, outbuf); 88 | SWAP(outbuf, inbuf); 89 | SWAP(inbuf, &tmp); 90 | 91 | /* 92 | * Optimization comes with a price; we need to notify the 93 | * buffer if necessary of the changes. oldoff is the amount 94 | * of data that we transfered from inbuf to outbuf 95 | */ 96 | if (inbuf->off != oldoff && inbuf->cb != NULL) 97 | (*inbuf->cb)(inbuf, oldoff, inbuf->off, inbuf->cbarg); 98 | if (oldoff && outbuf->cb != NULL) 99 | (*outbuf->cb)(outbuf, 0, oldoff, outbuf->cbarg); 100 | 101 | return (0); 102 | } 103 | 104 | res = evbuffer_add(outbuf, inbuf->buffer, inbuf->off); 105 | if (res == 0) { 106 | /* We drain the input buffer on success */ 107 | evbuffer_drain(inbuf, inbuf->off); 108 | } 109 | 110 | return (res); 111 | } 112 | 113 | int 114 | evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap) 115 | { 116 | char *buffer; 117 | size_t space; 118 | size_t oldoff = buf->off; 119 | int sz; 120 | va_list aq; 121 | 122 | /* make sure that at least some space is available */ 123 | evbuffer_expand(buf, 64); 124 | for (;;) { 125 | size_t used = buf->misalign + buf->off; 126 | buffer = (char *)buf->buffer + buf->off; 127 | assert(buf->totallen >= used); 128 | space = buf->totallen - used; 129 | 130 | #ifndef va_copy 131 | #define va_copy(dst, src) memcpy(&(dst), &(src), sizeof(va_list)) 132 | #endif 133 | va_copy(aq, ap); 134 | 135 | sz = vsnprintf(buffer, space, fmt, aq); 136 | buffer[space - 1] = '\0'; 137 | 138 | va_end(aq); 139 | 140 | if (sz < 0) 141 | return (-1); 142 | if ((size_t)sz < space) { 143 | buf->off += sz; 144 | if (buf->cb != NULL) 145 | (*buf->cb)(buf, oldoff, buf->off, buf->cbarg); 146 | return (sz); 147 | } 148 | if (evbuffer_expand(buf, sz + 1) == -1) 149 | return (-1); 150 | 151 | } 152 | /* NOTREACHED */ 153 | } 154 | 155 | int 156 | evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...) 157 | { 158 | int res = -1; 159 | va_list ap; 160 | 161 | va_start(ap, fmt); 162 | res = evbuffer_add_vprintf(buf, fmt, ap); 163 | va_end(ap); 164 | 165 | return (res); 166 | } 167 | 168 | /* Reads data from an event buffer and drains the bytes read */ 169 | 170 | int 171 | evbuffer_remove(struct evbuffer *buf, void *data, size_t datlen) 172 | { 173 | size_t nread = datlen; 174 | if (nread >= buf->off) 175 | nread = buf->off; 176 | 177 | memcpy(data, buf->buffer, nread); 178 | evbuffer_drain(buf, nread); 179 | 180 | return (nread); 181 | } 182 | 183 | /* 184 | * Reads a line terminated by either '\r\n', '\n\r' or '\r' or '\n'. 185 | * The returned buffer needs to be freed by the called. 186 | */ 187 | 188 | char * 189 | evbuffer_readline(struct evbuffer *buffer) 190 | { 191 | u_char *data = EVBUFFER_DATA(buffer); 192 | size_t len = EVBUFFER_LENGTH(buffer); 193 | char *line; 194 | unsigned int i; 195 | 196 | for (i = 0; i < len; i++) { 197 | if (data[i] == '\r' || data[i] == '\n') 198 | break; 199 | } 200 | 201 | if (i == len) 202 | return (NULL); 203 | 204 | if ((line = malloc(i + 1)) == NULL) { 205 | fprintf(stderr, "%s: out of memory\n", __func__); 206 | return (NULL); 207 | } 208 | 209 | memcpy(line, data, i); 210 | line[i] = '\0'; 211 | 212 | /* 213 | * Some protocols terminate a line with '\r\n', so check for 214 | * that, too. 215 | */ 216 | if ( i < len - 1 ) { 217 | char fch = data[i], sch = data[i+1]; 218 | 219 | /* Drain one more character if needed */ 220 | if ( (sch == '\r' || sch == '\n') && sch != fch ) 221 | i += 1; 222 | } 223 | 224 | evbuffer_drain(buffer, i + 1); 225 | 226 | return (line); 227 | } 228 | 229 | /* Adds data to an event buffer */ 230 | 231 | static void 232 | evbuffer_align(struct evbuffer *buf) 233 | { 234 | memmove(buf->orig_buffer, buf->buffer, buf->off); 235 | buf->buffer = buf->orig_buffer; 236 | buf->misalign = 0; 237 | } 238 | 239 | /* Expands the available space in the event buffer to at least datlen */ 240 | 241 | int 242 | evbuffer_expand(struct evbuffer *buf, size_t datlen) 243 | { 244 | size_t need = buf->misalign + buf->off + datlen; 245 | 246 | /* If we can fit all the data, then we don't have to do anything */ 247 | if (buf->totallen >= need) 248 | return (0); 249 | 250 | /* 251 | * If the misalignment fulfills our data needs, we just force an 252 | * alignment to happen. Afterwards, we have enough space. 253 | */ 254 | if (buf->misalign >= datlen) { 255 | evbuffer_align(buf); 256 | } else { 257 | void *newbuf; 258 | size_t length = buf->totallen; 259 | 260 | if (length < 256) 261 | length = 256; 262 | while (length < need) 263 | length <<= 1; 264 | 265 | if (buf->orig_buffer != buf->buffer) 266 | evbuffer_align(buf); 267 | if ((newbuf = realloc(buf->buffer, length)) == NULL) 268 | return (-1); 269 | 270 | buf->orig_buffer = buf->buffer = newbuf; 271 | buf->totallen = length; 272 | } 273 | 274 | return (0); 275 | } 276 | 277 | int 278 | evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen) 279 | { 280 | size_t need = buf->misalign + buf->off + datlen; 281 | size_t oldoff = buf->off; 282 | 283 | if (buf->totallen < need) { 284 | if (evbuffer_expand(buf, datlen) == -1) 285 | return (-1); 286 | } 287 | 288 | memcpy(buf->buffer + buf->off, data, datlen); 289 | buf->off += datlen; 290 | 291 | if (datlen && buf->cb != NULL) 292 | (*buf->cb)(buf, oldoff, buf->off, buf->cbarg); 293 | 294 | return (0); 295 | } 296 | 297 | void 298 | evbuffer_drain(struct evbuffer *buf, size_t len) 299 | { 300 | size_t oldoff = buf->off; 301 | 302 | if (len >= buf->off) { 303 | buf->off = 0; 304 | buf->buffer = buf->orig_buffer; 305 | buf->misalign = 0; 306 | goto done; 307 | } 308 | 309 | buf->buffer += len; 310 | buf->misalign += len; 311 | 312 | buf->off -= len; 313 | 314 | done: 315 | /* Tell someone about changes in this buffer */ 316 | if (buf->off != oldoff && buf->cb != NULL) 317 | (*buf->cb)(buf, oldoff, buf->off, buf->cbarg); 318 | 319 | } 320 | 321 | /* 322 | * Reads data from a file descriptor into a buffer. 323 | */ 324 | 325 | #define EVBUFFER_MAX_READ 4096 326 | 327 | int 328 | evbuffer_read(struct evbuffer *buf, int fd, int howmuch) 329 | { 330 | u_char *p; 331 | size_t oldoff = buf->off; 332 | int n = EVBUFFER_MAX_READ; 333 | 334 | #if defined(FIONREAD) 335 | #ifdef WIN32 336 | long lng = n; 337 | if (ioctlsocket(fd, FIONREAD, &lng) == -1 || (n=lng) <= 0) { 338 | #else 339 | if (ioctl(fd, FIONREAD, &n) == -1 || n <= 0) { 340 | #endif 341 | n = EVBUFFER_MAX_READ; 342 | } else if (n > EVBUFFER_MAX_READ && n > howmuch) { 343 | /* 344 | * It's possible that a lot of data is available for 345 | * reading. We do not want to exhaust resources 346 | * before the reader has a chance to do something 347 | * about it. If the reader does not tell us how much 348 | * data we should read, we artifically limit it. 349 | */ 350 | if ((size_t)n > buf->totallen << 2) 351 | n = buf->totallen << 2; 352 | if (n < EVBUFFER_MAX_READ) 353 | n = EVBUFFER_MAX_READ; 354 | } 355 | #endif 356 | if (howmuch < 0 || howmuch > n) 357 | howmuch = n; 358 | 359 | /* If we don't have FIONREAD, we might waste some space here */ 360 | if (evbuffer_expand(buf, howmuch) == -1) 361 | return (-1); 362 | 363 | /* We can append new data at this point */ 364 | p = buf->buffer + buf->off; 365 | 366 | #ifndef WIN32 367 | n = read(fd, p, howmuch); 368 | #else 369 | n = recv(fd, p, howmuch, 0); 370 | #endif 371 | if (n == -1) 372 | return (-1); 373 | if (n == 0) 374 | return (0); 375 | 376 | buf->off += n; 377 | 378 | /* Tell someone about changes in this buffer */ 379 | if (buf->off != oldoff && buf->cb != NULL) 380 | (*buf->cb)(buf, oldoff, buf->off, buf->cbarg); 381 | 382 | return (n); 383 | } 384 | 385 | int 386 | evbuffer_write(struct evbuffer *buffer, int fd) 387 | { 388 | int n; 389 | 390 | #ifndef WIN32 391 | n = write(fd, buffer->buffer, buffer->off); 392 | #else 393 | n = send(fd, buffer->buffer, buffer->off, 0); 394 | #endif 395 | if (n == -1) 396 | return (-1); 397 | if (n == 0) 398 | return (0); 399 | evbuffer_drain(buffer, n); 400 | 401 | return (n); 402 | } 403 | 404 | u_char * 405 | evbuffer_find(struct evbuffer *buffer, const u_char *what, size_t len) 406 | { 407 | u_char *search = buffer->buffer, *end = search + buffer->off; 408 | u_char *p; 409 | 410 | while (search < end && 411 | (p = memchr(search, *what, end - search)) != NULL) { 412 | if (p + len > end) 413 | break; 414 | if (memcmp(p, what, len) == 0) 415 | return (p); 416 | search = p + 1; 417 | } 418 | 419 | return (NULL); 420 | } 421 | 422 | void evbuffer_setcb(struct evbuffer *buffer, 423 | void (*cb)(struct evbuffer *, size_t, size_t, void *), 424 | void *cbarg) 425 | { 426 | buffer->cb = cb; 427 | buffer->cbarg = cbarg; 428 | } 429 | -------------------------------------------------------------------------------- /src/db.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __DB_H__ 3 | #define __DB_H__ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | 12 | enum index_type { 13 | I_NONE, 14 | I_FIRST, 15 | I_LAST, 16 | I_SUB 17 | }; 18 | 19 | enum sort_type { 20 | S_NONE = 0, 21 | S_NAME, 22 | S_ALBUM, 23 | S_ARTIST, 24 | }; 25 | 26 | #define Q_F_BROWSE (1 << 15) 27 | 28 | enum query_type { 29 | Q_ITEMS = (1 << 0), 30 | Q_PL = (1 << 1), 31 | Q_PLITEMS = (1 << 2), 32 | Q_BROWSE_ARTISTS = Q_F_BROWSE | (1 << 3), 33 | Q_BROWSE_ALBUMS = Q_F_BROWSE | (1 << 4), 34 | Q_BROWSE_GENRES = Q_F_BROWSE | (1 << 5), 35 | Q_BROWSE_COMPOSERS = Q_F_BROWSE | (1 << 6), 36 | Q_GROUPS = (1 << 7), 37 | Q_GROUPITEMS = (1 << 8), 38 | Q_GROUP_DIRS = Q_F_BROWSE | (1 << 9), 39 | }; 40 | 41 | struct query_params { 42 | /* Query parameters, filled in by caller */ 43 | enum query_type type; 44 | enum index_type idx_type; 45 | enum sort_type sort; 46 | int id; 47 | int offset; 48 | int limit; 49 | 50 | char *filter; 51 | 52 | /* Query results, filled in by query_start */ 53 | int results; 54 | 55 | /* Private query context, keep out */ 56 | sqlite3_stmt *stmt; 57 | char buf[32]; 58 | }; 59 | 60 | struct pairing_info { 61 | char *remote_id; 62 | char *name; 63 | char *guid; 64 | }; 65 | 66 | struct media_file_info { 67 | char *path; 68 | uint32_t index; 69 | char *fname; 70 | char *title; 71 | char *artist; 72 | char *album; 73 | char *genre; 74 | char *comment; 75 | char *type; /* daap.songformat */ 76 | char *composer; 77 | char *orchestra; 78 | char *conductor; 79 | char *grouping; 80 | char *url; /* daap.songdataurl (asul) */ 81 | 82 | uint32_t bitrate; 83 | uint32_t samplerate; 84 | uint32_t song_length; 85 | int64_t file_size; 86 | uint32_t year; /* TDRC */ 87 | 88 | uint32_t track; /* TRCK */ 89 | uint32_t total_tracks; 90 | 91 | uint32_t disc; /* TPOS */ 92 | uint32_t total_discs; 93 | 94 | uint32_t time_added; /* FIXME: time_t */ 95 | uint32_t time_modified; 96 | uint32_t time_played; 97 | 98 | uint32_t play_count; 99 | uint32_t rating; 100 | uint32_t db_timestamp; 101 | 102 | uint32_t disabled; 103 | uint32_t bpm; /* TBPM */ 104 | 105 | uint32_t id; 106 | 107 | char *description; /* daap.songdescription */ 108 | char *codectype; /* song.codectype, 4 chars max (32 bits) */ 109 | 110 | uint32_t item_kind; /* song or movie */ 111 | uint32_t data_kind; /* dmap.datakind (asdk) */ 112 | uint64_t sample_count; 113 | char compilation; 114 | 115 | /* iTunes 5+ */ 116 | uint32_t contentrating; 117 | 118 | /* iTunes 6.0.2 */ 119 | uint32_t has_video; 120 | uint32_t bits_per_sample; 121 | 122 | uint32_t media_kind; 123 | uint32_t tv_episode_sort; 124 | uint32_t tv_season_num; 125 | char *tv_series_name; 126 | char *tv_episode_num_str; /* com.apple.itunes.episode-num-str, used as a unique episode identifier */ 127 | char *tv_network_name; 128 | 129 | char *album_artist; 130 | 131 | int64_t songalbumid; 132 | 133 | char *title_sort; 134 | char *artist_sort; 135 | char *album_sort; 136 | char *composer_sort; 137 | char *album_artist_sort; 138 | }; 139 | 140 | #define mfi_offsetof(field) offsetof(struct media_file_info, field) 141 | 142 | enum pl_type { 143 | PL_PLAIN, 144 | PL_SMART, 145 | PL_MAX 146 | }; 147 | 148 | struct playlist_info { 149 | uint32_t id; /* integer id (miid) */ 150 | char *title; /* playlist name as displayed in iTunes (minm) */ 151 | enum pl_type type; /* see PL_ types */ 152 | uint32_t items; /* number of items (mimc) */ 153 | char *query; /* where clause if type 1 (MSPS) */ 154 | uint32_t db_timestamp; /* time last updated */ 155 | uint32_t disabled; 156 | char *path; /* path of underlying playlist */ 157 | uint32_t index; /* index of playlist for paths with multiple playlists */ 158 | uint32_t special_id; /* iTunes identifies certain 'special' playlists with special meaning */ 159 | }; 160 | 161 | #define pli_offsetof(field) offsetof(struct playlist_info, field) 162 | 163 | struct db_playlist_info { 164 | char *id; 165 | char *title; 166 | char *type; 167 | char *items; 168 | char *query; 169 | char *db_timestamp; 170 | char *disabled; 171 | char *path; 172 | char *index; 173 | char *special_id; 174 | }; 175 | 176 | #define dbpli_offsetof(field) offsetof(struct db_playlist_info, field) 177 | 178 | struct group_info { 179 | uint32_t id; /* integer id (miid) */ 180 | uint64_t persistentid; /* ulonglong id (mper) */ 181 | char *itemname; /* playlist name as displayed in iTunes (minm) */ 182 | uint32_t itemcount; /* number of items (mimc) */ 183 | char *songalbumartist; /* song album artist (asaa) */ 184 | }; 185 | 186 | #define gri_offsetof(field) offsetof(struct group_info, field) 187 | 188 | struct db_group_info { 189 | char *id; 190 | char *persistentid; 191 | char *itemname; 192 | char *itemcount; 193 | char *songalbumartist; 194 | }; 195 | 196 | #define dbgri_offsetof(field) offsetof(struct db_group_info, field) 197 | 198 | struct db_media_file_info { 199 | char *id; 200 | char *path; 201 | char *fname; 202 | char *title; 203 | char *artist; 204 | char *album; 205 | char *genre; 206 | char *comment; 207 | char *type; 208 | char *composer; 209 | char *orchestra; 210 | char *conductor; 211 | char *grouping; 212 | char *url; 213 | char *bitrate; 214 | char *samplerate; 215 | char *song_length; 216 | char *file_size; 217 | char *year; 218 | char *track; 219 | char *total_tracks; 220 | char *disc; 221 | char *total_discs; 222 | char *bpm; 223 | char *compilation; 224 | char *rating; 225 | char *play_count; 226 | char *data_kind; 227 | char *item_kind; 228 | char *description; 229 | char *time_added; 230 | char *time_modified; 231 | char *time_played; 232 | char *db_timestamp; 233 | char *disabled; 234 | char *sample_count; 235 | char *codectype; 236 | char *idx; 237 | char *has_video; 238 | char *contentrating; 239 | char *bits_per_sample; 240 | char *album_artist; 241 | char *media_kind; 242 | char *tv_episode_sort; 243 | char *tv_season_num; 244 | char *tv_series_name; 245 | char *tv_episode_num_str; 246 | char *tv_network_name; 247 | char *songalbumid; 248 | char *title_sort; 249 | char *artist_sort; 250 | char *album_sort; 251 | char *composer_sort; 252 | char *album_artist_sort; 253 | }; 254 | 255 | #define dbmfi_offsetof(field) offsetof(struct db_media_file_info, field) 256 | 257 | struct watch_info { 258 | int wd; 259 | char *path; 260 | uint32_t cookie; 261 | }; 262 | 263 | #define wi_offsetof(field) offsetof(struct watch_info, field) 264 | 265 | struct watch_enum { 266 | uint32_t cookie; 267 | char *match; 268 | 269 | /* Private enum context, keep out */ 270 | sqlite3_stmt *stmt; 271 | }; 272 | 273 | 274 | char * 275 | db_escape_string(const char *str); 276 | 277 | void 278 | free_pi(struct pairing_info *pi, int content_only); 279 | 280 | void 281 | free_mfi(struct media_file_info *mfi, int content_only); 282 | 283 | void 284 | unicode_fixup_mfi(struct media_file_info *mfi); 285 | 286 | void 287 | free_pli(struct playlist_info *pli, int content_only); 288 | 289 | /* Maintenance and DB hygiene */ 290 | void 291 | db_hook_post_scan(void); 292 | 293 | void 294 | db_purge_cruft(time_t ref); 295 | 296 | /* Queries */ 297 | int 298 | db_query_start(struct query_params *qp); 299 | 300 | void 301 | db_query_end(struct query_params *qp); 302 | 303 | int 304 | db_query_fetch_file(struct query_params *qp, struct db_media_file_info *dbmfi); 305 | 306 | int 307 | db_query_fetch_pl(struct query_params *qp, struct db_playlist_info *dbpli); 308 | 309 | int 310 | db_query_fetch_group(struct query_params *qp, struct db_group_info *dbgri); 311 | 312 | int 313 | db_query_fetch_string(struct query_params *qp, char **string); 314 | 315 | int 316 | db_query_fetch_string_sort(struct query_params *qp, char **string, char **sortstring); 317 | 318 | /* Files */ 319 | int 320 | db_files_get_count(void); 321 | 322 | void 323 | db_files_update_songalbumid(void); 324 | 325 | void 326 | db_file_inc_playcount(int id); 327 | 328 | void 329 | db_file_ping(int id); 330 | 331 | char * 332 | db_file_path_byid(int id); 333 | 334 | int 335 | db_file_id_bypath(char *path); 336 | 337 | int 338 | db_file_id_byfilebase(char *filename, char *base); 339 | 340 | int 341 | db_file_id_byfile(char *filename); 342 | 343 | int 344 | db_file_id_byurl(char *url); 345 | 346 | void 347 | db_file_stamp_bypath(char *path, time_t *stamp, int *id); 348 | 349 | struct media_file_info * 350 | db_file_fetch_byid(int id); 351 | 352 | int 353 | db_file_add(struct media_file_info *mfi); 354 | 355 | int 356 | db_file_update(struct media_file_info *mfi); 357 | 358 | void 359 | db_file_delete_bypath(char *path); 360 | 361 | void 362 | db_file_disable_bypath(char *path, char *strip, uint32_t cookie); 363 | 364 | void 365 | db_file_disable_bymatch(char *path, char *strip, uint32_t cookie); 366 | 367 | int 368 | db_file_enable_bycookie(uint32_t cookie, char *path); 369 | 370 | /* Playlists */ 371 | int 372 | db_pl_get_count(void); 373 | 374 | void 375 | db_pl_ping(int id); 376 | 377 | struct playlist_info * 378 | db_pl_fetch_bypath(char *path); 379 | 380 | struct playlist_info * 381 | db_pl_fetch_bytitlepath(char *title, char *path); 382 | 383 | int 384 | db_pl_add(char *title, char *path, int *id); 385 | 386 | int 387 | db_pl_add_item_bypath(int plid, char *path); 388 | 389 | int 390 | db_pl_add_item_byid(int plid, int fileid); 391 | 392 | void 393 | db_pl_clear_items(int id); 394 | 395 | void 396 | db_pl_delete(int id); 397 | 398 | void 399 | db_pl_delete_bypath(char *path); 400 | 401 | void 402 | db_pl_disable_bypath(char *path, char *strip, uint32_t cookie); 403 | 404 | void 405 | db_pl_disable_bymatch(char *path, char *strip, uint32_t cookie); 406 | 407 | int 408 | db_pl_enable_bycookie(uint32_t cookie, char *path); 409 | 410 | /* Groups */ 411 | int 412 | db_groups_clear(void); 413 | 414 | enum group_type 415 | db_group_type_byid(int id); 416 | 417 | /* Remotes */ 418 | int 419 | db_pairing_add(struct pairing_info *pi); 420 | 421 | int 422 | db_pairing_fetch_byguid(struct pairing_info *pi); 423 | 424 | /* Speakers */ 425 | int 426 | db_speaker_save(uint64_t id, int selected, int volume); 427 | 428 | int 429 | db_speaker_get(uint64_t id, int *selected, int *volume); 430 | 431 | void 432 | db_speaker_clear_all(void); 433 | 434 | /* Inotify */ 435 | int 436 | db_watch_clear(void); 437 | 438 | int 439 | db_watch_add(struct watch_info *wi); 440 | 441 | int 442 | db_watch_delete_bywd(uint32_t wd); 443 | 444 | int 445 | db_watch_delete_bypath(char *path); 446 | 447 | int 448 | db_watch_delete_bymatch(char *path); 449 | 450 | int 451 | db_watch_delete_bycookie(uint32_t cookie); 452 | 453 | int 454 | db_watch_get_bywd(struct watch_info *wi); 455 | 456 | void 457 | db_watch_mark_bypath(char *path, char *strip, uint32_t cookie); 458 | 459 | void 460 | db_watch_mark_bymatch(char *path, char *strip, uint32_t cookie); 461 | 462 | void 463 | db_watch_move_bycookie(uint32_t cookie, char *path); 464 | 465 | int 466 | db_watch_cookie_known(uint32_t cookie); 467 | 468 | int 469 | db_watch_enum_start(struct watch_enum *we); 470 | 471 | void 472 | db_watch_enum_end(struct watch_enum *we); 473 | 474 | int 475 | db_watch_enum_fetchwd(struct watch_enum *we, uint32_t *wd); 476 | 477 | 478 | int 479 | db_pool_get(void); 480 | 481 | void 482 | db_pool_release(void); 483 | 484 | 485 | int 486 | db_init(void); 487 | 488 | void 489 | db_deinit(void); 490 | 491 | #endif /* !__DB_H__ */ 492 | -------------------------------------------------------------------------------- /src/RSP2SQL.g: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2011 Julien BLACHE 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | tree grammar RSP2SQL; 20 | 21 | options { 22 | tokenVocab = RSP; 23 | ASTLabelType = pANTLR3_BASE_TREE; 24 | language = C; 25 | } 26 | 27 | @header { 28 | /* Needs #define _GNU_SOURCE for strptime() */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "logger.h" 36 | #include "db.h" 37 | #include "misc.h" 38 | #include "rsp_query.h" 39 | } 40 | 41 | @members { 42 | #define RSP_TYPE_STRING 0 43 | #define RSP_TYPE_INT 1 44 | #define RSP_TYPE_DATE 2 45 | 46 | struct rsp_query_field_map { 47 | char *rsp_field; 48 | int field_type; 49 | /* RSP fields are named after the DB columns - or vice versa */ 50 | }; 51 | 52 | /* gperf static hash, rsp_query.gperf */ 53 | #include "rsp_query_hash.c" 54 | } 55 | 56 | query returns [ pANTLR3_STRING result ] 57 | @init { $result = NULL; } 58 | : e = expr 59 | { 60 | if (!$e.valid) 61 | { 62 | $result = NULL; 63 | } 64 | else 65 | { 66 | $result = $e.result->factory->newRaw($e.result->factory); 67 | $result->append8($result, "("); 68 | $result->appendS($result, $e.result); 69 | $result->append8($result, ")"); 70 | } 71 | } 72 | ; 73 | 74 | expr returns [ pANTLR3_STRING result, int valid ] 75 | @init { $result = NULL; $valid = 1; } 76 | : ^(AND a = expr b = expr) 77 | { 78 | if (!$a.valid || !$b.valid) 79 | { 80 | $valid = 0; 81 | } 82 | else 83 | { 84 | $result = $a.result->factory->newRaw($a.result->factory); 85 | $result->append8($result, "("); 86 | $result->appendS($result, $a.result); 87 | $result->append8($result, " AND "); 88 | $result->appendS($result, $b.result); 89 | $result->append8($result, ")"); 90 | } 91 | } 92 | | ^(OR a = expr b = expr) 93 | { 94 | if (!$a.valid || !$b.valid) 95 | { 96 | $valid = 0; 97 | } 98 | else 99 | { 100 | $result = $a.result->factory->newRaw($a.result->factory); 101 | $result->append8($result, "("); 102 | $result->appendS($result, $a.result); 103 | $result->append8($result, " OR "); 104 | $result->appendS($result, $b.result); 105 | $result->append8($result, ")"); 106 | } 107 | } 108 | | c = strcrit 109 | { 110 | $valid = $c.valid; 111 | $result = $c.result; 112 | } 113 | | ^(NOT c = strcrit) 114 | { 115 | if (!$c.valid) 116 | { 117 | $valid = 0; 118 | } 119 | else 120 | { 121 | $result = $c.result->factory->newRaw($c.result->factory); 122 | $result->append8($result, "(NOT "); 123 | $result->appendS($result, $c.result); 124 | $result->append8($result, ")"); 125 | } 126 | } 127 | | i = intcrit 128 | { 129 | $valid = $i.valid; 130 | $result = $i.result; 131 | } 132 | | ^(NOT i = intcrit) 133 | { 134 | if (!$i.valid) 135 | { 136 | $valid = 0; 137 | } 138 | else 139 | { 140 | $result = $i.result->factory->newRaw($i.result->factory); 141 | $result->append8($result, "(NOT "); 142 | $result->appendS($result, $i.result); 143 | $result->append8($result, ")"); 144 | } 145 | } 146 | | d = datecrit 147 | { 148 | $valid = $d.valid; 149 | $result = $d.result; 150 | } 151 | ; 152 | 153 | strcrit returns [ pANTLR3_STRING result, int valid ] 154 | @init { $result = NULL; $valid = 1; } 155 | : ^(o = strop f = FIELD s = STR) 156 | { 157 | char *op; 158 | const struct rsp_query_field_map *rqfp; 159 | pANTLR3_STRING field; 160 | char *escaped; 161 | ANTLR3_UINT32 optok; 162 | 163 | escaped = NULL; 164 | 165 | op = NULL; 166 | optok = $o.op->getType($o.op); 167 | switch (optok) 168 | { 169 | case EQUAL: 170 | op = " = "; 171 | break; 172 | 173 | case INCLUDES: 174 | case STARTSW: 175 | case ENDSW: 176 | op = " LIKE "; 177 | break; 178 | } 179 | 180 | field = $f->getText($f); 181 | 182 | /* Field lookup */ 183 | rqfp = rsp_query_field_lookup((char *)field->chars, strlen((char *)field->chars)); 184 | if (!rqfp) 185 | { 186 | DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a valid field in queries\n", field->chars); 187 | $valid = 0; 188 | goto strcrit_valid_0; /* ABORT */ 189 | } 190 | 191 | /* Check field type */ 192 | if (rqfp->field_type != RSP_TYPE_STRING) 193 | { 194 | DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a string field\n", field->chars); 195 | $valid = 0; 196 | goto strcrit_valid_0; /* ABORT */ 197 | } 198 | 199 | escaped = db_escape_string((char *)$s->getText($s)->chars); 200 | if (!escaped) 201 | { 202 | DPRINTF(E_LOG, L_RSP, "Could not escape value\n"); 203 | $valid = 0; 204 | goto strcrit_valid_0; /* ABORT */ 205 | } 206 | 207 | $result = field->factory->newRaw(field->factory); 208 | $result->append8($result, "f."); 209 | $result->appendS($result, field); 210 | $result->append8($result, op); 211 | $result->append8($result, "'"); 212 | if ((optok == INCLUDES) || (optok == STARTSW)) 213 | $result->append8($result, "\%"); 214 | 215 | $result->append8($result, escaped); 216 | 217 | if ((optok == INCLUDES) || (optok == ENDSW)) 218 | $result->append8($result, "\%"); 219 | $result->append8($result, "'"); 220 | 221 | strcrit_valid_0: 222 | ; 223 | 224 | if (escaped) 225 | free(escaped); 226 | } 227 | ; 228 | 229 | strop returns [ pANTLR3_COMMON_TOKEN op ] 230 | @init { $op = NULL; } 231 | : n = EQUAL 232 | { $op = $n->getToken($n); } 233 | | n = INCLUDES 234 | { $op = $n->getToken($n); } 235 | | n = STARTSW 236 | { $op = $n->getToken($n); } 237 | | n = ENDSW 238 | { $op = $n->getToken($n); } 239 | ; 240 | 241 | intcrit returns [ pANTLR3_STRING result, int valid ] 242 | @init { $result = NULL; $valid = 1; } 243 | : ^(o = intop f = FIELD i = INT) 244 | { 245 | char *op; 246 | const struct rsp_query_field_map *rqfp; 247 | pANTLR3_STRING field; 248 | 249 | op = NULL; 250 | switch ($o.op->getType($o.op)) 251 | { 252 | case EQUAL: 253 | op = " = "; 254 | break; 255 | 256 | case LESS: 257 | op = " < "; 258 | break; 259 | 260 | case GREATER: 261 | op = " > "; 262 | break; 263 | 264 | case LTE: 265 | op = " <= "; 266 | break; 267 | 268 | case GTE: 269 | op = " >= "; 270 | break; 271 | } 272 | 273 | field = $f->getText($f); 274 | 275 | /* Field lookup */ 276 | rqfp = rsp_query_field_lookup((char *)field->chars, strlen((char *)field->chars)); 277 | if (!rqfp) 278 | { 279 | DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a valid field in queries\n", field->chars); 280 | $valid = 0; 281 | goto intcrit_valid_0; /* ABORT */ 282 | } 283 | 284 | /* Check field type */ 285 | if (rqfp->field_type != RSP_TYPE_INT) 286 | { 287 | DPRINTF(E_LOG, L_RSP, "Field '\%s' is not an integer field\n", field->chars); 288 | $valid = 0; 289 | goto intcrit_valid_0; /* ABORT */ 290 | } 291 | 292 | $result = field->factory->newRaw(field->factory); 293 | $result->append8($result, "f."); 294 | $result->appendS($result, field); 295 | $result->append8($result, op); 296 | $result->appendS($result, $i->getText($i)); 297 | 298 | intcrit_valid_0: 299 | ; 300 | } 301 | ; 302 | 303 | intop returns [ pANTLR3_COMMON_TOKEN op ] 304 | @init { $op = NULL; } 305 | : n = EQUAL 306 | { $op = $n->getToken($n); } 307 | | n = LESS 308 | { $op = $n->getToken($n); } 309 | | n = GREATER 310 | { $op = $n->getToken($n); } 311 | | n = LTE 312 | { $op = $n->getToken($n); } 313 | | n = GTE 314 | { $op = $n->getToken($n); } 315 | ; 316 | 317 | datecrit returns [ pANTLR3_STRING result, int valid ] 318 | @init { $result = NULL; $valid = 1; } 319 | : ^(o = dateop f = FIELD d = datespec) 320 | { 321 | char *op; 322 | const struct rsp_query_field_map *rqfp; 323 | pANTLR3_STRING field; 324 | char buf[32]; 325 | int ret; 326 | 327 | op = NULL; 328 | switch ($o.op->getType($o.op)) 329 | { 330 | case BEFORE: 331 | op = " < "; 332 | break; 333 | 334 | case AFTER: 335 | op = " > "; 336 | break; 337 | } 338 | 339 | field = $f->getText($f); 340 | 341 | /* Field lookup */ 342 | rqfp = rsp_query_field_lookup((char *)field->chars, strlen((char *)field->chars)); 343 | if (!rqfp) 344 | { 345 | DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a valid field in queries\n", field->chars); 346 | $valid = 0; 347 | goto datecrit_valid_0; /* ABORT */ 348 | } 349 | 350 | /* Check field type */ 351 | if (rqfp->field_type != RSP_TYPE_DATE) 352 | { 353 | DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a date field\n", field->chars); 354 | $valid = 0; 355 | goto datecrit_valid_0; /* ABORT */ 356 | } 357 | 358 | ret = snprintf(buf, sizeof(buf), "\%ld", $d.date); 359 | if ((ret < 0) || (ret >= sizeof(buf))) 360 | { 361 | DPRINTF(E_LOG, L_RSP, "Date \%ld too large for buffer, oops!\n", $d.date); 362 | $valid = 0; 363 | goto datecrit_valid_0; /* ABORT */ 364 | } 365 | 366 | $result = field->factory->newRaw(field->factory); 367 | $result->append8($result, "f."); 368 | $result->appendS($result, field); 369 | $result->append8($result, op); 370 | $result->append8($result, buf); 371 | 372 | datecrit_valid_0: 373 | ; 374 | } 375 | ; 376 | 377 | dateop returns [ pANTLR3_COMMON_TOKEN op ] 378 | @init { $op = NULL; } 379 | : n = BEFORE 380 | { $op = $n->getToken($n); } 381 | | n = AFTER 382 | { $op = $n->getToken($n); } 383 | ; 384 | 385 | datespec returns [ time_t date, int valid ] 386 | @init { $date = 0; $valid = 1; } 387 | : r = dateref 388 | { 389 | if (!$r.valid) 390 | $valid = 0; 391 | else 392 | $date = $r.date; 393 | } 394 | | ^(o = dateop r = dateref m = INT i = dateintval) 395 | { 396 | int32_t val; 397 | int ret; 398 | 399 | if (!$r.valid || !$i.valid) 400 | { 401 | $valid = 0; 402 | goto datespec_valid_0; /* ABORT */ 403 | } 404 | 405 | ret = safe_atoi32((char *)$m->getText($m)->chars, &val); 406 | if (ret < 0) 407 | { 408 | DPRINTF(E_LOG, L_RSP, "Could not convert '\%s' to integer\n", (char *)$m->getText($m)); 409 | $valid = 0; 410 | goto datespec_valid_0; /* ABORT */ 411 | } 412 | 413 | switch ($o.op->getType($o.op)) 414 | { 415 | case BEFORE: 416 | $date = $r.date - (val * $i.period); 417 | break; 418 | 419 | case AFTER: 420 | $date = $r.date + (val * $i.period); 421 | break; 422 | } 423 | 424 | datespec_valid_0: 425 | ; 426 | } 427 | ; 428 | 429 | dateref returns [ time_t date, int valid ] 430 | @init { $date = 0; $valid = 1; } 431 | : n = DATE 432 | { 433 | struct tm tm; 434 | char *ret; 435 | 436 | ret = strptime((char *)$n->getText($n), "\%Y-\%m-\%d", &tm); 437 | if (!ret) 438 | { 439 | DPRINTF(E_LOG, L_RSP, "Date '\%s' could not be interpreted\n", (char *)$n->getText($n)); 440 | $valid = 0; 441 | goto dateref_valid_0; /* ABORT */ 442 | } 443 | else 444 | { 445 | if (*ret != '\0') 446 | DPRINTF(E_LOG, L_RSP, "Garbage at end of date '\%s' ?!\n", (char *)$n->getText($n)); 447 | 448 | $date = mktime(&tm); 449 | if ($date == (time_t) -1) 450 | { 451 | DPRINTF(E_LOG, L_RSP, "Date '\%s' could not be converted to an epoch\n", (char *)$n->getText($n)); 452 | $valid = 0; 453 | goto dateref_valid_0; /* ABORT */ 454 | } 455 | } 456 | 457 | dateref_valid_0: 458 | ; 459 | } 460 | | TODAY 461 | { $date = time(NULL); } 462 | ; 463 | 464 | dateintval returns [ time_t period, int valid ] 465 | @init { $period = 0; $valid = 1; } 466 | : DAY 467 | { $period = 24 * 60 * 60; } 468 | | WEEK 469 | { $period = 7 * 24 * 60 * 60; } 470 | | MONTH 471 | { $period = 30 * 24 * 60 * 60; } 472 | | YEAR 473 | { $period = 365 * 24 * 60 * 60; } 474 | ; 475 | --------------------------------------------------------------------------------