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; } }*/ }