Archive
/
/
/
/
/
Echo.Database.Library.vala
2008-12-02 07:03:38 UTC
previous
next
/* vim: set noexpandtab tabstop=4 shiftwidth=4 nowrap textwidth=100
*
* Echo Media Player
* Copyright (C) 2008 Shane O'Connell
*
* [ The original file includes a copyright header in this location describing
* the file as being released under the terms of the GNU General Public
* License. It has been removed in order to display the file as part of the
* archive. ]
*/
using Echo.Core;
using Echo.Interfaces;
using Echo.Database.SQL;
public class Echo.Database.Library : GLib.Object, IPlaySource
{
private DatabaseConnection _db;
private int64 _id;
private string _name;
private string _icon;
private string _uri;
private Track[] _tracks;
private Gee.HashMap<int64, int> _track_id_map;
public string name { get { return _name; } }
public string icon { get { return _icon; } }
private string _last_refresh_message;
private GLib.Mutex _last_refresh_message_mutex;
public Library(DatabaseConnection db,
int64 id, string name, string icon, string uri)
{
_db = db;
_id = id;
_name = name;
_icon = icon;
_uri = uri;
_last_refresh_message_mutex = new GLib.Mutex();
// TODO handle error
// don't throw from the constructor, that doesn't work in vala yet
load_tracks();
}
private void load_tracks() throws SQLError
{
var stmt = _db.prepare(
"SELECT COUNT(*) FROM Tracks WHERE LibraryID = %lld".printf(_id));
if (!stmt.step())
throw new SQLError.GENERIC("Can't get row count for LibraryTracks");
_tracks = new Track[stmt.get_int_column(0)];
_track_id_map = new Gee.HashMap<int64, int>();
stmt = _db.prepare(
" SELECT" +
" TrackID," +
" ArtistName, AlbumName, AlbumReleaseYear, " +
" AlbumReleaseMonth, AlbumReleaseDay, " +
" TrackName, TrackNumber, TrackRating, TrackURI" +
" FROM" +
" Tracks, Artists, Albums" +
" WHERE" +
" Tracks.AlbumID = Albums.AlbumID AND" +
" Albums.ArtistID = Artists.ArtistID AND" +
" Tracks.LibraryID = ?1" +
" ORDER BY" +
" ArtistName, AlbumReleaseYear, AlbumReleaseMonth, AlbumReleaseDay, " +
" AlbumName, TrackNumber");
stmt.bind_int64(1, _id);
int i = 0;
while (stmt.step()) {
_tracks[i] = new Track(this, stmt.get_int64_column(0),
stmt.get_string_column(6), // Track name
stmt.get_string_column(1), // Artist
stmt.get_string_column(2), // Album
stmt.get_int_column(7), // Track number
stmt.get_int_column(3), // Release year
stmt.get_int_column(4), // Release month
stmt.get_int_column(5), // Release day
stmt.get_string_column(9), // URI
stmt.get_int_column(8)); // Rating
_track_id_map.set(stmt.get_int64_column(0), i);
i++;
}
}
public int get_num_tracks()
{
return _tracks.length;
}
public weak ITrack get_track(int index)
{
return _tracks[index];
}
public int get_index_from_track_id(int64 track_id)
{
return _track_id_map.get(track_id);
}
public void set_rating(int64 track_id, int value)
{
var stmt = _db.prepare("UPDATE Tracks SET TrackRating = ?2 WHERE TrackID = ?1");
stmt.bind_int64(1, track_id);
stmt.bind_int64(2, value);
stmt.step();
debug("Library: track_changed: %d", get_index_from_track_id(track_id));
this.track_changed(get_index_from_track_id(track_id));
}
private bool _currently_refreshing = false;
public void refresh_tracks()
{
if (_currently_refreshing)
return;
_currently_refreshing = true;
GLib.Thread.create(() => {
refresh_tracks_thread(_uri);
// TODO remove tracks from database that don't exist
// TODO remove artist/albums from database that aren't referenced by any tracks
GLib.Idle.add(this.refresh_tracks_finished);
}, false);
}
private bool refresh_tracks_finished()
{
load_tracks();
_currently_refreshing = false;
Services.ui().main_window.show_status_message("");
// TODO use the statusbar context id thing so that this doesn't erase any other status messages
// by another part of the program
this.source_refreshed();
return false;
}
private void refresh_tracks_thread(string uri)
{
var file = GLib.File.new_for_uri(uri);
var file_info = file.query_info("standard::type,standard::fast-content-type",
FileQueryInfoFlags.NONE, null);
/*debug("file.get_uri() = %s", file.get_uri());
debug("file_info.get_name() = %s", file_info.get_name());
debug("file_info.get_file_type() = %u", (uint)file_info.get_file_type());
debug("file_info.get_attribute_uint32() = %u", file_info.get_attribute_uint32("standard::type"));*/
if (file_info.get_file_type() == GLib.FileType.DIRECTORY) {
var children = file.enumerate_children("standard::name,standard::display-name",
FileQueryInfoFlags.NONE, null);
var child = children.next_file(null);
while (child != null) {
if (child.get_display_name().size() >= 1 && child.get_display_name()[0] != '.') {
// TODO add some kind of protection against infinitely recursive directories
refresh_tracks_thread(file.get_child(child.get_name()).get_uri());
}
child = children.next_file(null);
}
}
else {
string content_type = file_info.get_attribute_string("standard::fast-content-type");
if (file.is_native() && content_type == "audio/mpeg") { // MP3
// TODO trim whitespace at either side of the mp3 title/artist/album etc.
var file_tags = new TagLib.File(file.get_path());
update_refreshed_track(new StubTrack(
file_tags.tag().title(),
file_tags.tag().artist(),
file_tags.tag().album(),
(int)file_tags.tag().track(),
(int)file_tags.tag().year(), 0, 0,
uri));
// TODO make sure tags are okay before accepting
// if they aren't okay use the filename as the title
}
}
}
private void update_refreshed_track(StubTrack track)
{
int64 artist_id, album_id, track_id;
_db.run("BEGIN EXCLUSIVE TRANSACTION");
var stmt = _db.prepare("SELECT ArtistID FROM Artists WHERE ArtistName = ?1");
stmt.bind_string(1, track.artist);
if (stmt.step())
artist_id = stmt.get_int64_column(0);
else {
stmt = _db.prepare("INSERT INTO Artists (ArtistName) VALUES (?1)");
stmt.bind_string(1, track.artist);
stmt.step();
artist_id = _db.last_rowid();
}
stmt = _db.prepare(
"SELECT AlbumID FROM Albums WHERE ArtistID = ?1 AND AlbumName = ?2 AND " +
"AlbumReleaseYear = ?3 AND AlbumReleaseMonth = ?4 AND AlbumReleaseDay = ?5");
stmt.bind_int64(1, artist_id);
stmt.bind_string(2, track.album);
stmt.bind_int(3, track.release_year);
stmt.bind_int(4, track.release_month);
stmt.bind_int(5, track.release_day);
if (stmt.step())
album_id = stmt.get_int64_column(0);
else {
stmt = _db.prepare("INSERT INTO Albums (ArtistID, AlbumName, " +
"AlbumReleaseYear, AlbumReleaseMonth, AlbumReleaseDay) VALUES (?1,?2,?3,?4,?5)");
stmt.bind_int64(1, artist_id);
stmt.bind_string(2, track.album);
stmt.bind_int(3, track.release_year);
stmt.bind_int(4, track.release_month);
stmt.bind_int(5, track.release_day);
stmt.step();
album_id = _db.last_rowid();
}
stmt = _db.prepare("SELECT TrackID FROM Tracks WHERE TrackURI = ?1");
stmt.bind_string(1, track.uri);
if (stmt.step()) {
track_id = stmt.get_int64_column(0);
stmt = _db.prepare("UPDATE Tracks " +
"SET AlbumID = ?2, TrackName = ?3, TrackNumber = ?4 WHERE TrackID = ?1");
stmt.bind_int64(1, track_id);
stmt.bind_int64(2, album_id);
stmt.bind_string(3, track.title);
stmt.bind_int(4, track.track_number);
stmt.step();
}
else {
stmt = _db.prepare(
"INSERT INTO Tracks (LibraryID, AlbumID, TrackName, TrackNumber, TrackURI) " +
"VALUES (?1, ?2, ?3, ?4, ?5)");
stmt.bind_int64(1, _id);
stmt.bind_int64(2, album_id);
stmt.bind_string(3, track.title);
stmt.bind_int(4, track.track_number);
stmt.bind_string(5, track.uri);
stmt.step();
track_id = _db.last_rowid();
}
/*
stmt = _db.prepare("INSERT INTO LibraryTracks (LibraryID, TrackID) VALUES (?1, ?2)");
stmt.bind_int64(1, _id);
stmt.bind_int64(2, track_id);
stmt.step();
*/
_db.run("END TRANSACTION");
_last_refresh_message_mutex.@lock();
_last_refresh_message = "Refreshing tracks... (%s - %s)".printf(track.artist, track.title);
_last_refresh_message_mutex.unlock();
GLib.Idle.add(this.update_refresh_message);
}
private bool update_refresh_message()
{
_last_refresh_message_mutex.@lock();
Services.ui().main_window.show_status_message(_last_refresh_message);
_last_refresh_message_mutex.unlock();
return false;
}
// --------------------------------------[Library iterator]-------------------------------------
/*
public IPlaySourceTrackRef get_track_ref(int index)
{
return new LibraryIterator(this, index);
}
private class LibraryIterator : GLib.Object, IPlaySourceTrackRef
{
private Library _library;
private int _index;
private bool _valid;
public LibraryIterator(Library library, int index)
{
_library = library;
_index = index;
_valid = true;
// TODO check that these functions are automatically detached from the signal
// when the iterator is freed
_library.track_removed += (library, removed_index) => {
if (removed_index < _index)
_index--;
else if (removed_index == _index) {
_valid = false;
this.track_invalidated();
}
};
_library.track_inserted += (library, inserted_index) => {
if (inserted_index <= _index)
_index++;
};
_library.source_refreshed += (library) => {
// TODO store reference to database TrackID so that we don't
// have to invalidate this track reference when the library is refreshed
_valid = false;
this.track_invalidated();
};
}
public bool next()
{
if (!_valid)
return false;
if (_index < _library.get_num_tracks())
_index++;
return (_index < _library.get_num_tracks());
}
public bool prev()
{
if (!_valid)
return false;
if (_index >= 0)
_index--;
return (_index >= 0);
}
public weak Track? get()
{
if (!_valid)
return null;
if (_index >= 0 && _index < _library.get_num_tracks())
return _library.get_track(_index);
else
return null;
}
public IPlaySource? get_source()
{
if (!_valid)
return null;
else
return _library;
}
public int get_index()
{
if (!_valid || _index < 0 || _index >= _library.get_num_tracks())
return -1;
else
return _index;
}
}*/
}