diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | SConstruct | 1 | ||||
-rw-r--r-- | cache.cpp | 47 | ||||
-rw-r--r-- | cache.h | 26 | ||||
-rw-r--r-- | config.cpp | 1 | ||||
-rw-r--r-- | database.cpp | 110 | ||||
-rw-r--r-- | database.h | 27 | ||||
-rw-r--r-- | encoders/lame_encoder.cpp | 14 | ||||
-rw-r--r-- | encoders/lame_encoder.h | 3 | ||||
-rw-r--r-- | music.cpp | 140 | ||||
-rw-r--r-- | music.h | 7 |
11 files changed, 257 insertions, 122 deletions
@@ -9,3 +9,6 @@ .sconsign.dblite .sconf_temp config.log +/audist.db +/static +/cache @@ -24,6 +24,7 @@ else: env.ParseConfig('pkg-config --cflags --libs libmpg123') env.ParseConfig('pkg-config --cflags --libs id3tag') env.ParseConfig('pkg-config --cflags --libs vorbisenc') +env.ParseConfig('pkg-config --cflags --libs libssl') env.Program('audistd', Glob('*.cpp') + Glob('decoders/*.cpp') + Glob('encoders/*.cpp')) diff --git a/cache.cpp b/cache.cpp new file mode 100644 index 0000000..92e0cfa --- /dev/null +++ b/cache.cpp @@ -0,0 +1,47 @@ +#include "cache.h" +#include "config.h" + +#include <boost/format.hpp> +#include <boost/filesystem/fstream.hpp> +#include <openssl/sha.h> + +#include <iostream> +#include <stdexcept> + +EncodedCache::EncodedCache(fs::path path, Decoder::p decoder, Encoder::p encoder) { + this->decoder = decoder; + this->encoder = encoder; + + // TODO: Differentiate between encoders? + SHA_CTX context; + unsigned char md[SHA_DIGEST_LENGTH]; + SHA_Init(&context); + SHA_Update(&context, (unsigned char*)path.string().c_str(), path.string().length()); + SHA_Final(md, &context); + for(int i = 0; i < SHA_DIGEST_LENGTH; i++) { + hash = hash + (boost::format("%02x") % (int)md[i]).str(); + } +} + +fs::path EncodedCache::get_path() { + return fs::path(config::vm["audist.cache_dir"].as<std::string>()) / hash; +} + +void EncodedCache::create_cache() { + fs::path path = get_path(); + + // TODO: Locking? + fs::ofstream os(path, std::ios::out | std::ios::binary); + // TODO: Make an encoder-to-istream adapter to get rid of this: + + if(!os.is_open()) + throw std::runtime_error("failed to create cache object"); + char data[0x10000]; + std::streamsize size = 1; + while(size) { + size = encoder->read(data, 0x10000); + if(size > 0) + os.write(data, size); + } + os.close(); +} @@ -0,0 +1,26 @@ +#ifndef CACHE_H +#define CACHE_H + +#include "decoder.h" +#include "encoder.h" + +#include <boost/filesystem.hpp> + +namespace fs = boost::filesystem; + +class EncodedCache { + private: + Decoder::p decoder; + Encoder::p encoder; + + std::string hash; + + public: + EncodedCache(fs::path path, Decoder::p decoder, Encoder::p encoder); + virtual ~EncodedCache() {}; + + fs::path get_path(); + void create_cache(); +}; + +#endif @@ -14,6 +14,7 @@ void config::init() { ("audist.telnetd_port", po::value<int>()->default_value(7681), "telnetd port") ("audist.threads", po::value<std::size_t>()->default_value(10), "threads") ("audist.database", po::value<std::string>(), "database string") + ("audist.cache_dir", po::value<std::string>(), "cache dir") ; std::ifstream is("audist.conf", std::ios::in); po::store(po::parse_config_file(is, desc, true), vm); diff --git a/database.cpp b/database.cpp new file mode 100644 index 0000000..ea87e17 --- /dev/null +++ b/database.cpp @@ -0,0 +1,110 @@ +#include "database.h" +#include "config.h" + +#include <boost/algorithm/string/join.hpp> +#include <boost/format.hpp> + +Database::Database() { + sql.open(config::vm["audist.database"].as<std::string>()); +} + +Database::~Database() { + sql.close(); +} + +// NOTE: LIKE is case-insensitive by default on sqlite +std::vector<MusicListing::p> Database::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(); + st.execute(true); + + std::vector<MusicListing::p> results; + while(st.fetch()) { + MusicListing::p ml(new MusicTrack(filename)); + results.push_back(ml); + } + sql.close(); + + return results; +} + +std::vector<MusicListing::p> Database::find(std::string search) { + soci::session sql(config::vm["audist.database"].as<std::string>()); + + 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++) { + MusicListing::p ml(new MusicTrack(*it)); + results.push_back(ml); + } + + return results; +} + +// NOTE: last_insert_rowid() is sqlite-specific +void Database::update(fs::path track, Tag::p tag) { + int64_t 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)", soci::use(tag->fields["artist"]); + sql << "SELECT last_insert_rowid()", 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)", soci::use(artist_id, ind), soci::use(tag->fields["album"]); + sql << "SELECT last_insert_rowid()", 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(track.string()); + } + } +} diff --git a/database.h b/database.h new file mode 100644 index 0000000..a6ec5ad --- /dev/null +++ b/database.h @@ -0,0 +1,27 @@ +#ifndef DATABASE_H +#define DATABASE_H + +#include "music.h" +#include "tag.h" + +#include <soci/soci.h> +#include <boost/filesystem.hpp> + +#include <vector> +#include <map> +#include <string> + +class Database { + private: + soci::session sql; + + public: + Database(); + virtual ~Database(); + + std::vector<MusicListing::p> find(std::map<std::string, std::string> search); + std::vector<MusicListing::p> find(std::string search); + void update(fs::path track, Tag::p tag); +}; + +#endif diff --git a/encoders/lame_encoder.cpp b/encoders/lame_encoder.cpp index 6616b87..f3816bf 100644 --- a/encoders/lame_encoder.cpp +++ b/encoders/lame_encoder.cpp @@ -15,24 +15,16 @@ EncoderLame::~EncoderLame() { lame_close(gfp); } -size_t EncoderLame::encode(const uint8_t *input, size_t input_size, uint8_t *output, size_t output_size) { - return lame_encode_buffer_interleaved(gfp, (short*)input, input_size / 4, output, output_size); -} - -size_t EncoderLame::flush(uint8_t *output, size_t output_size) { - return lame_encode_flush(gfp, output, output_size); -} - std::size_t EncoderLame::read(char* buf, std::size_t buf_size) { char src_data[0x30000]; std::streamsize src_read = source->read(src_data, 0x30000); if(src_read < 0) src_read = 0; - std::streamsize size = encode((const uint8_t*)src_data, src_read, (uint8_t*)buf, buf_size); + std::size_t size = lame_encode_buffer_interleaved(gfp, (short*)src_data, src_read / 4, (unsigned char*)buf, buf_size); // no more data, flush encoder if(src_read == 0 && size == 0) { - size = flush((uint8_t*)buf, buf_size); + size = lame_encode_flush(gfp, (unsigned char*)buf, buf_size); } return size; -}
\ No newline at end of file +} diff --git a/encoders/lame_encoder.h b/encoders/lame_encoder.h index d150f60..79bc944 100644 --- a/encoders/lame_encoder.h +++ b/encoders/lame_encoder.h @@ -11,9 +11,6 @@ class EncoderLame : public Encoder { private: lame_global_flags *gfp; RawAudioSource::p source; - - size_t encode(const uint8_t *input, size_t input_size, uint8_t *output, size_t output_size); - size_t flush(uint8_t *output, size_t output_size); public: EncoderLame(RawAudioSource::p source_); @@ -2,7 +2,8 @@ #include "decoder.h" #include "encoder.h" #include "tag.h" -#include "config.h" +#include "database.h" +#include "cache.h" #include <boost/format.hpp> #include <boost/algorithm/string/predicate.hpp> @@ -77,65 +78,16 @@ MusicDirectory::p music::get_directory(const std::string& path) { * 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(); - st.execute(true); - - std::vector<MusicListing::p> results; - while(st.fetch()) { - MusicListing::p ml(new MusicTrack(filename)); - results.push_back(ml); - } - sql.close(); - - return results; + Database db; + return db.find(search); } /** 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>()); - - 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++) { - MusicListing::p ml(new MusicTrack(*it)); - results.push_back(ml); - } - - return results; + Database db; + return db.find(search); } /** Initiate an update on \p path and its subdirs. @@ -145,58 +97,28 @@ 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); + Database db; + dir->update(db); } } /** 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) { - std::cout << "track " << t << std::endl; - Tag::p tag = Tag::load(t.string()); +void MusicDirectory::update(Database& db) { + BOOST_FOREACH(fs::path track, tracks) { + std::cout << "track " << track << std::endl; + Tag::p tag = Tag::load(track.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()); - } - } + db.update(track, tag); } - sql.close(); - std::for_each(dir.directories.begin(), dir.directories.end(), update); + for(PathListings::iterator it = directories.begin(); it != directories.end(); it++) { + MusicDirectory dir(*it); + dir.update(db); + } } void MusicDirectory::render(HTTP::Connection::p req) { @@ -247,7 +169,9 @@ MusicDirectory::MusicDirectory(const fs::path root) { } void MusicTrack::render(HTTP::Connection::p req) { +<<<<<<< HEAD req->add_header("Content-Type", "audio/mpeg"); + fs::path file_path = path; int skip = 0; int range = 0; @@ -264,6 +188,10 @@ void MusicTrack::render(HTTP::Connection::p req) { Decoder::p decoder = Decoder::get(req->args["decoder"], path.string()); Encoder::p encoder = Encoder::get(req->args["encoder"], decoder); + EncodedCache ec(path, decoder, encoder); + file_path = ec.get_path(); + if(!fs::exists(file_path)) + ec.create_cache(); // TODO: Make an encoder-to-istream adapter to get rid of this: char data[0x10000]; std::streamsize size = 1; @@ -285,16 +213,16 @@ void MusicTrack::render(HTTP::Connection::p req) { if(size > 0) req->send_data(data, size); } - - } else { - req->send_file(path); - /* - 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); - */ } + + req->send_file(path); + + /* + fs::ifstream is(file_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); + */ } @@ -25,6 +25,9 @@ class MusicTrack : public MusicListing { virtual void render(HTTP::Connection::p req); }; +// Forward declaration for MusicDirectory::update() +class Database; + //! Represents a directory. class MusicDirectory : public MusicListing { public: @@ -35,6 +38,7 @@ class MusicDirectory : public MusicListing { MusicDirectory(const fs::path root); virtual void render(HTTP::Connection::p req); + void update(Database& db); }; namespace music { @@ -43,10 +47,9 @@ 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(const std::map<std::string, std::string> search); + std::vector<MusicListing::p> find(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); }; #endif |