From 84aac6b2746599f6bf3680b7d34e5536c0da1e2d Mon Sep 17 00:00:00 2001 From: wr61 Date: Sun, 27 Feb 2005 15:20:04 +0000 Subject: split mg_db.[ch] into mg_content and mg_selection git-svn-id: https://vdr-muggle.svn.sourceforge.net/svnroot/vdr-muggle/branches/0.1.3-wr@516 e10066b5-e1e2-0310-b819-94efdf66514b --- Makefile | 2 +- mg_content.c | 244 +++++++++ mg_content.h | 112 ++++ mg_db.c | 1450 ---------------------------------------------------- mg_db.h | 574 --------------------- mg_selection.c | 1224 ++++++++++++++++++++++++++++++++++++++++++++ mg_selection.h | 493 ++++++++++++++++++ muggle.c | 2 +- vdr_decoder.c | 2 +- vdr_decoder_flac.c | 2 +- vdr_decoder_mp3.c | 2 +- vdr_decoder_ogg.c | 2 +- vdr_player.c | 1 - vdr_player.h | 2 +- 14 files changed, 2080 insertions(+), 2032 deletions(-) create mode 100644 mg_content.c create mode 100644 mg_content.h delete mode 100644 mg_db.c delete mode 100644 mg_db.h create mode 100644 mg_selection.c create mode 100644 mg_selection.h diff --git a/Makefile b/Makefile index 2f5ac30..2349694 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ MIFLAGS += -I/usr/include/taglib -lmysqlclient ### The object files (add further files here): -OBJS = $(PLUGIN).o i18n.o mg_valmap.o mg_order.o mg_db.o mg_actions.o vdr_menu.o mg_tools.o \ +OBJS = $(PLUGIN).o i18n.o mg_valmap.o mg_order.o mg_content.o mg_selection.o mg_actions.o vdr_menu.o mg_tools.o \ vdr_decoder_mp3.o vdr_stream.o vdr_decoder.o vdr_player.o \ vdr_setup.o mg_setup.o diff --git a/mg_content.c b/mg_content.c new file mode 100644 index 0000000..8e2ca29 --- /dev/null +++ b/mg_content.c @@ -0,0 +1,244 @@ +/*! + * \file mg_selection.c + * \brief A general interface to data items, currently only GiantDisc + * + * \version $Revision: 1.0 $ + * \date $Date: 2004-12-07 10:10:35 +0200 (Tue, 07 Dec 2004) $ + * \author Wolfgang Rohdewald + * \author Responsible author: $Author: wr $ + * + */ + +#include +#include "mg_selection.h" +#include "vdr_setup.h" +#include "mg_tools.h" + + +string +mgContentItem::getKeyId(mgKeyTypes kt) +{ + if (m_id<0) + return ""; + switch (kt) { + case keyGenres: + case keyGenre1: + case keyGenre2: + case keyGenre3: return m_genre1_id; + default: return getKeyValue(kt); + } +} + +string +mgContentItem::getKeyValue(mgKeyTypes kt) +{ + if (m_id<0) + return ""; + switch (kt) { + case keyGenres: + case keyGenre1: + case keyGenre2: + case keyGenre3: return getGenre(); + case keyArtist: return getArtist(); + case keyAlbum: return getAlbum(); + case keyYear: return string(ltos(getYear())); + case keyDecade: return string(ltos(int((getYear() % 100) / 10) * 10)); + case keyTitle: return getTitle(); + case keyTrack: return getTitle(); + default: return ""; + } +} + + +string mgContentItem::getGenre () const +{ + string result=""; + if (m_genre1!="NULL") + result = m_genre1; + if (m_genre2!="NULL") + { + if (!result.empty()) + result += "/"; + result += m_genre2; + } + return result; +} + + +string mgContentItem::getBitrate () const +{ + return m_bitrate; +} + + +string mgContentItem::getImageFile () const +{ + return "Name of Imagefile"; +} + + +string mgContentItem::getAlbum () const +{ + return m_albumtitle; +} + + +int mgContentItem::getYear () const +{ + return m_year; +} + + +int mgContentItem::getRating () const +{ + return m_rating; +} + + +int mgContentItem::getDuration () const +{ + return m_duration; +} + + +int mgContentItem::getSampleRate () const +{ + return m_samplerate; +} + + +int mgContentItem::getChannels () const +{ + return m_channels; +} + +mgContentItem::mgContentItem () +{ + m_id = -1; +} + +mgContentItem::mgContentItem (const mgContentItem* c) +{ + m_id = c->m_id; + m_title = c->m_title; + m_mp3file = c->m_mp3file; + m_artist = c->m_artist; + m_albumtitle = c->m_albumtitle; + m_genre1_id = c->m_genre1_id; + m_genre2_id = c->m_genre2_id; + m_genre1 = c->m_genre1; + m_genre2 = c->m_genre2; + m_bitrate = c->m_bitrate; + m_year = c->m_year; + m_rating = c->m_rating; + m_duration = c->m_duration; + m_samplerate = c->m_samplerate; + m_channels = c->m_channels; +} + +static char *mg_readline(FILE *f) +{ + static char buffer[MAXPARSEBUFFER]; + if (fgets(buffer, sizeof(buffer), f) > 0) { + int l = strlen(buffer) - 1; + if (l >= 0 && buffer[l] == '\n') + buffer[l] = 0; + return buffer; + } + return 0; +} + +static const char *FINDCMD = "cd '%s' 2>/dev/null && find -follow -name '%s' -print 2>/dev/null"; + +static string +GdFindFile( const char* tld, string mp3file ) +{ + string result = ""; + char *cmd = 0; + asprintf( &cmd, FINDCMD, tld, mp3file.c_str() ); + FILE *p = popen( cmd, "r" ); + if (p) + { + char *s; + if( (s = mg_readline(p) ) != 0) + result = string(s); + pclose(p); + } + + free( cmd ); + + return result; +} + +string +mgContentItem::getSourceFile(bool AbsolutePath) const +{ + const char* tld = the_setup.ToplevelDir; + string result=""; + if (AbsolutePath) result = tld; + if (the_setup.GdCompatibility) + result += GdFindFile(tld,m_mp3file); + else + result += m_mp3file; + return result; +} + +mgContentItem::mgContentItem (const mgSelection* sel,const MYSQL_ROW row) +{ + m_id = atol (row[0]); + if (row[1]) + m_title = row[1]; + else + m_title = "NULL"; + if (row[2]) + m_mp3file = row[2]; + else + m_mp3file = "NULL"; + if (row[3]) + m_artist = row[3]; + else + m_artist = "NULL"; + if (row[4]) + m_albumtitle = row[4]; + else + m_albumtitle = "NULL"; + if (row[5]) + { + m_genre1_id = row[5]; + m_genre1 = sel->value(keyGenres,row[5]); + } + else + m_genre1 = "NULL"; + if (row[6]) + { + m_genre2_id = row[6]; + m_genre2 = sel->value(keyGenres,row[6]); + } + else + m_genre2 = "NULL"; + if (row[7]) + m_bitrate = row[7]; + else + m_bitrate = "NULL"; + if (row[8]) + m_year = atol (row[8]); + else + m_year = 0; + if (row[9]) + m_rating = atol (row[9]); + else + m_rating = 0; + if (row[10]) + m_duration = atol (row[10]); + else + m_duration = 0; + if (row[11]) + m_samplerate = atol (row[11]); + else + m_samplerate = 0; + if (row[12]) + m_channels = atol (row[12]); + else + m_channels = 0; +}; + diff --git a/mg_content.h b/mg_content.h new file mode 100644 index 0000000..76d5869 --- /dev/null +++ b/mg_content.h @@ -0,0 +1,112 @@ +/*! + * \file mg_selection.h + * \brief A general interface to data items, currently only GiantDisc + * + * \version $Revision: 1.0 $ + * \date $Date: 2004-12-07 10:10:35 +0200 (Tue, 07 Dec 2004) $ + * \author Wolfgang Rohdewald + * \author Responsible author: $Author: wr $ + * + */ + +#ifndef _MG_CONTENT_H +#define _MG_CONTENT_H +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +#include "mg_tools.h" +#include "mg_valmap.h" +#include "mg_order.h" + +typedef vector strvector; + + +class mgSelection; + +//! \brief represents a content item like an mp3 file. +class mgContentItem +{ + public: + mgContentItem (); + + string getKeyValue(mgKeyTypes kt); + string getKeyId(mgKeyTypes kt); + + //! \brief copy constructor + mgContentItem(const mgContentItem* c); + + //! \brief construct an item from an SQL row + mgContentItem (const mgSelection* sel, const MYSQL_ROW row); +//! \brief returns track id + long getId () const + { + return m_id; + } + +//! \brief returns title + string getTitle () const + { + return m_title; + } + +//! \brief returns filename + string getSourceFile (bool AbsolutePath=true) const; + +//! \brief returns artist + string getArtist () const + { + return m_artist; + } + +//! \brief returns the name of the album + string getAlbum () const; + +//! \brief returns the name of genre + string getGenre () const; + +//! \brief returns the bitrate + string getBitrate () const; + +//! \brief returns the file name of the album image + string getImageFile () const; + +//! \brief returns year + int getYear () const; + +//! \brief returns rating + int getRating () const; + +//! \brief returns duration + int getDuration () const; + +//! \brief returns samplerate + int getSampleRate () const; + +//! \brief returns # of channels + int getChannels () const; + + private: + long m_id; + string m_title; + string m_mp3file; + string m_artist; + string m_albumtitle; + string m_genre1_id; + string m_genre2_id; + string m_genre1; + string m_genre2; + string m_bitrate; + int m_year; + int m_rating; + int m_duration; + int m_samplerate; + int m_channels; +}; + +#endif diff --git a/mg_db.c b/mg_db.c deleted file mode 100644 index f215e92..0000000 --- a/mg_db.c +++ /dev/null @@ -1,1450 +0,0 @@ -/*! - * \file mg_db.c - * \brief A database interface to the GiantDisc - * - * \version $Revision: 1.0 $ - * \date $Date: 2004-12-07 10:10:35 +0200 (Tue, 07 Dec 2004) $ - * \author Wolfgang Rohdewald - * \author Responsible author: $Author: wr $ - * - */ - -#include -#include "mg_db.h" -#include "vdr_setup.h" -#include "mg_tools.h" - -bool needGenre2; -bool needGenre2_set; - -//! \brief adds string n to string s, using a comma to separate them -static string comma (string & s, string n); - -/*! \brief returns a random integer within some range - */ -unsigned int -randrange (const unsigned int high) -{ - unsigned int result=0; - result = random () % high; - return result; -} - - -static string -comma (string & s, string n) -{ - return addsep (s, ",", n); -} - - -static string zerostring; - -bool -mgSelection::mgSelStrings::operator==(const mgSelStrings&x) const -{ - bool result = strings.size()==x.strings.size(); - if (result) - for (unsigned int i=0;irefreshValues(); - return strings.size(); -} - -string& -mgSelection::mgSelStrings::operator[](unsigned int idx) -{ - if (!m_sel) - mgError("mgSelStrings: m_sel is 0"); - m_sel->refreshValues(); - if (idx>=strings.size()) return zerostring; - return strings[idx]; -} - -void -mgSelection::mgSelStrings::setOwner(mgSelection* sel) -{ - m_sel = sel; -} - -void -mgSelection::clearCache() const -{ - m_current_values = ""; - m_current_tracks = ""; -} - -string -mgSelection::getCurrentValue() -{ - return values[gotoPosition()]; -} - -string -mgSelection::getKeyValue(const unsigned int level) const -{ - return order.getKeyValue(level); -} - -unsigned int -mgSelection::getKeyIndex(const unsigned int level) const -{ - return valindex(getKeyValue(level)); -} - - -mgKeyTypes -mgSelection::getKeyType (const unsigned int level) const -{ - return order.getKeyType(level); -} - -MYSQL_RES* -mgSelection::exec_sql(string query) const -{ - return ::exec_sql(m_db, query); -} - -/*! \brief executes a query and returns the first columnu of the - * first row. - * \param query the SQL query string to be executed - */ -string mgSelection::get_col0 (string query) const -{ - return ::get_col0(m_db, query); -} - - -unsigned long -mgSelection::exec_count (string query) const -{ - return ::exec_count(m_db, query); -} - - - -string -mgSelection::sql_string (const string s) const -{ - char *buf = (char *) malloc (s.size () * 2 + 1); - mysql_real_escape_string (m_db, buf, s.c_str (), s.size ()); - string result = "'" + std::string (buf) + "'"; - free (buf); - return result; -} - -string -mgContentItem::getKeyId(mgKeyTypes kt) -{ - if (m_id<0) - return ""; - switch (kt) { - case keyGenres: - case keyGenre1: - case keyGenre2: - case keyGenre3: return m_genre1_id; - default: return getKeyValue(kt); - } -} - -string -mgContentItem::getKeyValue(mgKeyTypes kt) -{ - if (m_id<0) - return ""; - switch (kt) { - case keyGenres: - case keyGenre1: - case keyGenre2: - case keyGenre3: return getGenre(); - case keyArtist: return getArtist(); - case keyAlbum: return getAlbum(); - case keyYear: return string(ltos(getYear())); - case keyDecade: return string(ltos(int((getYear() % 100) / 10) * 10)); - case keyTitle: return getTitle(); - case keyTrack: return getTitle(); - default: return ""; - } -} - -mgContentItem * -mgSelection::getTrack (unsigned int position) -{ - if (position >= getNumTracks ()) - return 0; - return &(m_tracks[position]); -} - - -string mgContentItem::getGenre () const -{ - string result=""; - if (m_genre1!="NULL") - result = m_genre1; - if (m_genre2!="NULL") - { - if (!result.empty()) - result += "/"; - result += m_genre2; - } - return result; -} - - -string mgContentItem::getBitrate () const -{ - return m_bitrate; -} - - -string mgContentItem::getImageFile () const -{ - return "Name of Imagefile"; -} - - -string mgContentItem::getAlbum () const -{ - return m_albumtitle; -} - - -int mgContentItem::getYear () const -{ - return m_year; -} - - -int mgContentItem::getRating () const -{ - return m_rating; -} - - -int mgContentItem::getDuration () const -{ - return m_duration; -} - - -int mgContentItem::getSampleRate () const -{ - return m_samplerate; -} - - -int mgContentItem::getChannels () const -{ - return m_channels; -} - - -mgSelection::ShuffleMode mgSelection::toggleShuffleMode () -{ - setShuffleMode((m_shuffle_mode == SM_PARTY) ? SM_NONE : ShuffleMode (m_shuffle_mode + 1)); - Shuffle(); - return getShuffleMode(); -} - -void -mgSelection::setShuffleMode (mgSelection::ShuffleMode mode) -{ - m_shuffle_mode = mode; -} - -void -mgSelection::Shuffle() const -{ - unsigned int tracksize = getNumTracks(); - if (tracksize==0) return; - switch (m_shuffle_mode) - { - case SM_NONE: - { - long id = m_tracks[getTrackPosition()].getId (); - m_current_tracks = ""; // force a reload - tracksize = getNumTracks(); // getNumTracks also reloads - for (unsigned int i = 0; i < tracksize; i++) - if (m_tracks[i].getId () == id) - { - setTrackPosition(i); - break; - } - } - break; - case SM_PARTY: - case SM_NORMAL: - { - // play all, beginning with current track: - mgContentItem tmp = m_tracks[getTrackPosition()]; - m_tracks[getTrackPosition()]=m_tracks[0]; - m_tracks[0]=tmp; - setTrackPosition(0); - // randomize all other tracks - for (unsigned int i = 1; i < tracksize; i++) - { - unsigned int j = 1+randrange (tracksize-1); - tmp = m_tracks[i]; - m_tracks[i] = m_tracks[j]; - m_tracks[j] = tmp; - } - } break; -/* - * das kapiere ich nicht... (wolfgang) - - Party mode (see iTunes) - - initialization - - find 15 titles according to the scheme below - - playing - - before entering next title perform track selection - - track selection - - generate a random uid - - if file exists: - - determine maximum playcount of all tracks -- generate a random number n -- if n < playcount / max. playcount -- add the file to the end of the list -*/ - } -} - - -mgSelection::LoopMode mgSelection::toggleLoopMode () -{ - m_loop_mode = (m_loop_mode == LM_FULL) ? LM_NONE : LoopMode (m_loop_mode + 1); - return m_loop_mode; -} - - -unsigned int -mgSelection::AddToCollection (const string Name) -{ - if (!m_db) return 0; - CreateCollection(Name); - string listid = sql_string (get_col0 - ("SELECT id FROM playlist WHERE title=" + sql_string (Name))); - unsigned int tracksize = getNumTracks (); - - // this code is rather complicated but works in a multi user - // environment: - - // insert a unique trackid: - string trackid = ltos(mysql_thread_id(m_db)+1000000); - exec_sql("INSERT INTO playlistitem SELECT "+listid+"," - "MAX(tracknumber)+"+ltos(tracksize)+","+trackid+ - " FROM playlistitem WHERE playlist="+listid); - - // find tracknumber of the trackid we just inserted: - string sql = string("SELECT tracknumber FROM playlistitem WHERE " - "playlist=")+listid+" AND trackid="+trackid; - long first = atol(get_col0(sql).c_str()) - tracksize + 1; - - // replace the place holder trackid by the correct value: - exec_sql("UPDATE playlistitem SET trackid="+ltos(m_tracks[tracksize-1].getId())+ - " WHERE playlist="+listid+" AND trackid="+trackid); - - // insert all other tracks: - const char *sql_prefix = "INSERT INTO playlistitem VALUES "; - sql = ""; - for (unsigned int i = 0; i < tracksize-1; i++) - { - string item = "(" + listid + "," + ltos (first + i) + "," + - ltos (m_tracks[i].getId ()) + ")"; - comma(sql, item); - if ((i%100)==99) - { - exec_sql (sql_prefix+sql); - sql = ""; - } - } - if (!sql.empty()) exec_sql (sql_prefix+sql); - if (inCollection(Name)) clearCache (); - return tracksize; -} - - -unsigned int -mgSelection::RemoveFromCollection (const string Name) -{ - if (!m_db) return 0; - mgParts p = order.Parts(m_db,m_level,false); - string sql = p.sql_delete_from_collection(id(keyCollection,Name)); - exec_sql (sql); - unsigned int removed = mysql_affected_rows (m_db); - if (inCollection(Name)) clearCache (); - return removed; -} - - -bool mgSelection::DeleteCollection (const string Name) -{ - if (!m_db) return false; - ClearCollection(Name); - exec_sql ("DELETE FROM playlist WHERE title=" + sql_string (Name)); - if (isCollectionlist()) clearCache (); - return (mysql_affected_rows (m_db) == 1); -} - - -void mgSelection::ClearCollection (const string Name) -{ - if (!m_db) return; - string listid = id(keyCollection,Name); - exec_sql ("DELETE FROM playlistitem WHERE playlist="+sql_string(listid)); - if (inCollection(Name)) clearCache (); -} - - -bool mgSelection::CreateCollection(const string Name) -{ - if (!m_db) return false; - string name = sql_string(Name); - if (exec_count("SELECT count(title) FROM playlist WHERE title = " + name)>0) - return false; - exec_sql ("INSERT playlist VALUES(" + name + ",'VDR',NULL,NULL,NULL)"); - if (isCollectionlist()) clearCache (); - return true; -} - - -string mgSelection::exportM3U () -{ - -// open a file for writing - string fn = "/tmp/" + ListFilename () + ".m3u"; - FILE * listfile = fopen (fn.c_str (), "w"); - if (!listfile) - return ""; - fprintf (listfile, "#EXTM3U\n"); - unsigned int tracksize = getNumTracks (); - for (unsigned i = 0; i < tracksize; i++) - { - mgContentItem& t = m_tracks[i]; - fprintf (listfile, "#EXTINF:%d,%s\n", t.getDuration (), - t.getTitle ().c_str ()); - fprintf (listfile, "#MUGGLE:%ld\n", t.getId()); - fprintf (listfile, "%s\n", t.getSourceFile (false).c_str ()); - } - fclose (listfile); - return fn; -} - -bool -mgSelection::empty() -{ - if (m_level>= order.size ()-1) - return ( getNumTracks () == 0); - else - return ( values.size () == 0); -} - -void -mgSelection::setPosition (unsigned int position) -{ - if (m_level == order.size()) - setTrackPosition(position); - else - m_position = position; -} - -void -mgSelection::setTrackPosition (unsigned int position) const -{ - m_tracks_position = position; -} - -unsigned int -mgSelection::getPosition () const -{ - if (m_level == order.size()) - return getTrackPosition(); - else - return m_position; -} - -unsigned int -mgSelection::gotoPosition () -{ - if (m_level == order.size ()) - return gotoTrackPosition(); - else - { - unsigned int valsize = values.size(); - if (valsize==0) - m_position = 0; - else if (m_position >= valsize) - m_position = valsize -1; - return m_position; - } -} - -unsigned int -mgSelection::getTrackPosition() const -{ - if (m_tracks_position>=m_tracks.size()) - if (m_tracks.size()==0) - m_tracks_position=0; - else - m_tracks_position = m_tracks.size()-1; - return m_tracks_position; -} - -unsigned int -mgSelection::gotoTrackPosition() -{ - unsigned int tracksize = getNumTracks (); - if (tracksize == 0) - setTrackPosition(0); - else if (m_tracks_position >= tracksize) - setTrackPosition(tracksize -1); - return m_tracks_position; -} - -bool mgSelection::skipTracks (int steps) -{ - unsigned int tracksize = getNumTracks(); - if (tracksize == 0) - return false; - if (m_loop_mode == LM_SINGLE) - return true; - unsigned int old_pos = m_tracks_position; - unsigned int new_pos; - if (old_pos + steps < 0) - { - if (m_loop_mode == LM_NONE) - return false; - new_pos = tracksize - 1; - } - else - new_pos = old_pos + steps; - if (new_pos >= tracksize) - { - if (m_loop_mode == LM_NONE) - return false; - new_pos = 0; - } - setTrackPosition (new_pos); - return (new_pos == gotoTrackPosition()); -} - - -unsigned long -mgSelection::getLength () -{ - unsigned long result = 0; - unsigned int tracksize = getNumTracks (); - for (unsigned int i = 0; i < tracksize; i++) - result += m_tracks[i].getDuration (); - return result; -} - - -unsigned long -mgSelection::getCompletedLength () const -{ - unsigned long result = 0; - tracks (); // make sure they are loaded - for (unsigned int i = 0; i < getTrackPosition(); i++) - result += m_tracks[i].getDuration (); - return result; -} - - - -string mgSelection::getListname () const -{ - list st; - for (unsigned int i = 0; i < m_level; i++) - st.push_back(order.getKeyValue(i)); - st.unique(); - string result=""; - for (list < string >::iterator it = st.begin (); it != st.end (); ++it) - { - addsep (result, ":", *it); - } - if (result.empty ()) - if (order.size()>0) - result = string(ktName(order.getKeyType(0))); - return result; -} - -string mgSelection::ListFilename () -{ - string res = getListname (); - // convert char set ? - string::iterator it; - for (it=res.begin();it!=res.end();it++) - { - char& c = *it; - switch (c) - { - case '\'': - case '/': - case '\\': - case ' ': - case ')': - case '(': c = '_';break; - } - } - return res; -} - -const vector < mgContentItem > & -mgSelection::tracks () const -{ - if (!m_db) return m_tracks; - if (!m_current_tracks.empty()) return m_tracks; - mgParts p = order.Parts(m_db,m_level); - p.fields.clear(); - p.fields.push_back("tracks.id"); - p.fields.push_back("tracks.title"); - p.fields.push_back("tracks.mp3file"); - p.fields.push_back("tracks.artist"); - p.fields.push_back("album.title"); - p.fields.push_back("tracks.genre1"); - p.fields.push_back("tracks.genre2"); - p.fields.push_back("tracks.bitrate"); - p.fields.push_back("tracks.year"); - p.fields.push_back("tracks.rating"); - p.fields.push_back("tracks.length"); - p.fields.push_back("tracks.samplerate"); - p.fields.push_back("tracks.channels"); - p.tables.push_back("tracks"); - p.tables.push_back("album"); - for (unsigned int i = m_level; iParts(m_db,true); - m_current_tracks = p.sql_select(false); - m_tracks.clear (); - MYSQL_RES *rows = exec_sql (m_current_tracks); - if (rows) - { - MYSQL_ROW row; - while ((row = mysql_fetch_row (rows)) != 0) - m_tracks.push_back (mgContentItem (this,row)); - mysql_free_result (rows); - } - if (m_shuffle_mode!=SM_NONE) - Shuffle(); - return m_tracks; -} - - -mgContentItem::mgContentItem () -{ - m_id = -1; -} - -mgContentItem::mgContentItem (const mgContentItem* c) -{ - m_id = c->m_id; - m_title = c->m_title; - m_mp3file = c->m_mp3file; - m_artist = c->m_artist; - m_albumtitle = c->m_albumtitle; - m_genre1_id = c->m_genre1_id; - m_genre2_id = c->m_genre2_id; - m_genre1 = c->m_genre1; - m_genre2 = c->m_genre2; - m_bitrate = c->m_bitrate; - m_year = c->m_year; - m_rating = c->m_rating; - m_duration = c->m_duration; - m_samplerate = c->m_samplerate; - m_channels = c->m_channels; -} - -static char *mg_readline(FILE *f) -{ - static char buffer[MAXPARSEBUFFER]; - if (fgets(buffer, sizeof(buffer), f) > 0) { - int l = strlen(buffer) - 1; - if (l >= 0 && buffer[l] == '\n') - buffer[l] = 0; - return buffer; - } - return 0; -} - -static const char *FINDCMD = "cd '%s' 2>/dev/null && find -follow -name '%s' -print 2>/dev/null"; - -static string -GdFindFile( const char* tld, string mp3file ) -{ - string result = ""; - char *cmd = 0; - asprintf( &cmd, FINDCMD, tld, mp3file.c_str() ); - FILE *p = popen( cmd, "r" ); - if (p) - { - char *s; - if( (s = mg_readline(p) ) != 0) - result = string(s); - pclose(p); - } - - free( cmd ); - - return result; -} - -string -mgContentItem::getSourceFile(bool AbsolutePath) const -{ - const char* tld = the_setup.ToplevelDir; - string result=""; - if (AbsolutePath) result = tld; - if (the_setup.GdCompatibility) - result += GdFindFile(tld,m_mp3file); - else - result += m_mp3file; - return result; -} - -mgContentItem::mgContentItem (const mgSelection* sel,const MYSQL_ROW row) -{ - m_id = atol (row[0]); - if (row[1]) - m_title = row[1]; - else - m_title = "NULL"; - if (row[2]) - m_mp3file = row[2]; - else - m_mp3file = "NULL"; - if (row[3]) - m_artist = row[3]; - else - m_artist = "NULL"; - if (row[4]) - m_albumtitle = row[4]; - else - m_albumtitle = "NULL"; - if (row[5]) - { - m_genre1_id = row[5]; - m_genre1 = sel->value(keyGenres,row[5]); - } - else - m_genre1 = "NULL"; - if (row[6]) - { - m_genre2_id = row[6]; - m_genre2 = sel->value(keyGenres,row[6]); - } - else - m_genre2 = "NULL"; - if (row[7]) - m_bitrate = row[7]; - else - m_bitrate = "NULL"; - if (row[8]) - m_year = atol (row[8]); - else - m_year = 0; - if (row[9]) - m_rating = atol (row[9]); - else - m_rating = 0; - if (row[10]) - m_duration = atol (row[10]); - else - m_duration = 0; - if (row[11]) - m_samplerate = atol (row[11]); - else - m_samplerate = 0; - if (row[12]) - m_channels = atol (row[12]); - else - m_channels = 0; -}; - -void mgSelection::InitSelection() { - setDB(0); - m_Directory="."; - m_level = 0; - m_position = 0; - m_tracks_position = 0; - m_trackid = -1; - if (the_setup.InitShuffleMode) - m_shuffle_mode = SM_NORMAL; - else - m_shuffle_mode = SM_NONE; - if (the_setup.InitLoopMode) - m_loop_mode = LM_FULL; - else - m_loop_mode = LM_NONE; - clearCache(); - values.setOwner(this); -} - - -mgSelection::mgSelection (const bool fall_through) -{ - InitSelection (); - Connect(); - m_fall_through = fall_through; -} - -mgSelection::mgSelection (const mgSelection &s) -{ - InitFrom(&s); -} - -mgSelection::mgSelection (const mgSelection* s) -{ - InitFrom(s); -} - -mgSelection::mgSelection (mgValmap& nv) -{ - InitFrom(nv); -} - -void -mgSelection::setDB(MYSQL *db) -{ - m_db = db; -} - -void -mgSelection::setOrder(mgOrder* o) -{ - if (o) - { - order = *o; - } - else - mgWarning("mgSelection::setOrder(0)"); -} - -void -mgSelection::InitFrom(mgValmap& nv) -{ - InitSelection(); - Connect(); - m_fall_through = nv.getbool("FallThrough"); - m_Directory = nv.getstr("Directory"); - while (m_level < nv.getuint("Level")) - { - char *idx; - asprintf(&idx,"order.Keys.%u.Position",m_level); - string newval = nv.getstr(idx); - free(idx); - if (!enter (newval)) - if (!select (newval)) break; - } - m_trackid = nv.getlong("TrackId"); - setPosition(nv.getstr("Position")); - if (m_level>=order.size()-1) - setTrackPosition(nv.getlong("TrackPosition")); -} - - -mgSelection::~mgSelection () -{ - if (m_db) - mysql_close (m_db); -} - -void mgSelection::InitFrom(const mgSelection* s) -{ - InitSelection(); - m_fall_through = s->m_fall_through; - m_Directory = s->m_Directory; - map_values = s->map_values; - map_ids = s->map_ids; - order = s->order; - m_level = s->m_level; - m_position = s->m_position; - m_trackid = s->m_trackid; - m_tracks_position = s->m_tracks_position; - Connect(); - setShuffleMode (s->getShuffleMode ()); - setLoopMode (s->getLoopMode ()); -} - -unsigned int -mgSelection::ordersize () -{ - return order.size (); -} - -unsigned int -mgSelection::valindex (const string val,const bool second_try) const -{ - for (unsigned int i = 0; i < values.size (); i++) - { - if (values[i] == val) - return i; - } - // nochmal mit neuen Werten: - clearCache(); - refreshValues(); - if (second_try) { - esyslog("valindex: Gibt es nicht:%s",val.c_str()); - return 0; - } - else - return valindex(val,true); -} - -unsigned int -mgSelection::valcount (string value) -{ - assert(m_counts.size()==values.size()); - return m_counts[valindex(value)]; -} - -unsigned int -mgSelection::idindex (const string id,const bool second_try) const -{ - for (unsigned int i = 0; i < m_ids.size (); i++) - if (m_ids[i] == id) - return i; - // nochmal mit neuen Werten: - clearCache(); - refreshValues(); - if (second_try) { - mgWarning("idindex: Gibt es nicht:%s",id.c_str()); - return 0; - } - else - return idindex(id,true); -} - - -void -mgSelection::refreshValues () const -{ - assert(this); - if (!m_db) - return; - if (m_current_values.empty()) - { - mgParts p = order.Parts(m_db,m_level); - m_current_values = p.sql_select(); - values.strings.clear (); - m_ids.clear (); - m_counts.clear(); - MYSQL_RES *rows = exec_sql (m_current_values); - if (rows) - { - unsigned int num_fields = mysql_num_fields(rows); - MYSQL_ROW row; - while ((row = mysql_fetch_row (rows)) != 0) - { - if (!row[0]) continue; - string r0 = row[0]; - if (r0=="NULL") // there is a genre NULL! - continue; - if (num_fields==3) - { - if (!row[1]) continue; - string r1 = row[1]; - values.strings.push_back (r0); - m_ids.push_back (r1); - m_counts.push_back(atol(row[2])); - } - else - { - values.strings.push_back (value(order.Key(m_level),r0)); - m_ids.push_back (r0); - m_counts.push_back(atol(row[1])); - } - } - mysql_free_result (rows); - } - } -} - -unsigned int -mgSelection::count () const -{ - return values.size (); -} -void -mgSelection::Connect () -{ - if (m_db) - { - mysql_close (m_db); - setDB(0); - } - if (the_setup.DbHost == "") return; - setDB(mysql_init (0)); - if (!m_db) - return; - bool success; - if (the_setup.DbSocket != NULL) - { - mgDebug(1,"Using socket %s for connecting to Database %s as user %s.", - the_setup.DbSocket, - the_setup.DbName, - the_setup.DbUser); - mgDebug(3,"DbPassword is: '%s'",the_setup.DbPass); - success = (mysql_real_connect( m_db, - "", - the_setup.DbUser, - the_setup.DbPass, - the_setup.DbName, - 0, - the_setup.DbSocket, 0 ) != 0 ); - } - else - { - mgDebug(1,"Using TCP-%s for connecting to Database %s as user %s.", - the_setup.DbHost, - the_setup.DbName, - the_setup.DbUser); - mgDebug(3,"DbPassword is: '%s'",the_setup.DbPass); - success = ( mysql_real_connect( m_db, - the_setup.DbHost, - the_setup.DbUser, - the_setup.DbPass, - the_setup.DbName, - the_setup.DbPort, - 0, 0 ) != 0 ); - } - if (!success) - { - mgWarning("Failed to connect to host '%s' as User '%s', Password '%s': Error: %s", - the_setup.DbHost,the_setup.DbUser,the_setup.DbPass,mysql_error(m_db)); - mysql_close (m_db); - setDB(0); - } - if (!needGenre2_set && m_db) - { - needGenre2_set=true; - needGenre2=exec_count("SELECT COUNT(DISTINCT genre2) from tracks")>1; - } - return; -} - - -bool mgSelection::enter (unsigned int position) -{ - if (order.empty()) - esyslog("mgSelection::enter(%u): order is empty", position); - if (empty()) - return false; - setPosition (position); - position = gotoPosition(); // reload adjusted position - if (values.size()==0) - return false; - string value = values[position]; - string id = m_ids[position]; - mgSelStrings prev; - if (m_fall_through && values.size()<10) - prev=values; - while (1) - { - if (m_level >= order.size () - 1) - return false; - order[m_level++]->set (value,id); - clearCache(); - if (m_level >= order.size()) - mgError("mgSelection::enter(%u): level greater than order.size() %u", - m_level,order.size()); - m_position = 0; - refreshValues(); - if (count()==0) - break; - value = values[0]; - id = m_ids[0]; - if (!m_fall_through) - break; - if (m_level==order.size()-1) - break; - if (count () > 1 && !(prev==values)) - break; - } - return true; -} - - -bool mgSelection::select (unsigned int position) -{ - if (m_level == order.size () - 1) - { - if (getNumTracks () <= position) - return false; - order[m_level]->set (values[position],m_ids[position]); - m_level++; - m_trackid = m_tracks[position].getId (); - - clearCache(); - return true; - } - else - return enter (position); -} - -bool -mgSelection::leave () -{ - string prevvalue; - if (order.empty()) - { - mgWarning("mgSelection::leave(): order is empty"); - return false; - } - if (m_level == order.size ()) - { - m_level--; - prevvalue=order.getKeyValue(m_level); - order[m_level]->set("",EMPTY); - m_trackid = -1; - clearCache(); - setPosition(prevvalue); - return true; - } - mgSelStrings prev; - if (m_fall_through && values.size()<10) - prev=values; - while (1) - { - if (m_level < 1) - return false; - if (m_levelset ("",EMPTY); - m_level--; - prevvalue=order.getKeyValue(m_level); - if (m_levelset ("",EMPTY); - clearCache(); - if (!m_fall_through) - break; - if (count () > 1 && !(prev==values)) - break; - } - setPosition(prevvalue); - return true; -} - -void -mgSelection::leave_all () -{ - m_level=0; - for (unsigned int i=0;iset ("",EMPTY); - clearCache(); -} - -void -mgSelection::selectfrom(mgOrder& oldorder,mgContentItem* o) -{ - leave_all(); - string selval; - string selid; - assert(m_level==0); - for (unsigned int idx = 0; idx < ordersize(); idx++) - { - selval = EMPTY; - selid = EMPTY; - mgKeyTypes new_kt = getKeyType(idx); - for (unsigned int i=0;inew_kt - && iskeyGenre(old_kt) - && iskeyGenre(new_kt)) - { - selid = id(new_kt,value(new_kt,oldorder.getKeyId(i))); - selval= value(new_kt,selid); - } - if (selid!=EMPTY) break; - } - if (selid==EMPTY && o && o->getId()>=0) - { - selval = o->getKeyValue(new_kt); - selid = o->getKeyId(new_kt); - } - if (selid==EMPTY) - break; - if (m_levelset (selval, selid); - } - else - { - setPosition(selval); - return; - } - } - if (m_level>0) - { - m_level--; - selval = order.getKeyValue(m_level); - order[m_level]->set("",EMPTY); - setPosition(selval); - order[m_level+1]->set("",EMPTY); - } - assert(m_level& valmap = map_values[kt]; - map::iterator it; - it = valmap.find(id); - if (it!=valmap.end()) - { - string r = it->second; - if (!r.empty()) - return r; - } - map_ids[kt].clear(); - loadvalues(kt); - it = valmap.find(id); - if (it!=valmap.end()) - return valmap[id]; - } - return id; -} - -string -mgSelection::value(mgKey* k, string id) const -{ - return value(k->Type(),id); -} - -string -mgSelection::value(mgKey* k) const -{ - return value(k,k->id()); -} - -string -mgSelection::id(mgKeyTypes kt, string val) const -{ - if (loadvalues (kt)) - { - map& idmap = map_ids[kt]; - string v = idmap[val]; - if (kt==keyGenre1) v=v.substr(0,1); - if (kt==keyGenre2) v=v.substr(0,2); - if (kt==keyGenre3) v=v.substr(0,3); - return v; - } - else - return val; -} - -string -mgSelection::id(mgKey* k, string val) const -{ - return id(k->Type(),val); -} - -string -mgSelection::id(mgKey* k) const -{ - return k->id(); -} - -bool -mgSelection::UsedBefore(mgOrder *o,const mgKeyTypes kt,unsigned int level) const -{ - if (level>=o->size()) - level = o->size() -1; - for (unsigned int lx = 0; lx < level; lx++) - if (o->getKeyType(lx)==kt) - return true; - return false; -} - -bool mgSelection::isLanguagelist() const -{ - return (order.getKeyType(0) == keyLanguage); -} - -bool mgSelection::isCollectionlist () const -{ - if (order.size()==0) return false; - return (order.getKeyType(0) == keyCollection && m_level == 0); -} - -bool -mgSelection::inCollection(const string Name) const -{ - if (order.size()==0) return false; - bool result = (order.getKeyType(0) == keyCollection && m_level == 1); - if (result) - if (order.getKeyType(1) != keyCollectionItem) - mgError("inCollection: key[1] is not keyCollectionItem"); - if (!Name.empty()) - result &= (order.getKeyValue(0) == Name); - return result; -} - - -void mgSelection::DumpState(mgValmap& nv) const -{ - nv.put("FallThrough",m_fall_through); - nv.put("Directory",m_Directory); - nv.put("Level",int(m_level)); - for (unsigned int i=0;i=order.size()-1) - nv.put("TrackPosition",getTrackPosition()); -} - -map -mgSelection::UsedKeyValues() -{ - map result; - for (unsigned int idx = 0 ; idx < level() ; idx++) - { - result[order.getKeyType(idx)] = order.getKeyValue(idx); - } - if (level() < order.size()-1) - { - mgKeyTypes ch = order.getKeyType(level()); - result[ch] = getCurrentValue(); - } - return result; -} - -bool -mgSelection::loadvalues (mgKeyTypes kt) const -{ - if (map_ids.count(kt)>0) - return true; - map& idmap = map_ids[kt]; - mgKey* k = ktGenerate(kt); - if (k->map_idfield().empty()) - { - delete k; - return false; - } - map& valmap = map_values[kt]; - char *b; - asprintf(&b,"select %s,%s from %s;",k->map_idfield().c_str(),k->map_valuefield().c_str(),k->map_valuetable().c_str()); - MYSQL_RES *rows = exec_sql (string(b)); - free(b); - if (rows) - { - MYSQL_ROW row; - while ((row = mysql_fetch_row (rows)) != 0) - { - if (row[0] && row[1]) - { - valmap[row[0]] = row[1]; - idmap[row[1]] = row[0]; - } - } - mysql_free_result (rows); - } - delete k; - return true; -} - -static vector keycounts; - -unsigned int -mgSelection::keycount(mgKeyTypes kt) -{ - assert(strlen(m_db->host)); - if (keycounts.size()==0) - { - for (unsigned int ki=int(mgKeyTypesLow);ki<=int(mgKeyTypesHigh);ki++) - { - keycounts.push_back(-1); - } - } - int& count = keycounts[int(kt-mgKeyTypesLow)]; - if (count==-1) - { - mgKey* k = ktGenerate(kt); - if (k->Enabled(m_db)) - count = exec_count(k->Parts(m_db,true).sql_count()); - else - count = 0; - delete k; - } - return count; -} - - -vector -mgSelection::choices(mgOrder *o,unsigned int level, unsigned int *current) -{ - vector result; - if (level>o->size()) - { - *current = 0; - return result; - } - for (unsigned int ki=int(mgKeyTypesLow);ki<=int(mgKeyTypesHigh);ki++) - { - mgKeyTypes kt = mgKeyTypes(ki); - if (kt==o->getKeyType(level)) - { - *current = result.size(); - result.push_back(ktName(kt)); - continue; - } - if (UsedBefore(o,kt,level)) - continue; - if (kt==keyDecade && UsedBefore(o,keyYear,level)) - continue; - if (kt==keyGenre1) - { - if (UsedBefore(o,keyGenre2,level)) continue; - if (UsedBefore(o,keyGenre3,level)) continue; - if (UsedBefore(o,keyGenres,level)) continue; - } - if (kt==keyGenre2) - { - if (UsedBefore(o,keyGenre3,level)) continue; - if (UsedBefore(o,keyGenres,level)) continue; - } - if (kt==keyGenre3) - { - if (UsedBefore(o,keyGenres,level)) continue; - } - if (kt==keyFolder1) - { - if (UsedBefore(o,keyFolder2,level)) continue; - if (UsedBefore(o,keyFolder3,level)) continue; - if (UsedBefore(o,keyFolder4,level)) continue; - } - if (kt==keyFolder2) - { - if (UsedBefore(o,keyFolder3,level)) continue; - if (UsedBefore(o,keyFolder4,level)) continue; - } - if (kt==keyFolder3) - { - if (UsedBefore(o,keyFolder4,level)) continue; - } - if (kt==keyCollection || kt==keyCollectionItem) - result.push_back(ktName(kt)); - else if (keycount(kt)>1) - result.push_back(ktName(kt)); - } - return result; -} diff --git a/mg_db.h b/mg_db.h deleted file mode 100644 index a053757..0000000 --- a/mg_db.h +++ /dev/null @@ -1,574 +0,0 @@ -/*! - * \file mg_db.h - * \brief A database interface to the GiantDisc - * - * \version $Revision: 1.0 $ - * \date $Date: 2004-12-07 10:10:35 +0200 (Tue, 07 Dec 2004) $ - * \author Wolfgang Rohdewald - * \author Responsible author: $Author: wr $ - * - */ - -#ifndef _MG_DB_H -#define _MG_DB_H -#include -#include -#include -#include -#include -#include -#include -using namespace std; - -#include "mg_tools.h" -#include "mg_valmap.h" -#include "mg_order.h" - -typedef vector strvector; - - -class mgSelection; - -//! \brief represents a content item like an mp3 file. -class mgContentItem -{ - public: - mgContentItem (); - - string getKeyValue(mgKeyTypes kt); - string getKeyId(mgKeyTypes kt); - - //! \brief copy constructor - mgContentItem(const mgContentItem* c); - - //! \brief construct an item from an SQL row - mgContentItem (const mgSelection* sel, const MYSQL_ROW row); -//! \brief returns track id - long getId () const - { - return m_id; - } - -//! \brief returns title - string getTitle () const - { - return m_title; - } - -//! \brief returns filename - string getSourceFile (bool AbsolutePath=true) const; - -//! \brief returns artist - string getArtist () const - { - return m_artist; - } - -//! \brief returns the name of the album - string getAlbum () const; - -//! \brief returns the name of genre - string getGenre () const; - -//! \brief returns the bitrate - string getBitrate () const; - -//! \brief returns the file name of the album image - string getImageFile () const; - -//! \brief returns year - int getYear () const; - -//! \brief returns rating - int getRating () const; - -//! \brief returns duration - int getDuration () const; - -//! \brief returns samplerate - int getSampleRate () const; - -//! \brief returns # of channels - int getChannels () const; - - private: - long m_id; - string m_title; - string m_mp3file; - string m_artist; - string m_albumtitle; - string m_genre1_id; - string m_genre2_id; - string m_genre1; - string m_genre2; - string m_bitrate; - int m_year; - int m_rating; - int m_duration; - int m_samplerate; - int m_channels; -}; - -/*! - * \brief the only interface to the database. - * Some member functions are declared const although they can modify the inner state of mgSelection. - * But they only modify variables used for caching. With const, we want to express - * the logical constness. E.g. the selected tracks can change without breaking constness: - * The selection never defines concrete tracks but only how to choose them. - */ -class mgSelection -{ - private: - class mgSelStrings - { - friend class mgSelection; - private: - strvector strings; - mgSelection* m_sel; - void setOwner(mgSelection* sel); - public: - string& operator[](unsigned int idx); - bool operator==(const mgSelStrings&x) const; - size_t size() const; - }; - public: -//! \brief defines an order to be used - void setOrder(mgOrder *o); - - mgOrder& getOrder() { return order; } - -/*! \brief define various ways to play music in random order - * \todo Party mode is not implemented, does same as SM_NORMAL - */ - enum ShuffleMode - { - SM_NONE, //!< \brief play normal sequence - SM_NORMAL, //!< \brief a shuffle with a fair distribution - SM_PARTY //!< \brief select the next few songs randomly, continue forever - }; - -//! \brief define various ways to play music in a neverending loop - enum LoopMode - { - LM_NONE, //!< \brief do not loop - LM_SINGLE, //!< \brief loop a single track - LM_FULL //!< \brief loop the whole track list - }; - -//! \brief escapes special characters - string sql_string(const string s) const; - -/*! \brief the main constructor - * \param fall_through if TRUE: If enter() returns a choice - * containing only one item, that item is automatically entered. - * The analog happens with leave() - */ - mgSelection ( const bool fall_through = false); - -/*! \brief a copy constructor. Does a deep copy. - * Some of the data base content will only be retrieved by the - * new mgSelection as needed, so some data base - * overhead is involved - */ - mgSelection (const mgSelection& s); -/*! \brief a copy constructor. Does a deep copy. - * Some of the data base content will only be retrieved by the - * new mgSelection as needed, so some data base - * overhead is involved - */ - mgSelection (const mgSelection* s); - - -//! \brief initializes from a map. - void InitFrom(mgValmap& nv); - -//! \brief the normal destructor - ~mgSelection (); - -/*! \brief represents all values for the current level. The result - * is cached in values, subsequent accesses to values only incur a - * small overhead for building the SQL WHERE command. The values will - * be reloaded when the SQL command changes - */ - mutable mgSelStrings values; - -/*! \brief returns the name of a key - */ - mgKeyTypes getKeyType (const unsigned int level) const; - -//! \brief return the current value of this key - string getKeyValue (const unsigned int level) const; - unsigned int getKeyIndex(const unsigned int level) const; - -/*! \brief returns the current item from the value() list - */ - string getCurrentValue(); - -//! \brief returns a map (new allocated) for all used key fields and their values - map UsedKeyValues(); - -//! \brief the number of key fields used for the query - unsigned int ordersize (); - -//! \brief the number of music items currently selected - unsigned int count () const; - -//! \brief the current position - unsigned int getPosition ()const; - - //! \brief go to the current position. If it does not exist, - // go to the nearest. - unsigned int gotoPosition (); - - -//! \brief the current position in the tracks list - unsigned int getTrackPosition () const; - - //! \brief go to the current track position. If it does not exist, - // go to the nearest. - unsigned int gotoTrackPosition (); - -/*! \brief enter the the next higher level, go one up in the tree. - * If fall_through (see constructor) is set to true, and the - * level entered by enter() contains only one item, automatically - * goes down further until a level with more than one item is reached. - * \param position is the position in the current level that is to be expanded - * \return returns false if there is no further level - */ - bool enter (unsigned int position); - - /*! \brief like enter but if we are at the leaf level simply select - * the entry at position - */ - bool select (unsigned int position); - -/*! \brief enter the next higher level, expanding the current position. - * See also enter(unsigned int position) - */ - bool enter () - { - return enter (gotoPosition ()); - } - - /*! \brief like enter but if we are at the leaf level simply select - * the current entry - */ - bool select () - { - return select (gotoPosition ()); - } - -/*! \brief enter the next higher level, expanding the position holding a certain value - * \param value the position holding value will be expanded. - */ - bool enter (const string value) - { - return enter (valindex (value)); - } - - /*! \brief like enter but if we are at the leaf level simply select - * the current entry - */ - bool select (const string value) - { - return select (valindex(value)); - } - - bool selectid (const string id) - { - return select(idindex(id)); - } - - void selectfrom(mgOrder& oldorder,mgContentItem* o); - -/*! \brief leave the current level, go one up in the tree. - * If fall_through (see constructor) is set to true, and the - * level entered by leave() contains only one item, automatically - * goes up further until a level with more than one item is reached. - * \return returns false if there is no further upper level - */ - bool leave (); - -/*! \brief leave the current level, go up in the tree until - * target level is reached. - * If fall_through (see constructor) is set to true, and the - * level entered by leave() contains only one item, automatically - * goes up further until a level with more than one item is reached. - * \return returns false if there is no further upper level - */ - void leave_all (); - -//! \brief the current level in the tree - unsigned int level () const - { - return m_level; - } - - //! \brief true if the selection holds no items - bool empty(); - -/*! \brief returns detailed info about all selected tracks. - * The ordering is done only by the keyfield of the current level. - * This might have to be changed - suborder by keyfields of detail - * levels. This list is cached so several consequent calls mean no - * loss of performance. See value(), the same warning applies. - * \todo call this more seldom. See getNumTracks() - */ - const vector < mgContentItem > &tracks () const; - -/*! \brief returns an item from the tracks() list - * \param position the position in the tracks() list - * \return returns NULL if position is out of range - */ - mgContentItem* getTrack (unsigned int position); - -/*! \brief returns the current item from the tracks() list - */ - mgContentItem* getCurrentTrack () - { - return getTrack (gotoTrackPosition()); - } - -/*! \brief toggles the shuffle mode thru all possible values. - * When a shuffle modus SM_NORMAL or SM_PARTY is selected, the - * order of the tracks in the track list will be randomly changed. - */ - ShuffleMode toggleShuffleMode (); - -//! \brief toggles the loop mode thru all possible values - LoopMode toggleLoopMode (); - -//! \brief returns the current shuffle mode - ShuffleMode getShuffleMode () const - { - return m_shuffle_mode; - } - -//! \brief sets the current shuffle mode - void setShuffleMode (const ShuffleMode shuffle_mode); - -//! \brief returns the current loop mode - LoopMode getLoopMode () const - { - return m_loop_mode; - } - -//! \brief sets the current loop mode - void setLoopMode (const LoopMode loop_mode) - { - m_loop_mode = loop_mode; - } - -/*! \brief adds the whole current track list to a collection - * \param Name the name of the collection. If it does not yet exist, - * it will be created. - */ - unsigned int AddToCollection (const string Name); - -/*! \brief removes the whole current track from a the collection - * Remember - this selection can be configured to hold exactly - * one list, so this command can be used to clear a selected list. - * \param Name the name of the collection - */ - unsigned int RemoveFromCollection (const string Name); -//! \brief delete a collection - bool DeleteCollection (const string Name); -/*! \brief create a collection only if it does not yet exist. - * \return true only if it has been created. false if it already existed. - */ - bool CreateCollection(const string Name); - -//! \brief remove all items from the collection - void ClearCollection (const string Name); - -/*! generates an m3u file containing all tracks. The directory - * can be indicated by SetDirectory(). - * The file name will be built from the list name, slashes - * and spaces converted - */ - string exportM3U (); - - /*! import/export tags like - * \par path can be a file or a directory. If directory, - * sync all files within but by default non recursive - * \par recursive recurse into all directories beneath path - * \par assorted see mugglei -h - * \par delete_missing if the file does not exist, delete the - * data base entry. If the file is unreadable, do not delete. - */ - void Sync(string path, bool recursive=false,bool assorted=false,bool delete_missing=false); - -/*! \brief go to a position in the current level. If we are at the - * most detailled level this also sets the track position since - * they are identical. - * \param position the wanted position. If it is too big, go to the - * last existing position - * \return only if no position exists, false will be returned - */ - void setPosition (unsigned int position); - -/*! \brief go to the position with value in the current level - * \param value the value of the wanted position - */ - void setPosition (const string value) - { - setPosition (valindex (value)); - } - -/*! \brief go to a position in the track list - * \param position the wanted position. If it is too big, go to the - * last existing position - * \return only if no position exists, false will be returned - */ - void setTrackPosition (unsigned int position) const; - -/*! \brief skip some tracks in the track list - * \return false if new position does not exist - */ - bool skipTracks (int step=1); - -/*! \brief skip forward by 1 in the track list - * \return false if new position does not exist - */ - bool skipFwd () - { - return skipTracks (+1); - } - -/*! \brief skip back by 1 in the track list - * \return false if new position does not exist - */ - bool skipBack () - { - return skipTracks (-1); - } - -//! \brief returns the sum of the durations of all tracks - unsigned long getLength (); - -/*! \brief returns the sum of the durations of completed tracks - * those are tracks before the current track position - */ - unsigned long getCompletedLength () const; - -/*! returns the number of tracks in the track list - * \todo should not call tracks () which loads all track info. - * instead, only count the tracks. If the size differs from - * m_tracks.size(), invalidate m_tracks - */ - unsigned int getNumTracks () const - { - return tracks ().size (); - } - -//! sets the directory for the storage of m3u file - void SetDirectory (const string directory) - { - m_Directory = directory; - } - -/*! returns the name of the current play list. If no play list is active, - * the name is built from the name of the key fields. - */ - string getListname () const; - -/*! \brief true if this selection currently selects a list of collections - */ - bool isCollectionlist () const; - -/*! \brief true if this selection currently selects a list of languages - */ - bool isLanguagelist () const; - - //! \brief true if we have entered a collection - bool inCollection(const string Name="") const; - - /*! \brief dumps the entire state of this selection into a map, - * \param nv the values will be entered into this map - */ - void DumpState(mgValmap& nv) const; - - /*! \brief creates a new selection using saved definitions - * \param nv this map contains the saved definitions - */ - mgSelection(mgValmap& nv); - - //! \brief clear the cache, next access will reload from data base - void clearCache() const; - - void refreshValues() const; - - //! \brief true if values and tracks need to be reloaded - bool cacheIsEmpty() const - { - return (m_current_values=="" && m_current_tracks==""); - } - string value(mgKeyTypes kt, string id) const; - string value(mgKey* k, string id) const; - string value(mgKey* k) const; - string id(mgKeyTypes kt, string val) const; - string id(mgKey* k, string val) const; - string id(mgKey* k) const; - unsigned int keycount(mgKeyTypes kt); - vector choices(mgOrder *o,unsigned int level, unsigned int *current); - unsigned int valcount (string val); - - private: - mutable map > map_values; - mutable map > map_ids; - mutable string m_current_values; - mutable string m_current_tracks; -//! \brief be careful when accessing this, see mgSelection::tracks() - mutable vector < mgContentItem > m_tracks; - mutable strvector m_ids; - mutable vector < unsigned int > m_counts; - //! \brief initializes maps for id/value mapping in both direction - bool loadvalues (mgKeyTypes kt) const; - bool m_fall_through; - unsigned int m_position; - mutable unsigned int m_tracks_position; - ShuffleMode m_shuffle_mode; - void Shuffle() const; - LoopMode m_loop_mode; - MYSQL *m_db; - void setDB(MYSQL *db); - unsigned int m_level; - long m_trackid; - - mgOrder order; - bool UsedBefore (mgOrder *o,const mgKeyTypes kt, unsigned int level) const; - void InitSelection (); - void Connect (); - /*! \brief returns the SQL command for getting all values. - * For the leaf level, all values are returned. For upper - * levels, every distinct value is returned only once. - * This must be so for the leaf level because otherwise - * the value() entries do not correspond to the track() - * entries and the wrong tracks might be played. - */ - string sql_values (); - unsigned int valindex (const string val,const bool second_try=false) const; - unsigned int idindex (const string val,const bool second_try=false) const; - string ListFilename (); - string m_Directory; - void loadgenres (); - MYSQL_RES * exec_sql(string query) const; - string get_col0 (string query) const; - - void InitFrom(const mgSelection* s); - -/*! \brief executes a query and returns the integer value from - * the first column in the first row. The query shold be a COUNT query - * returning only one row. - * \param query the SQL query to be executed - */ - unsigned long exec_count (string query) const; - - -}; - - -unsigned int randrange (const unsigned int high); - - -#endif // _DB_H diff --git a/mg_selection.c b/mg_selection.c new file mode 100644 index 0000000..3bb79e1 --- /dev/null +++ b/mg_selection.c @@ -0,0 +1,1224 @@ +/*! + * \file mg_selection.c + * \brief A general interface to data items, currently only GiantDisc + * + * \version $Revision: 1.0 $ + * \date $Date: 2004-12-07 10:10:35 +0200 (Tue, 07 Dec 2004) $ + * \author Wolfgang Rohdewald + * \author Responsible author: $Author: wr $ + * + */ + +#include +#include "mg_selection.h" +#include "vdr_setup.h" +#include "mg_tools.h" + +bool needGenre2; +bool needGenre2_set; + +//! \brief adds string n to string s, using a comma to separate them +static string comma (string & s, string n); + +/*! \brief returns a random integer within some range + */ +unsigned int +randrange (const unsigned int high) +{ + unsigned int result=0; + result = random () % high; + return result; +} + + +static string +comma (string & s, string n) +{ + return addsep (s, ",", n); +} + + +static string zerostring; + +bool +mgSelection::mgSelStrings::operator==(const mgSelStrings&x) const +{ + bool result = strings.size()==x.strings.size(); + if (result) + for (unsigned int i=0;irefreshValues(); + return strings.size(); +} + +string& +mgSelection::mgSelStrings::operator[](unsigned int idx) +{ + if (!m_sel) + mgError("mgSelStrings: m_sel is 0"); + m_sel->refreshValues(); + if (idx>=strings.size()) return zerostring; + return strings[idx]; +} + +void +mgSelection::mgSelStrings::setOwner(mgSelection* sel) +{ + m_sel = sel; +} + +void +mgSelection::clearCache() const +{ + m_current_values = ""; + m_current_tracks = ""; +} + +string +mgSelection::getCurrentValue() +{ + return values[gotoPosition()]; +} + +string +mgSelection::getKeyValue(const unsigned int level) const +{ + return order.getKeyValue(level); +} + +unsigned int +mgSelection::getKeyIndex(const unsigned int level) const +{ + return valindex(getKeyValue(level)); +} + + +mgKeyTypes +mgSelection::getKeyType (const unsigned int level) const +{ + return order.getKeyType(level); +} + +MYSQL_RES* +mgSelection::exec_sql(string query) const +{ + return ::exec_sql(m_db, query); +} + +/*! \brief executes a query and returns the first columnu of the + * first row. + * \param query the SQL query string to be executed + */ +string mgSelection::get_col0 (string query) const +{ + return ::get_col0(m_db, query); +} + + +unsigned long +mgSelection::exec_count (string query) const +{ + return ::exec_count(m_db, query); +} + + + +string +mgSelection::sql_string (const string s) const +{ + char *buf = (char *) malloc (s.size () * 2 + 1); + mysql_real_escape_string (m_db, buf, s.c_str (), s.size ()); + string result = "'" + std::string (buf) + "'"; + free (buf); + return result; +} + + +mgContentItem * +mgSelection::getTrack (unsigned int position) +{ + if (position >= getNumTracks ()) + return 0; + return &(m_tracks[position]); +} + + +mgSelection::ShuffleMode mgSelection::toggleShuffleMode () +{ + setShuffleMode((m_shuffle_mode == SM_PARTY) ? SM_NONE : ShuffleMode (m_shuffle_mode + 1)); + Shuffle(); + return getShuffleMode(); +} + +void +mgSelection::setShuffleMode (mgSelection::ShuffleMode mode) +{ + m_shuffle_mode = mode; +} + +void +mgSelection::Shuffle() const +{ + unsigned int tracksize = getNumTracks(); + if (tracksize==0) return; + switch (m_shuffle_mode) + { + case SM_NONE: + { + long id = m_tracks[getTrackPosition()].getId (); + m_current_tracks = ""; // force a reload + tracksize = getNumTracks(); // getNumTracks also reloads + for (unsigned int i = 0; i < tracksize; i++) + if (m_tracks[i].getId () == id) + { + setTrackPosition(i); + break; + } + } + break; + case SM_PARTY: + case SM_NORMAL: + { + // play all, beginning with current track: + mgContentItem tmp = m_tracks[getTrackPosition()]; + m_tracks[getTrackPosition()]=m_tracks[0]; + m_tracks[0]=tmp; + setTrackPosition(0); + // randomize all other tracks + for (unsigned int i = 1; i < tracksize; i++) + { + unsigned int j = 1+randrange (tracksize-1); + tmp = m_tracks[i]; + m_tracks[i] = m_tracks[j]; + m_tracks[j] = tmp; + } + } break; +/* + * das kapiere ich nicht... (wolfgang) + - Party mode (see iTunes) + - initialization + - find 15 titles according to the scheme below + - playing + - before entering next title perform track selection + - track selection + - generate a random uid + - if file exists: + - determine maximum playcount of all tracks +- generate a random number n +- if n < playcount / max. playcount +- add the file to the end of the list +*/ + } +} + + +mgSelection::LoopMode mgSelection::toggleLoopMode () +{ + m_loop_mode = (m_loop_mode == LM_FULL) ? LM_NONE : LoopMode (m_loop_mode + 1); + return m_loop_mode; +} + + +unsigned int +mgSelection::AddToCollection (const string Name) +{ + if (!m_db) return 0; + CreateCollection(Name); + string listid = sql_string (get_col0 + ("SELECT id FROM playlist WHERE title=" + sql_string (Name))); + unsigned int tracksize = getNumTracks (); + + // this code is rather complicated but works in a multi user + // environment: + + // insert a unique trackid: + string trackid = ltos(mysql_thread_id(m_db)+1000000); + exec_sql("INSERT INTO playlistitem SELECT "+listid+"," + "MAX(tracknumber)+"+ltos(tracksize)+","+trackid+ + " FROM playlistitem WHERE playlist="+listid); + + // find tracknumber of the trackid we just inserted: + string sql = string("SELECT tracknumber FROM playlistitem WHERE " + "playlist=")+listid+" AND trackid="+trackid; + long first = atol(get_col0(sql).c_str()) - tracksize + 1; + + // replace the place holder trackid by the correct value: + exec_sql("UPDATE playlistitem SET trackid="+ltos(m_tracks[tracksize-1].getId())+ + " WHERE playlist="+listid+" AND trackid="+trackid); + + // insert all other tracks: + const char *sql_prefix = "INSERT INTO playlistitem VALUES "; + sql = ""; + for (unsigned int i = 0; i < tracksize-1; i++) + { + string item = "(" + listid + "," + ltos (first + i) + "," + + ltos (m_tracks[i].getId ()) + ")"; + comma(sql, item); + if ((i%100)==99) + { + exec_sql (sql_prefix+sql); + sql = ""; + } + } + if (!sql.empty()) exec_sql (sql_prefix+sql); + if (inCollection(Name)) clearCache (); + return tracksize; +} + + +unsigned int +mgSelection::RemoveFromCollection (const string Name) +{ + if (!m_db) return 0; + mgParts p = order.Parts(m_db,m_level,false); + string sql = p.sql_delete_from_collection(id(keyCollection,Name)); + exec_sql (sql); + unsigned int removed = mysql_affected_rows (m_db); + if (inCollection(Name)) clearCache (); + return removed; +} + + +bool mgSelection::DeleteCollection (const string Name) +{ + if (!m_db) return false; + ClearCollection(Name); + exec_sql ("DELETE FROM playlist WHERE title=" + sql_string (Name)); + if (isCollectionlist()) clearCache (); + return (mysql_affected_rows (m_db) == 1); +} + + +void mgSelection::ClearCollection (const string Name) +{ + if (!m_db) return; + string listid = id(keyCollection,Name); + exec_sql ("DELETE FROM playlistitem WHERE playlist="+sql_string(listid)); + if (inCollection(Name)) clearCache (); +} + + +bool mgSelection::CreateCollection(const string Name) +{ + if (!m_db) return false; + string name = sql_string(Name); + if (exec_count("SELECT count(title) FROM playlist WHERE title = " + name)>0) + return false; + exec_sql ("INSERT playlist VALUES(" + name + ",'VDR',NULL,NULL,NULL)"); + if (isCollectionlist()) clearCache (); + return true; +} + + +string mgSelection::exportM3U () +{ + +// open a file for writing + string fn = "/tmp/" + ListFilename () + ".m3u"; + FILE * listfile = fopen (fn.c_str (), "w"); + if (!listfile) + return ""; + fprintf (listfile, "#EXTM3U\n"); + unsigned int tracksize = getNumTracks (); + for (unsigned i = 0; i < tracksize; i++) + { + mgContentItem& t = m_tracks[i]; + fprintf (listfile, "#EXTINF:%d,%s\n", t.getDuration (), + t.getTitle ().c_str ()); + fprintf (listfile, "#MUGGLE:%ld\n", t.getId()); + fprintf (listfile, "%s\n", t.getSourceFile (false).c_str ()); + } + fclose (listfile); + return fn; +} + +bool +mgSelection::empty() +{ + if (m_level>= order.size ()-1) + return ( getNumTracks () == 0); + else + return ( values.size () == 0); +} + +void +mgSelection::setPosition (unsigned int position) +{ + if (m_level == order.size()) + setTrackPosition(position); + else + m_position = position; +} + +void +mgSelection::setTrackPosition (unsigned int position) const +{ + m_tracks_position = position; +} + +unsigned int +mgSelection::getPosition () const +{ + if (m_level == order.size()) + return getTrackPosition(); + else + return m_position; +} + +unsigned int +mgSelection::gotoPosition () +{ + if (m_level == order.size ()) + return gotoTrackPosition(); + else + { + unsigned int valsize = values.size(); + if (valsize==0) + m_position = 0; + else if (m_position >= valsize) + m_position = valsize -1; + return m_position; + } +} + +unsigned int +mgSelection::getTrackPosition() const +{ + if (m_tracks_position>=m_tracks.size()) + if (m_tracks.size()==0) + m_tracks_position=0; + else + m_tracks_position = m_tracks.size()-1; + return m_tracks_position; +} + +unsigned int +mgSelection::gotoTrackPosition() +{ + unsigned int tracksize = getNumTracks (); + if (tracksize == 0) + setTrackPosition(0); + else if (m_tracks_position >= tracksize) + setTrackPosition(tracksize -1); + return m_tracks_position; +} + +bool mgSelection::skipTracks (int steps) +{ + unsigned int tracksize = getNumTracks(); + if (tracksize == 0) + return false; + if (m_loop_mode == LM_SINGLE) + return true; + unsigned int old_pos = m_tracks_position; + unsigned int new_pos; + if (old_pos + steps < 0) + { + if (m_loop_mode == LM_NONE) + return false; + new_pos = tracksize - 1; + } + else + new_pos = old_pos + steps; + if (new_pos >= tracksize) + { + if (m_loop_mode == LM_NONE) + return false; + new_pos = 0; + } + setTrackPosition (new_pos); + return (new_pos == gotoTrackPosition()); +} + + +unsigned long +mgSelection::getLength () +{ + unsigned long result = 0; + unsigned int tracksize = getNumTracks (); + for (unsigned int i = 0; i < tracksize; i++) + result += m_tracks[i].getDuration (); + return result; +} + + +unsigned long +mgSelection::getCompletedLength () const +{ + unsigned long result = 0; + tracks (); // make sure they are loaded + for (unsigned int i = 0; i < getTrackPosition(); i++) + result += m_tracks[i].getDuration (); + return result; +} + + + +string mgSelection::getListname () const +{ + list st; + for (unsigned int i = 0; i < m_level; i++) + st.push_back(order.getKeyValue(i)); + st.unique(); + string result=""; + for (list < string >::iterator it = st.begin (); it != st.end (); ++it) + { + addsep (result, ":", *it); + } + if (result.empty ()) + if (order.size()>0) + result = string(ktName(order.getKeyType(0))); + return result; +} + +string mgSelection::ListFilename () +{ + string res = getListname (); + // convert char set ? + string::iterator it; + for (it=res.begin();it!=res.end();it++) + { + char& c = *it; + switch (c) + { + case '\'': + case '/': + case '\\': + case ' ': + case ')': + case '(': c = '_';break; + } + } + return res; +} + +const vector < mgContentItem > & +mgSelection::tracks () const +{ + if (!m_db) return m_tracks; + if (!m_current_tracks.empty()) return m_tracks; + mgParts p = order.Parts(m_db,m_level); + p.fields.clear(); + p.fields.push_back("tracks.id"); + p.fields.push_back("tracks.title"); + p.fields.push_back("tracks.mp3file"); + p.fields.push_back("tracks.artist"); + p.fields.push_back("album.title"); + p.fields.push_back("tracks.genre1"); + p.fields.push_back("tracks.genre2"); + p.fields.push_back("tracks.bitrate"); + p.fields.push_back("tracks.year"); + p.fields.push_back("tracks.rating"); + p.fields.push_back("tracks.length"); + p.fields.push_back("tracks.samplerate"); + p.fields.push_back("tracks.channels"); + p.tables.push_back("tracks"); + p.tables.push_back("album"); + for (unsigned int i = m_level; iParts(m_db,true); + m_current_tracks = p.sql_select(false); + m_tracks.clear (); + MYSQL_RES *rows = exec_sql (m_current_tracks); + if (rows) + { + MYSQL_ROW row; + while ((row = mysql_fetch_row (rows)) != 0) + m_tracks.push_back (mgContentItem (this,row)); + mysql_free_result (rows); + } + if (m_shuffle_mode!=SM_NONE) + Shuffle(); + return m_tracks; +} + + +void mgSelection::InitSelection() { + setDB(0); + m_Directory="."; + m_level = 0; + m_position = 0; + m_tracks_position = 0; + m_trackid = -1; + if (the_setup.InitShuffleMode) + m_shuffle_mode = SM_NORMAL; + else + m_shuffle_mode = SM_NONE; + if (the_setup.InitLoopMode) + m_loop_mode = LM_FULL; + else + m_loop_mode = LM_NONE; + clearCache(); + values.setOwner(this); +} + + +mgSelection::mgSelection (const bool fall_through) +{ + InitSelection (); + Connect(); + m_fall_through = fall_through; +} + +mgSelection::mgSelection (const mgSelection &s) +{ + InitFrom(&s); +} + +mgSelection::mgSelection (const mgSelection* s) +{ + InitFrom(s); +} + +mgSelection::mgSelection (mgValmap& nv) +{ + InitFrom(nv); +} + +void +mgSelection::setDB(MYSQL *db) +{ + m_db = db; +} + +void +mgSelection::setOrder(mgOrder* o) +{ + if (o) + { + order = *o; + } + else + mgWarning("mgSelection::setOrder(0)"); +} + +void +mgSelection::InitFrom(mgValmap& nv) +{ + InitSelection(); + Connect(); + m_fall_through = nv.getbool("FallThrough"); + m_Directory = nv.getstr("Directory"); + while (m_level < nv.getuint("Level")) + { + char *idx; + asprintf(&idx,"order.Keys.%u.Position",m_level); + string newval = nv.getstr(idx); + free(idx); + if (!enter (newval)) + if (!select (newval)) break; + } + m_trackid = nv.getlong("TrackId"); + setPosition(nv.getstr("Position")); + if (m_level>=order.size()-1) + setTrackPosition(nv.getlong("TrackPosition")); +} + + +mgSelection::~mgSelection () +{ + if (m_db) + mysql_close (m_db); +} + +void mgSelection::InitFrom(const mgSelection* s) +{ + InitSelection(); + m_fall_through = s->m_fall_through; + m_Directory = s->m_Directory; + map_values = s->map_values; + map_ids = s->map_ids; + order = s->order; + m_level = s->m_level; + m_position = s->m_position; + m_trackid = s->m_trackid; + m_tracks_position = s->m_tracks_position; + Connect(); + setShuffleMode (s->getShuffleMode ()); + setLoopMode (s->getLoopMode ()); +} + +unsigned int +mgSelection::ordersize () +{ + return order.size (); +} + +unsigned int +mgSelection::valindex (const string val,const bool second_try) const +{ + for (unsigned int i = 0; i < values.size (); i++) + { + if (values[i] == val) + return i; + } + // nochmal mit neuen Werten: + clearCache(); + refreshValues(); + if (second_try) { + esyslog("valindex: Gibt es nicht:%s",val.c_str()); + return 0; + } + else + return valindex(val,true); +} + +unsigned int +mgSelection::valcount (string value) +{ + assert(m_counts.size()==values.size()); + return m_counts[valindex(value)]; +} + +unsigned int +mgSelection::idindex (const string id,const bool second_try) const +{ + for (unsigned int i = 0; i < m_ids.size (); i++) + if (m_ids[i] == id) + return i; + // nochmal mit neuen Werten: + clearCache(); + refreshValues(); + if (second_try) { + mgWarning("idindex: Gibt es nicht:%s",id.c_str()); + return 0; + } + else + return idindex(id,true); +} + + +void +mgSelection::refreshValues () const +{ + assert(this); + if (!m_db) + return; + if (m_current_values.empty()) + { + mgParts p = order.Parts(m_db,m_level); + m_current_values = p.sql_select(); + values.strings.clear (); + m_ids.clear (); + m_counts.clear(); + MYSQL_RES *rows = exec_sql (m_current_values); + if (rows) + { + unsigned int num_fields = mysql_num_fields(rows); + MYSQL_ROW row; + while ((row = mysql_fetch_row (rows)) != 0) + { + if (!row[0]) continue; + string r0 = row[0]; + if (r0=="NULL") // there is a genre NULL! + continue; + if (num_fields==3) + { + if (!row[1]) continue; + string r1 = row[1]; + values.strings.push_back (r0); + m_ids.push_back (r1); + m_counts.push_back(atol(row[2])); + } + else + { + values.strings.push_back (value(order.Key(m_level),r0)); + m_ids.push_back (r0); + m_counts.push_back(atol(row[1])); + } + } + mysql_free_result (rows); + } + } +} + +unsigned int +mgSelection::count () const +{ + return values.size (); +} +void +mgSelection::Connect () +{ + if (m_db) + { + mysql_close (m_db); + setDB(0); + } + if (the_setup.DbHost == "") return; + setDB(mysql_init (0)); + if (!m_db) + return; + bool success; + if (the_setup.DbSocket != NULL) + { + mgDebug(1,"Using socket %s for connecting to Database %s as user %s.", + the_setup.DbSocket, + the_setup.DbName, + the_setup.DbUser); + mgDebug(3,"DbPassword is: '%s'",the_setup.DbPass); + success = (mysql_real_connect( m_db, + "", + the_setup.DbUser, + the_setup.DbPass, + the_setup.DbName, + 0, + the_setup.DbSocket, 0 ) != 0 ); + } + else + { + mgDebug(1,"Using TCP-%s for connecting to Database %s as user %s.", + the_setup.DbHost, + the_setup.DbName, + the_setup.DbUser); + mgDebug(3,"DbPassword is: '%s'",the_setup.DbPass); + success = ( mysql_real_connect( m_db, + the_setup.DbHost, + the_setup.DbUser, + the_setup.DbPass, + the_setup.DbName, + the_setup.DbPort, + 0, 0 ) != 0 ); + } + if (!success) + { + mgWarning("Failed to connect to host '%s' as User '%s', Password '%s': Error: %s", + the_setup.DbHost,the_setup.DbUser,the_setup.DbPass,mysql_error(m_db)); + mysql_close (m_db); + setDB(0); + } + if (!needGenre2_set && m_db) + { + needGenre2_set=true; + needGenre2=exec_count("SELECT COUNT(DISTINCT genre2) from tracks")>1; + } + return; +} + + +bool mgSelection::enter (unsigned int position) +{ + if (order.empty()) + esyslog("mgSelection::enter(%u): order is empty", position); + if (empty()) + return false; + setPosition (position); + position = gotoPosition(); // reload adjusted position + if (values.size()==0) + return false; + string value = values[position]; + string id = m_ids[position]; + mgSelStrings prev; + if (m_fall_through && values.size()<10) + prev=values; + while (1) + { + if (m_level >= order.size () - 1) + return false; + order[m_level++]->set (value,id); + clearCache(); + if (m_level >= order.size()) + mgError("mgSelection::enter(%u): level greater than order.size() %u", + m_level,order.size()); + m_position = 0; + refreshValues(); + if (count()==0) + break; + value = values[0]; + id = m_ids[0]; + if (!m_fall_through) + break; + if (m_level==order.size()-1) + break; + if (count () > 1 && !(prev==values)) + break; + } + return true; +} + + +bool mgSelection::select (unsigned int position) +{ + if (m_level == order.size () - 1) + { + if (getNumTracks () <= position) + return false; + order[m_level]->set (values[position],m_ids[position]); + m_level++; + m_trackid = m_tracks[position].getId (); + + clearCache(); + return true; + } + else + return enter (position); +} + +bool +mgSelection::leave () +{ + string prevvalue; + if (order.empty()) + { + mgWarning("mgSelection::leave(): order is empty"); + return false; + } + if (m_level == order.size ()) + { + m_level--; + prevvalue=order.getKeyValue(m_level); + order[m_level]->set("",EMPTY); + m_trackid = -1; + clearCache(); + setPosition(prevvalue); + return true; + } + mgSelStrings prev; + if (m_fall_through && values.size()<10) + prev=values; + while (1) + { + if (m_level < 1) + return false; + if (m_levelset ("",EMPTY); + m_level--; + prevvalue=order.getKeyValue(m_level); + if (m_levelset ("",EMPTY); + clearCache(); + if (!m_fall_through) + break; + if (count () > 1 && !(prev==values)) + break; + } + setPosition(prevvalue); + return true; +} + +void +mgSelection::leave_all () +{ + m_level=0; + for (unsigned int i=0;iset ("",EMPTY); + clearCache(); +} + +void +mgSelection::selectfrom(mgOrder& oldorder,mgContentItem* o) +{ + leave_all(); + string selval; + string selid; + assert(m_level==0); + for (unsigned int idx = 0; idx < ordersize(); idx++) + { + selval = EMPTY; + selid = EMPTY; + mgKeyTypes new_kt = getKeyType(idx); + for (unsigned int i=0;inew_kt + && iskeyGenre(old_kt) + && iskeyGenre(new_kt)) + { + selid = id(new_kt,value(new_kt,oldorder.getKeyId(i))); + selval= value(new_kt,selid); + } + if (selid!=EMPTY) break; + } + if (selid==EMPTY && o && o->getId()>=0) + { + selval = o->getKeyValue(new_kt); + selid = o->getKeyId(new_kt); + } + if (selid==EMPTY) + break; + if (m_levelset (selval, selid); + } + else + { + setPosition(selval); + return; + } + } + if (m_level>0) + { + m_level--; + selval = order.getKeyValue(m_level); + order[m_level]->set("",EMPTY); + setPosition(selval); + order[m_level+1]->set("",EMPTY); + } + assert(m_level& valmap = map_values[kt]; + map::iterator it; + it = valmap.find(id); + if (it!=valmap.end()) + { + string r = it->second; + if (!r.empty()) + return r; + } + map_ids[kt].clear(); + loadvalues(kt); + it = valmap.find(id); + if (it!=valmap.end()) + return valmap[id]; + } + return id; +} + +string +mgSelection::value(mgKey* k, string id) const +{ + return value(k->Type(),id); +} + +string +mgSelection::value(mgKey* k) const +{ + return value(k,k->id()); +} + +string +mgSelection::id(mgKeyTypes kt, string val) const +{ + if (loadvalues (kt)) + { + map& idmap = map_ids[kt]; + string v = idmap[val]; + if (kt==keyGenre1) v=v.substr(0,1); + if (kt==keyGenre2) v=v.substr(0,2); + if (kt==keyGenre3) v=v.substr(0,3); + return v; + } + else + return val; +} + +string +mgSelection::id(mgKey* k, string val) const +{ + return id(k->Type(),val); +} + +string +mgSelection::id(mgKey* k) const +{ + return k->id(); +} + +bool +mgSelection::UsedBefore(mgOrder *o,const mgKeyTypes kt,unsigned int level) const +{ + if (level>=o->size()) + level = o->size() -1; + for (unsigned int lx = 0; lx < level; lx++) + if (o->getKeyType(lx)==kt) + return true; + return false; +} + +bool mgSelection::isLanguagelist() const +{ + return (order.getKeyType(0) == keyLanguage); +} + +bool mgSelection::isCollectionlist () const +{ + if (order.size()==0) return false; + return (order.getKeyType(0) == keyCollection && m_level == 0); +} + +bool +mgSelection::inCollection(const string Name) const +{ + if (order.size()==0) return false; + bool result = (order.getKeyType(0) == keyCollection && m_level == 1); + if (result) + if (order.getKeyType(1) != keyCollectionItem) + mgError("inCollection: key[1] is not keyCollectionItem"); + if (!Name.empty()) + result &= (order.getKeyValue(0) == Name); + return result; +} + + +void mgSelection::DumpState(mgValmap& nv) const +{ + nv.put("FallThrough",m_fall_through); + nv.put("Directory",m_Directory); + nv.put("Level",int(m_level)); + for (unsigned int i=0;i=order.size()-1) + nv.put("TrackPosition",getTrackPosition()); +} + +map +mgSelection::UsedKeyValues() +{ + map result; + for (unsigned int idx = 0 ; idx < level() ; idx++) + { + result[order.getKeyType(idx)] = order.getKeyValue(idx); + } + if (level() < order.size()-1) + { + mgKeyTypes ch = order.getKeyType(level()); + result[ch] = getCurrentValue(); + } + return result; +} + +bool +mgSelection::loadvalues (mgKeyTypes kt) const +{ + if (map_ids.count(kt)>0) + return true; + map& idmap = map_ids[kt]; + mgKey* k = ktGenerate(kt); + if (k->map_idfield().empty()) + { + delete k; + return false; + } + map& valmap = map_values[kt]; + char *b; + asprintf(&b,"select %s,%s from %s;",k->map_idfield().c_str(),k->map_valuefield().c_str(),k->map_valuetable().c_str()); + MYSQL_RES *rows = exec_sql (string(b)); + free(b); + if (rows) + { + MYSQL_ROW row; + while ((row = mysql_fetch_row (rows)) != 0) + { + if (row[0] && row[1]) + { + valmap[row[0]] = row[1]; + idmap[row[1]] = row[0]; + } + } + mysql_free_result (rows); + } + delete k; + return true; +} + +static vector keycounts; + +unsigned int +mgSelection::keycount(mgKeyTypes kt) +{ + assert(strlen(m_db->host)); + if (keycounts.size()==0) + { + for (unsigned int ki=int(mgKeyTypesLow);ki<=int(mgKeyTypesHigh);ki++) + { + keycounts.push_back(-1); + } + } + int& count = keycounts[int(kt-mgKeyTypesLow)]; + if (count==-1) + { + mgKey* k = ktGenerate(kt); + if (k->Enabled(m_db)) + count = exec_count(k->Parts(m_db,true).sql_count()); + else + count = 0; + delete k; + } + return count; +} + + +vector +mgSelection::choices(mgOrder *o,unsigned int level, unsigned int *current) +{ + vector result; + if (level>o->size()) + { + *current = 0; + return result; + } + for (unsigned int ki=int(mgKeyTypesLow);ki<=int(mgKeyTypesHigh);ki++) + { + mgKeyTypes kt = mgKeyTypes(ki); + if (kt==o->getKeyType(level)) + { + *current = result.size(); + result.push_back(ktName(kt)); + continue; + } + if (UsedBefore(o,kt,level)) + continue; + if (kt==keyDecade && UsedBefore(o,keyYear,level)) + continue; + if (kt==keyGenre1) + { + if (UsedBefore(o,keyGenre2,level)) continue; + if (UsedBefore(o,keyGenre3,level)) continue; + if (UsedBefore(o,keyGenres,level)) continue; + } + if (kt==keyGenre2) + { + if (UsedBefore(o,keyGenre3,level)) continue; + if (UsedBefore(o,keyGenres,level)) continue; + } + if (kt==keyGenre3) + { + if (UsedBefore(o,keyGenres,level)) continue; + } + if (kt==keyFolder1) + { + if (UsedBefore(o,keyFolder2,level)) continue; + if (UsedBefore(o,keyFolder3,level)) continue; + if (UsedBefore(o,keyFolder4,level)) continue; + } + if (kt==keyFolder2) + { + if (UsedBefore(o,keyFolder3,level)) continue; + if (UsedBefore(o,keyFolder4,level)) continue; + } + if (kt==keyFolder3) + { + if (UsedBefore(o,keyFolder4,level)) continue; + } + if (kt==keyCollection || kt==keyCollectionItem) + result.push_back(ktName(kt)); + else if (keycount(kt)>1) + result.push_back(ktName(kt)); + } + return result; +} diff --git a/mg_selection.h b/mg_selection.h new file mode 100644 index 0000000..4e10644 --- /dev/null +++ b/mg_selection.h @@ -0,0 +1,493 @@ +/*! + * \file mg_selection.h + * \brief A general interface to data items, currently only GiantDisc + * + * \version $Revision: 1.0 $ + * \date $Date: 2004-12-07 10:10:35 +0200 (Tue, 07 Dec 2004) $ + * \author Wolfgang Rohdewald + * \author Responsible author: $Author: wr $ + * + */ + +#ifndef _MG_SELECTION_H +#define _MG_SELECTION_H +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +#include "mg_tools.h" +#include "mg_valmap.h" +#include "mg_order.h" +#include "mg_content.h" + +typedef vector strvector; + + +/*! + * \brief the only interface to the database. + * Some member functions are declared const although they can modify the inner state of mgSelection. + * But they only modify variables used for caching. With const, we want to express + * the logical constness. E.g. the selected tracks can change without breaking constness: + * The selection never defines concrete tracks but only how to choose them. + */ +class mgSelection +{ + private: + class mgSelStrings + { + friend class mgSelection; + private: + strvector strings; + mgSelection* m_sel; + void setOwner(mgSelection* sel); + public: + string& operator[](unsigned int idx); + bool operator==(const mgSelStrings&x) const; + size_t size() const; + }; + public: +//! \brief defines an order to be used + void setOrder(mgOrder *o); + + mgOrder& getOrder() { return order; } + +/*! \brief define various ways to play music in random order + * \todo Party mode is not implemented, does same as SM_NORMAL + */ + enum ShuffleMode + { + SM_NONE, //!< \brief play normal sequence + SM_NORMAL, //!< \brief a shuffle with a fair distribution + SM_PARTY //!< \brief select the next few songs randomly, continue forever + }; + +//! \brief define various ways to play music in a neverending loop + enum LoopMode + { + LM_NONE, //!< \brief do not loop + LM_SINGLE, //!< \brief loop a single track + LM_FULL //!< \brief loop the whole track list + }; + +//! \brief escapes special characters + string sql_string(const string s) const; + +/*! \brief the main constructor + * \param fall_through if TRUE: If enter() returns a choice + * containing only one item, that item is automatically entered. + * The analog happens with leave() + */ + mgSelection ( const bool fall_through = false); + +/*! \brief a copy constructor. Does a deep copy. + * Some of the data base content will only be retrieved by the + * new mgSelection as needed, so some data base + * overhead is involved + */ + mgSelection (const mgSelection& s); +/*! \brief a copy constructor. Does a deep copy. + * Some of the data base content will only be retrieved by the + * new mgSelection as needed, so some data base + * overhead is involved + */ + mgSelection (const mgSelection* s); + + +//! \brief initializes from a map. + void InitFrom(mgValmap& nv); + +//! \brief the normal destructor + ~mgSelection (); + +/*! \brief represents all values for the current level. The result + * is cached in values, subsequent accesses to values only incur a + * small overhead for building the SQL WHERE command. The values will + * be reloaded when the SQL command changes + */ + mutable mgSelStrings values; + +/*! \brief returns the name of a key + */ + mgKeyTypes getKeyType (const unsigned int level) const; + +//! \brief return the current value of this key + string getKeyValue (const unsigned int level) const; + unsigned int getKeyIndex(const unsigned int level) const; + +/*! \brief returns the current item from the value() list + */ + string getCurrentValue(); + +//! \brief returns a map (new allocated) for all used key fields and their values + map UsedKeyValues(); + +//! \brief the number of key fields used for the query + unsigned int ordersize (); + +//! \brief the number of music items currently selected + unsigned int count () const; + +//! \brief the current position + unsigned int getPosition ()const; + + //! \brief go to the current position. If it does not exist, + // go to the nearest. + unsigned int gotoPosition (); + + +//! \brief the current position in the tracks list + unsigned int getTrackPosition () const; + + //! \brief go to the current track position. If it does not exist, + // go to the nearest. + unsigned int gotoTrackPosition (); + +/*! \brief enter the the next higher level, go one up in the tree. + * If fall_through (see constructor) is set to true, and the + * level entered by enter() contains only one item, automatically + * goes down further until a level with more than one item is reached. + * \param position is the position in the current level that is to be expanded + * \return returns false if there is no further level + */ + bool enter (unsigned int position); + + /*! \brief like enter but if we are at the leaf level simply select + * the entry at position + */ + bool select (unsigned int position); + +/*! \brief enter the next higher level, expanding the current position. + * See also enter(unsigned int position) + */ + bool enter () + { + return enter (gotoPosition ()); + } + + /*! \brief like enter but if we are at the leaf level simply select + * the current entry + */ + bool select () + { + return select (gotoPosition ()); + } + +/*! \brief enter the next higher level, expanding the position holding a certain value + * \param value the position holding value will be expanded. + */ + bool enter (const string value) + { + return enter (valindex (value)); + } + + /*! \brief like enter but if we are at the leaf level simply select + * the current entry + */ + bool select (const string value) + { + return select (valindex(value)); + } + + bool selectid (const string id) + { + return select(idindex(id)); + } + + void selectfrom(mgOrder& oldorder,mgContentItem* o); + +/*! \brief leave the current level, go one up in the tree. + * If fall_through (see constructor) is set to true, and the + * level entered by leave() contains only one item, automatically + * goes up further until a level with more than one item is reached. + * \return returns false if there is no further upper level + */ + bool leave (); + +/*! \brief leave the current level, go up in the tree until + * target level is reached. + * If fall_through (see constructor) is set to true, and the + * level entered by leave() contains only one item, automatically + * goes up further until a level with more than one item is reached. + * \return returns false if there is no further upper level + */ + void leave_all (); + +//! \brief the current level in the tree + unsigned int level () const + { + return m_level; + } + + //! \brief true if the selection holds no items + bool empty(); + +/*! \brief returns detailed info about all selected tracks. + * The ordering is done only by the keyfield of the current level. + * This might have to be changed - suborder by keyfields of detail + * levels. This list is cached so several consequent calls mean no + * loss of performance. See value(), the same warning applies. + * \todo call this more seldom. See getNumTracks() + */ + const vector < mgContentItem > &tracks () const; + +/*! \brief returns an item from the tracks() list + * \param position the position in the tracks() list + * \return returns NULL if position is out of range + */ + mgContentItem* getTrack (unsigned int position); + +/*! \brief returns the current item from the tracks() list + */ + mgContentItem* getCurrentTrack () + { + return getTrack (gotoTrackPosition()); + } + +/*! \brief toggles the shuffle mode thru all possible values. + * When a shuffle modus SM_NORMAL or SM_PARTY is selected, the + * order of the tracks in the track list will be randomly changed. + */ + ShuffleMode toggleShuffleMode (); + +//! \brief toggles the loop mode thru all possible values + LoopMode toggleLoopMode (); + +//! \brief returns the current shuffle mode + ShuffleMode getShuffleMode () const + { + return m_shuffle_mode; + } + +//! \brief sets the current shuffle mode + void setShuffleMode (const ShuffleMode shuffle_mode); + +//! \brief returns the current loop mode + LoopMode getLoopMode () const + { + return m_loop_mode; + } + +//! \brief sets the current loop mode + void setLoopMode (const LoopMode loop_mode) + { + m_loop_mode = loop_mode; + } + +/*! \brief adds the whole current track list to a collection + * \param Name the name of the collection. If it does not yet exist, + * it will be created. + */ + unsigned int AddToCollection (const string Name); + +/*! \brief removes the whole current track from a the collection + * Remember - this selection can be configured to hold exactly + * one list, so this command can be used to clear a selected list. + * \param Name the name of the collection + */ + unsigned int RemoveFromCollection (const string Name); +//! \brief delete a collection + bool DeleteCollection (const string Name); +/*! \brief create a collection only if it does not yet exist. + * \return true only if it has been created. false if it already existed. + */ + bool CreateCollection(const string Name); + +//! \brief remove all items from the collection + void ClearCollection (const string Name); + +/*! generates an m3u file containing all tracks. The directory + * can be indicated by SetDirectory(). + * The file name will be built from the list name, slashes + * and spaces converted + */ + string exportM3U (); + + /*! import/export tags like + * \par path can be a file or a directory. If directory, + * sync all files within but by default non recursive + * \par recursive recurse into all directories beneath path + * \par assorted see mugglei -h + * \par delete_missing if the file does not exist, delete the + * data base entry. If the file is unreadable, do not delete. + */ + void Sync(string path, bool recursive=false,bool assorted=false,bool delete_missing=false); + +/*! \brief go to a position in the current level. If we are at the + * most detailled level this also sets the track position since + * they are identical. + * \param position the wanted position. If it is too big, go to the + * last existing position + * \return only if no position exists, false will be returned + */ + void setPosition (unsigned int position); + +/*! \brief go to the position with value in the current level + * \param value the value of the wanted position + */ + void setPosition (const string value) + { + setPosition (valindex (value)); + } + +/*! \brief go to a position in the track list + * \param position the wanted position. If it is too big, go to the + * last existing position + * \return only if no position exists, false will be returned + */ + void setTrackPosition (unsigned int position) const; + +/*! \brief skip some tracks in the track list + * \return false if new position does not exist + */ + bool skipTracks (int step=1); + +/*! \brief skip forward by 1 in the track list + * \return false if new position does not exist + */ + bool skipFwd () + { + return skipTracks (+1); + } + +/*! \brief skip back by 1 in the track list + * \return false if new position does not exist + */ + bool skipBack () + { + return skipTracks (-1); + } + +//! \brief returns the sum of the durations of all tracks + unsigned long getLength (); + +/*! \brief returns the sum of the durations of completed tracks + * those are tracks before the current track position + */ + unsigned long getCompletedLength () const; + +/*! returns the number of tracks in the track list + * \todo should not call tracks () which loads all track info. + * instead, only count the tracks. If the size differs from + * m_tracks.size(), invalidate m_tracks + */ + unsigned int getNumTracks () const + { + return tracks ().size (); + } + +//! sets the directory for the storage of m3u file + void SetDirectory (const string directory) + { + m_Directory = directory; + } + +/*! returns the name of the current play list. If no play list is active, + * the name is built from the name of the key fields. + */ + string getListname () const; + +/*! \brief true if this selection currently selects a list of collections + */ + bool isCollectionlist () const; + +/*! \brief true if this selection currently selects a list of languages + */ + bool isLanguagelist () const; + + //! \brief true if we have entered a collection + bool inCollection(const string Name="") const; + + /*! \brief dumps the entire state of this selection into a map, + * \param nv the values will be entered into this map + */ + void DumpState(mgValmap& nv) const; + + /*! \brief creates a new selection using saved definitions + * \param nv this map contains the saved definitions + */ + mgSelection(mgValmap& nv); + + //! \brief clear the cache, next access will reload from data base + void clearCache() const; + + void refreshValues() const; + + //! \brief true if values and tracks need to be reloaded + bool cacheIsEmpty() const + { + return (m_current_values=="" && m_current_tracks==""); + } + string value(mgKeyTypes kt, string id) const; + string value(mgKey* k, string id) const; + string value(mgKey* k) const; + string id(mgKeyTypes kt, string val) const; + string id(mgKey* k, string val) const; + string id(mgKey* k) const; + unsigned int keycount(mgKeyTypes kt); + vector choices(mgOrder *o,unsigned int level, unsigned int *current); + unsigned int valcount (string val); + + private: + mutable map > map_values; + mutable map > map_ids; + mutable string m_current_values; + mutable string m_current_tracks; +//! \brief be careful when accessing this, see mgSelection::tracks() + mutable vector < mgContentItem > m_tracks; + mutable strvector m_ids; + mutable vector < unsigned int > m_counts; + //! \brief initializes maps for id/value mapping in both direction + bool loadvalues (mgKeyTypes kt) const; + bool m_fall_through; + unsigned int m_position; + mutable unsigned int m_tracks_position; + ShuffleMode m_shuffle_mode; + void Shuffle() const; + LoopMode m_loop_mode; + MYSQL *m_db; + void setDB(MYSQL *db); + unsigned int m_level; + long m_trackid; + + mgOrder order; + bool UsedBefore (mgOrder *o,const mgKeyTypes kt, unsigned int level) const; + void InitSelection (); + void Connect (); + /*! \brief returns the SQL command for getting all values. + * For the leaf level, all values are returned. For upper + * levels, every distinct value is returned only once. + * This must be so for the leaf level because otherwise + * the value() entries do not correspond to the track() + * entries and the wrong tracks might be played. + */ + string sql_values (); + unsigned int valindex (const string val,const bool second_try=false) const; + unsigned int idindex (const string val,const bool second_try=false) const; + string ListFilename (); + string m_Directory; + void loadgenres (); + MYSQL_RES * exec_sql(string query) const; + string get_col0 (string query) const; + + void InitFrom(const mgSelection* s); + +/*! \brief executes a query and returns the integer value from + * the first column in the first row. The query shold be a COUNT query + * returning only one row. + * \param query the SQL query to be executed + */ + unsigned long exec_count (string query) const; + + +}; + + +unsigned int randrange (const unsigned int high); + + +#endif // _DB_H diff --git a/muggle.c b/muggle.c index 46fda32..9b22f80 100644 --- a/muggle.c +++ b/muggle.c @@ -15,7 +15,7 @@ #include "vdr_menu.h" #include "vdr_setup.h" #include "mg_tools.h" -#include "mg_db.h" +#include "mg_selection.h" #include "i18n.h" #include diff --git a/vdr_decoder.c b/vdr_decoder.c index 62e6a6d..605b007 100644 --- a/vdr_decoder.c +++ b/vdr_decoder.c @@ -21,7 +21,7 @@ #include #include -#include "mg_db.h" +#include "mg_selection.h" #include #include diff --git a/vdr_decoder_flac.c b/vdr_decoder_flac.c index 2861d4e..fd1ca53 100644 --- a/vdr_decoder_flac.c +++ b/vdr_decoder_flac.c @@ -17,7 +17,7 @@ #include #include "mg_tools.h" -#include "mg_db.h" +#include "mg_content.h" #include "vdr_setup.h" #include "vdr_decoder_flac.h" diff --git a/vdr_decoder_mp3.c b/vdr_decoder_mp3.c index 2ad6bc3..6569760 100644 --- a/vdr_decoder_mp3.c +++ b/vdr_decoder_mp3.c @@ -29,7 +29,7 @@ #include "vdr_setup.h" #include "mg_tools.h" -#include "mg_db.h" +#include "mg_content.h" #define d(x) x diff --git a/vdr_decoder_ogg.c b/vdr_decoder_ogg.c index f3ba8ef..47011eb 100644 --- a/vdr_decoder_ogg.c +++ b/vdr_decoder_ogg.c @@ -21,7 +21,7 @@ #include "vdr_setup.h" -#include "mg_db.h" +#include "mg_content.h" // --- mgOggFile ---------------------------------------------------------------- diff --git a/vdr_player.c b/vdr_player.c index bf0a504..3b3035f 100644 --- a/vdr_player.c +++ b/vdr_player.c @@ -41,7 +41,6 @@ #include "vdr_config.h" #include "vdr_setup.h" #include "i18n.h" -#include "mg_db.h" #include "mg_tools.h" diff --git a/vdr_player.h b/vdr_player.h index 5b8ea9f..5d60344 100644 --- a/vdr_player.h +++ b/vdr_player.h @@ -18,7 +18,7 @@ #define ___VDR_PLAYER_H #include -#include "mg_db.h" +#include "mg_selection.h" #if VDRVERSNUM >= 10307 class cOsd; #endif -- cgit v1.2.3