summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--SConstruct1
-rw-r--r--cache.cpp47
-rw-r--r--cache.h26
-rw-r--r--config.cpp1
-rw-r--r--database.cpp110
-rw-r--r--database.h27
-rw-r--r--encoders/lame_encoder.cpp14
-rw-r--r--encoders/lame_encoder.h3
-rw-r--r--music.cpp140
-rw-r--r--music.h7
11 files changed, 257 insertions, 122 deletions
diff --git a/.gitignore b/.gitignore
index 85183e8..fc370ea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,6 @@
.sconsign.dblite
.sconf_temp
config.log
+/audist.db
+/static
+/cache
diff --git a/SConstruct b/SConstruct
index 6b960cf..65311ab 100644
--- a/SConstruct
+++ b/SConstruct
@@ -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();
+}
diff --git a/cache.h b/cache.h
new file mode 100644
index 0000000..21d4a52
--- /dev/null
+++ b/cache.h
@@ -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
diff --git a/config.cpp b/config.cpp
index 68e8a2f..910d0f0 100644
--- a/config.cpp
+++ b/config.cpp
@@ -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_);
diff --git a/music.cpp b/music.cpp
index e277ee5..712a461 100644
--- a/music.cpp
+++ b/music.cpp
@@ -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);
+ */
}
diff --git a/music.h b/music.h
index f7d06bd..17f99f2 100644
--- a/music.h
+++ b/music.h
@@ -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