diff options
author | Jon Bergli Heier <snakebite@jvnv.net> | 2011-01-06 04:01:17 +0100 |
---|---|---|
committer | Jon Bergli Heier <snakebite@jvnv.net> | 2011-01-06 04:05:46 +0100 |
commit | 78c5f37d50ed5687bd5525954838f62b50de0690 (patch) | |
tree | 2def0d74af305f040f6e48e8f644621cd8add64e | |
parent | e755f01e631e832e3ef529049888d62af38d2b38 (diff) |
Reworked "find" to allow for more sophisticated searches.
The usage of the "find" command is as follows:
find SEARCH
where SEARCH is either a simple string to search for, or key:value pairs
with the following keys:
artist
album
title
-rw-r--r-- | commands.cpp | 45 | ||||
-rw-r--r-- | commands.h | 4 | ||||
-rw-r--r-- | music.cpp | 70 | ||||
-rw-r--r-- | music.h | 3 |
4 files changed, 97 insertions, 25 deletions
diff --git a/commands.cpp b/commands.cpp index 84c1e5f..76ce2ff 100644 --- a/commands.cpp +++ b/commands.cpp @@ -3,9 +3,13 @@ #include <boost/format.hpp> #include <boost/algorithm/string/join.hpp> #include <boost/bind.hpp> +#include <boost/spirit/include/qi.hpp> +#include <boost/fusion/include/std_pair.hpp> #include <iostream> +namespace qi = boost::spirit::qi; + std::vector<std::string> Commands::ls() { if(args.size() != 2) { throw CommandException("usage: ls DIR"); @@ -29,24 +33,39 @@ std::vector<std::string> Commands::ls() { return result; } +typedef std::map<std::string, std::string> stringmap; + +static bool find_parse(std::string str, stringmap& foo) { + typedef std::string::const_iterator Iterator; + + Iterator begin = str.begin(); + Iterator end = str.end(); + + return qi::parse(begin, end, + // pairs wrapped in "quotes" + (('"' >> +(qi::char_ - ':') >> ':' >> +(qi::char_ - '"') >> '"') | + // simple key:value pairs + (+(qi::char_ - ':' - ' ') >> ':' >> +(qi::char_ - ' '))) + // skip spaces between pairs + % +qi::char_(' '), + foo) && begin == end; +} + std::vector<std::string> Commands::find() { - if(args.size() != 3) { - throw CommandException("usage: find TYPE SEARCH"); + if(args.size() < 2) { + throw CommandException("usage: find SEARCH"); } - FindFunction ff; - if(find_handlers.find(args[1]) != find_handlers.end()) { - ff = find_handlers[args[1]]; + stringmap find_args; + args.erase(args.begin()); + std::string search = boost::algorithm::join(args, " "); + std::vector<MusicListing::p> ml; + if(find_parse(search, find_args)) { + ml = music::find(find_args); } else { - std::vector<std::string> types; - for(std::map<std::string, FindFunction>::iterator it = find_handlers.begin(); it != find_handlers.end(); it++) { - types.push_back(it->first); - } - std::string s = boost::str(boost::format("unknown search type, must be one of %s") % boost::algorithm::join(types, ", ")); - throw CommandException(s.c_str()); + ml = music::find(search); } - std::vector<MusicListing::p> ml = ff(args[2]); if(!ml.size()) { throw CommandException("no results"); } @@ -56,7 +75,6 @@ std::vector<std::string> Commands::find() { result.push_back((*it)->path.string()); } - return result; } @@ -74,7 +92,6 @@ std::vector<std::string> Commands::update() { Commands::Commands(boost::asio::io_service& io_service_, std::vector<std::string>& args_) : io_service(io_service_), args(args_) { handlers["ls"] = &Commands::ls; handlers["find"] = &Commands::find; - find_handlers["artist"] = music::find_artist; handlers["update"] = &Commands::update; } @@ -24,10 +24,6 @@ class Commands { typedef boost::function<std::vector<std::string> (Commands*)> Handler; std::map<std::string, Handler> handlers; - typedef boost::function<std::vector<MusicListing::p> (const std::string artist)> FindFunction; - //! Handlers for the find command. - std::map<std::string, FindFunction> find_handlers; - boost::asio::io_service& io_service; //! Command arguments. std::vector<std::string>& args; @@ -24,7 +24,7 @@ void music::init(std::string root) { root_directory = root; } -/** Fetches a MusicListing object from the given path. +/** 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. */ @@ -52,7 +52,7 @@ MusicListing::p music::get(const HTTP::Connection::PathList& path) { return MusicListing::p(); } -/** Fetches a MusicListing object from the given path. +/** 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) { @@ -74,21 +74,76 @@ MusicDirectory::p music::get_directory(const std::string& path) { return dir; } -std::vector<MusicListing::p> music::find_artist(const std::string artist) { +/** Find tracks in the database. + * Does a search on specific fields given by \p search. + */ +std::vector<MusicListing::p> music::find(std::map<std::string, std::string> search) { + soci::session sql(config::vm["audist.database"].as<std::string>()); + 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<std::string> 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(); + std::cout << "query: " << query << std::endl; + std::cout << "st.execute: " << st.execute(true) << std::endl; + std::cout << "st.got_data: " << st.got_data() << std::endl; + + std::vector<MusicListing::p> 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<MusicListing::p> music::find(std::string search) { soci::session sql(config::vm["audist.database"].as<std::string>()); - soci::rowset<std::string> rs = (sql.prepare << "SELECT file_name FROM tracks WHERE artist_id IN (SELECT id FROM artists WHERE name LIKE :name)", - soci::use("%"+artist+"%")); + search = "%"+search+"%"; + soci::rowset<std::string> 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<MusicListing::p> results; for(soci::rowset<std::string>::const_iterator it = rs.begin(); it != rs.end(); it++) { - boost::shared_ptr<MusicListing> ml(new MusicTrack(*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; @@ -97,7 +152,10 @@ void music::begin_update(const std::string 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<std::string>()); BOOST_FOREACH(fs::path t, dir.tracks) { @@ -43,7 +43,8 @@ namespace music { MusicListing::p get(const HTTP::Connection::PathList& path); MusicListing::p get(const std::string& path); MusicDirectory::p get_directory(const std::string& path); - std::vector<MusicListing::p> find_artist(const std::string artist); + std::vector<MusicListing::p> find(const std::map<std::string, std::string> search); + std::vector<MusicListing::p> find(std::string search); void begin_update(const std::string path); void update(const MusicDirectory& dir); }; |