#include "music.h" #include "decoder.h" #include "encoder.h" #include "tag.h" #include "config.h" #include #include #include #include #include #include #include #include #include fs::path music::root_directory; void music::init(std::string root) { // remove trailing slash if(boost::algorithm::ends_with(root, "/")) root = root.substr(0, root.size()-1); root_directory = root; } /** Fetches a MusicListing object from the given \p path. * Prefixes the given path with the music root directory. * This can be either a track (file) or a directory. */ MusicListing::p music::get(const HTTP::Connection::PathList& path) { // prefix path with our root_directory fs::path p = root_directory; for(HTTP::Connection::PathList::const_iterator it = path.begin(); it != path.end(); it++) { // don't allow requests with /../ in the path if(*it == "..") { return MusicListing::p(); } p /= *it; } if(fs::is_directory(p)) { boost::shared_ptr ml(new MusicDirectory(p)); return ml; } else if(fs::is_regular_file(p)) { boost::shared_ptr ml(new MusicTrack(p)); return ml; } return MusicListing::p(); } /** Fetches a MusicListing object from the given \p path. * Splits the given string and calls the above function. */ MusicListing::p music::get(const std::string& path) { HTTP::Connection::PathList path_vector; boost::algorithm::split(path_vector, path, boost::algorithm::is_any_of("/\\")); return get(path_vector); } /** Fetches a directory. * This is a helper function which returns a null pointer if the fetched MusicListing isn't a directory. */ MusicDirectory::p music::get_directory(const std::string& path) { MusicListing::p ml = get(path); if(!ml || !fs::is_directory(ml->path)) { return MusicDirectory::p(); } MusicDirectory::p dir(boost::dynamic_pointer_cast(ml)); return dir; } /** Find tracks in the database. * Does a search on specific fields given by \p search. */ std::vector music::find(std::map search) { soci::session sql(config::vm["audist.database"].as()); soci::statement st(sql); std::string filename, artist, album, title; st.exchange(soci::into(filename)); std::string query = "SELECT file_name FROM tracks WHERE "; std::vector where_conditions; if(search.find("artist") != search.end()) { where_conditions.push_back("artist_id IN (SELECT id FROM artists WHERE name LIKE :artist)"); artist = "%"+search["artist"]+"%"; st.exchange(soci::use(artist, "artist")); } if(search.find("album") != search.end()) { where_conditions.push_back("album_id IN (SELECT id FROM albums WHERE name LIKE :album)"); album = "%"+search["album"]+"%"; st.exchange(soci::use(album, "album")); } if(search.find("title") != search.end()) { where_conditions.push_back("name LIKE :title"); artist = "%"+search["title"]+"%"; st.exchange(soci::use(title, "title")); } query += boost::algorithm::join(where_conditions, " AND "); st.alloc(); st.prepare(query); st.define_and_bind(); st.execute(true); std::vector results; while(st.fetch()) { MusicListing::p ml(new MusicTrack(filename)); results.push_back(ml); } sql.close(); return results; } /** Find tracks in the database. * Returns tracks where title, artist, album or filename matches \p search. */ std::vector music::find(std::string search) { soci::session sql(config::vm["audist.database"].as()); search = "%"+search+"%"; soci::rowset rs = (sql.prepare << "SELECT DISTINCT file_name FROM tracks WHERE name LIKE :search OR file_name LIKE :search OR " "artist_id IN (SELECT id FROM artists WHERE name LIKE :search) OR album_id IN (SELECT id FROM albums WHERE name LIKE :search)", soci::use(search, "search")); std::vector results; for(soci::rowset::const_iterator it = rs.begin(); it != rs.end(); it++) { MusicListing::p ml(new MusicTrack(*it)); results.push_back(ml); } return results; } /** Initiate an update on \p path and its subdirs. * music::update does the actual work. */ void music::begin_update(const std::string path) { MusicDirectory::p dir = get_directory(path); std::cout << boost::format("updater(%s) called") % path << std::endl; if(dir) { update(dir->path); } } /** Recursively update \p dir and its subdirectories. */ void music::update(const MusicDirectory& dir) { // TODO: Fix engine-specific SQL syntax inside this function. soci::session sql(config::vm["audist.database"].as()); BOOST_FOREACH(fs::path t, dir.tracks) { std::cout << "track " << t << std::endl; Tag::p tag = Tag::load(t.string()); BOOST_FOREACH(Tag::Fields::value_type& f, tag->fields) { std::cout << boost::format(" %s: %s") % f.first % f.second << std::endl; } int artist_id = 0, album_id = 0, track_id = 0; if(tag->has_field("artist")) { sql << "SELECT id FROM artists WHERE name = :name", soci::use(tag->fields["artist"]), soci::into(artist_id); if(!sql.got_data()) sql << "INSERT INTO artists (name) VALUES (:name) RETURNING id", soci::use(tag->fields["artist"]), soci::into(artist_id); } if(tag->has_field("album")) { std::string query(boost::str(boost::format("SELECT id FROM albums WHERE %s AND name = :name") % (artist_id ? boost::str(boost::format("artist_id = %d") % artist_id) : "artist_id IS NULL"))); sql << query, soci::use(tag->fields["album"]), soci::into(album_id); if(!sql.got_data()) { soci::indicator ind = (artist_id ? soci::i_ok : soci::i_null); sql << "INSERT INTO albums (artist_id, name) VALUES (:artist_id, :name) RETURNING id", soci::use(artist_id, ind), soci::use(tag->fields["album"]), soci::into(album_id); } } if(tag->has_field("title")) { std::string query(boost::str(boost::format("SELECT id FROM tracks WHERE %s AND %s AND name = :name") % (artist_id ? boost::str(boost::format("artist_id = %d") % artist_id) : "artist_id IS NULL") % (album_id ? boost::str(boost::format("album_id = %d") % album_id) : "album_id IS NULL"))); sql << query, soci::use(tag->fields["title"]), soci::into(track_id); if(!sql.got_data()) { soci::indicator artist_ind = (artist_id ? soci::i_ok : soci::i_null), album_ind = (album_id ? soci::i_ok : soci::i_null); sql << "INSERT INTO tracks (artist_id, album_id, name, file_name) VALUES (:artist_id, :album_id, :name, :file_name)", soci::use(artist_id, artist_ind), soci::use(album_id, album_ind), soci::use(tag->fields["title"]), soci::use(t.string()); } } } sql.close(); std::for_each(dir.directories.begin(), dir.directories.end(), update); } void MusicDirectory::render(HTTP::Connection::p req) { req->add_header("content-type", "text/html"); std::string base_path = boost::algorithm::join(req->base_path, "/"); // strip root_directory from the absolute file path std::string rel_base(path.string().substr(music::root_directory.string().size())); // set relative base to "/" if we're at the root_directory if(!rel_base.size()) rel_base = "/"; req->send_data(boost::str(boost::format("

%s

") % rel_base)); // link to parent directory if we're not at the root_directory if(rel_base != "/") { std::string rel_parent(rel_base.substr(0, rel_base.find_last_of('/'))); req->send_data(boost::str(boost::format("..
") % base_path % rel_parent)); } for(PathListings::iterator it = directories.begin(); it != directories.end(); it++) { std::string rel_path = it->string().substr(music::root_directory.string().size()); req->send_data(boost::str(boost::format("%s
") % base_path % rel_path % it->filename())); } for(PathListings::iterator it = tracks.begin(); it != tracks.end(); it++) { std::string rel_path = it->string().substr(music::root_directory.string().size()); req->send_data(boost::str(boost::format("%s
") % base_path % rel_path % it->filename())); } } MusicTrack::MusicTrack(const fs::path path) { std::cout << path << std::endl; this->path = path; } MusicDirectory::MusicDirectory(const fs::path root) { this->path = root; std::cout << this->path << std::endl; fs::directory_iterator end_itr; for(fs::directory_iterator it(this->path); it != end_itr; it++) { if(fs::is_directory(it->status())) { directories.push_back(it->path()); } else if(fs::is_regular_file(it->status())) { tracks.push_back(it->path()); } } } void MusicTrack::render(HTTP::Connection::p req) { req->add_header("content-type", "application/octet-stream"); if(req->args.count("decoder") && req->args.count("encoder")) { Decoder::p decoder = Decoder::get(req->args["decoder"], path.string()); Encoder::p encoder = Encoder::get(req->args["encoder"], decoder); // TODO: Make an encoder-to-istream adapter to get rid of this: char data[0x10000]; std::streamsize size = 1; while(size) { size = encoder->read(data, 0x10000); if(size > 0) req->send_data(data, size); } } else { fs::ifstream is(path, std::ios::in | std::ios::binary); is.seekg(0, std::ios::end); req->add_header("content-length", boost::str(boost::format("%d") % is.tellg())); is.seekg(0, std::ios::beg); req->send_data(is); } }