/*! * \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 "mg_db.h" #include "vdr_setup.h" #include "mg_tools.h" /*! \brief a RAM copy of the genres table for faster access * and in order to avoid having to use genre as genre 1 etc. */ static map < string, string > genres; //! \brief adds string n to string s, using string sep to separate them static string addsep (string & s, string sep, string n); //! \brief adds string n to string s, using a comma to separate them static string comma (string & s, string n); //! \brief adds string n to string s, using AND to separate them static string und (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; } //! \brief adds n1=n2 to string s, using AND to separate several such items static string undequal (string & s, string n1, string op, string n2) { if (n1.compare (n2) || op != "=") return addsep (s, " AND ", n1 + op + n2); else return s; } static string comma (string & s, string n) { return addsep (s, ",", n); } static string und (string & s, string n) { return addsep (s, " AND ", n); } static string commalist (string prefix,list < string > v,bool sort=true) { string result = ""; if (sort) v.sort (); v.unique (); for (list < string >::iterator it = v.begin (); it != v.end (); it++) { comma (result, *it); } if (!result.empty()) result.insert(0," "+prefix+" "); return result; } //! \brief converts long to string string itos (int i) { stringstream s; s << i; return s.str (); } //! \brief convert long to string string ltos (long l) { stringstream s; s << l; return s.str (); } static string zerostring; size_t mgSelection::mgSelStrings::size() { if (!m_sel) mgError("mgSelStrings: m_sel is NULL"); m_sel->refreshValues(); return strings.size(); } string& mgSelection::mgSelStrings::operator[](unsigned int idx) { if (!m_sel) mgError("mgSelStrings: m_sel is NULL"); m_sel->refreshValues(); if (idx>=strings.size()) return zerostring; return strings[idx]; } void mgSelection::mgSelStrings::setOwner(mgSelection* sel) { m_sel = sel; } mgValmap::mgValmap(const char *key) { m_key = key; } void mgValmap::Read(FILE *f) { char *line=(char*)malloc(1000); char *prefix=(char*)malloc(strlen(m_key)+2); strcpy(prefix,m_key); strcat(prefix,"."); rewind(f); while (fgets(line,1000,f)) { if (strncmp(line,prefix,strlen(prefix))) continue; if (line[strlen(line)-1]=='\n') line[strlen(line)-1]=0; char *name = line + strlen(prefix); char *eq = strchr(name,'='); if (!eq) continue; *(eq-1)=0; char *value = eq + 2; (*this)[string(name)]=string(value); } free(prefix); free(line); } void mgValmap::Write(FILE *f) { for (mgValmap::const_iterator it=begin();it!=end();++it) { char b[1000]; sprintf(b,"%s.%s = %s\n", m_key,it->first.c_str(), it->second.c_str()); fputs(b,f); } } void mgValmap::put(const char* name, const string value) { if (value.empty() || value==EMPTY) return; (*this)[string(name)] = value; } void mgValmap::put(const char* name, const char* value) { if (!value || *value==0) return; (*this)[string(name)] = value; } void mgValmap::put(const char* name, const int value) { put(name,ltos(value)); } void mgValmap::put(const char* name, const unsigned int value) { put(name,ltos(value)); } void mgValmap::put(const char* name, const long value) { put(name,ltos(value)); } void mgValmap::put(const char* name, const bool value) { string s; if (value) s = "true"; else s = "false"; put(name,s); } void mgSelection::clearCache() { m_current_values = ""; m_current_tracks = ""; } string mgSelection::getCurrentValue() { return values[gotoPosition()]; } MYSQL_RES * mgSelection::exec_sql (string query) { mgDebug(3,query.c_str()); if (!m_db) return NULL; if (mysql_query (m_db, (query + ';').c_str ())) { mgError("SQL Error in %s: %s",query.c_str(),mysql_error (m_db)); return NULL; } return mysql_store_result (m_db); } /*! \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) { MYSQL_RES * sql_result = exec_sql (query); if (!sql_result) return "NULL"; MYSQL_ROW row = mysql_fetch_row (sql_result); string result; if (row == NULL) result = "NULL"; else if (row[0] == NULL) result = "NULL"; else result = row[0]; mysql_free_result (sql_result); return result; } unsigned long mgSelection::exec_count (string query) { return atol (get_col0 (query).c_str ()); } /*! \brief extract table names. All words preceding a . are supposed to be * table names. Table names are supposed to only contain letters. That is * sufficient for GiantDisc * \par spar the SQL command * \return a list of table names * \todo is this thread safe? */ static list < string > tables (const string spar) { list < string > result; string s = spar; string::size_type dot; while ((dot = s.rfind ('.')) != string::npos) { s.erase (dot, string::npos); // cut the rest string::size_type lword = s.size (); while (strchr ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", s[lword - 1])) { lword--; if (lword == 0) break; } result.push_back (s.substr (lword)); } return result; } /*! \brief if the SQL command works on only 1 table, remove all table * qualifiers. Example: SELECT tracks.title FROM tracks becomes SELECT title * FROM tracks * \param spar the sql command. It will be edited in place * \return the new sql command is also returned */ static string optimize (string & spar) { string s = spar; string::size_type tmp = s.find (" WHERE"); if (tmp != string::npos) s.erase (tmp, 9999); tmp = s.find (" ORDER"); if (tmp != string::npos) s.erase (tmp, 9999); string::size_type frompos = s.find (" FROM ") + 6; if (s.substr (frompos).find (",") == string::npos) { string from = s.substr (frompos, 999) + '.'; string::size_type track; while ((track = spar.find (from)) != string::npos) { spar.erase (track, from.size ()); } } return spar; } string keyfield::sql_string(const string s) const { return selection->sql_string(s); } keyfield::keyfield (const string choice) { if (choice.empty()) mgError("keyfield::keyfield: choice is empty"); m_choice = choice; m_id = EMPTY; m_value = ""; m_filter = ""; } 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 keyfield::restrict (string & result) const { string id = ""; string op; if (m_id == EMPTY) return result; if (m_id == "NULL") { op = " is "; id = "NULL"; } else { op = "="; id = sql_string (m_id); } if (idfield () == valuefield ()) undequal (result, idfield (), op, id); else undequal (result, basefield (), op, id); return result; } string keyfield::join () const { string result; if (need_join ()) return undequal (result, basefield (), "=", idfield ()); else return ""; } bool keyfield::need_join () const { return lookup; } void keyfield::set(const string id,const string value) { if (id != EMPTY) if (m_id == id && m_value == value) return; m_id = id; m_value = value; if (selection) selection->clearCache(); } void keyfield::writeAt (ostream & s) const { if (m_id == EMPTY) s << choice () << '/'; else s << choice () << '=' << m_id; } const char * toCString (mgSelection::ShuffleMode m) { static const char *modes[] = { "SM_NONE", "SM_NORMAL", "SM_PARTY" }; return modes[m]; } string toString (mgSelection::ShuffleMode m) { return toCString (m); } //! \brief dump keyfield ostream & operator<< (ostream & s, keyfield & k) { k.writeAt (s); return s; } string addsep (string & s, string sep, string n) { if (!n.empty ()) { if (!s.empty ()) s.append (sep); s.append (n); } return s; } mgContentItem * mgSelection::getTrack (unsigned int position) { if (position >= getNumTracks ()) return NULL; return &(m_tracks[position]); } void mgSelection::loadgenres () { MYSQL_RES *rows = exec_sql ("select id,genre from genre;"); if (rows) { MYSQL_ROW row; while ((row = mysql_fetch_row (rows)) != NULL) { genres[row[0]] = row[1]; } mysql_free_result (rows); } } string mgContentItem::getGenre1 () { return genres[m_genre1]; } string mgContentItem::getGenre2 () { return genres[m_genre2]; } mgSelection::ShuffleMode mgSelection::toggleShuffleMode () { m_shuffle_mode = (m_shuffle_mode == SM_PARTY) ? SM_NONE : ShuffleMode (m_shuffle_mode + 1); unsigned int tracksize = getNumTracks(); switch (m_shuffle_mode) { case SM_NONE: { long id = m_tracks[m_tracks_position].getId (); m_current_tracks = ""; // force a reload for (unsigned int i = 0; i < tracksize; i++) if (m_tracks[i].getId () == id) { m_tracks_position = i; break; } } break; case SM_PARTY: case SM_NORMAL: { // play all, beginning with current track: mgContentItem tmp = m_tracks[m_tracks_position]; m_tracks[m_tracks_position]=m_tracks[0]; m_tracks[0]=tmp; m_tracks_position=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 */ } return m_shuffle_mode; } 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))); string tmp = get_col0 ("SELECT MAX(tracknumber) FROM playlistitem WHERE playlist=" + listid); int high; if (tmp == "NULL") high = 0; else high = atol (tmp.c_str ()); unsigned int tracksize = getNumTracks (); const char *sql_prefix = "INSERT INTO playlistitem VALUES "; string sql = ""; for (unsigned int i = 0; i < tracksize; i++) { string value = "(" + listid + "," + ltos (high + 1 + i) + "," + ltos (m_tracks[i].getId ()) + ")"; comma(sql, value); 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; string listid = get_col0 ("SELECT id FROM playlist WHERE title=" + sql_string (Name)); where(); m_fromtables.push_back("playlistitem"); m_fromtables.push_back("playlist"); string sql = "DELETE playlistitem" + commalist("FROM",m_fromtables) + m_where + " AND tracks.id = playlistitem.trackid " " AND playlistitem.playlist = " + listid; 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; 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; exec_sql ("DELETE playlistitem FROM playlist,playlistitem " "WHERE playlistitem.playlist=playlist.id " " AND playlist.title=" + sql_string (Name)); 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 = m_Directory + '/' + ListFilename () + ".m3u"; FILE * listfile = fopen (fn.c_str (), "w"); if (!listfile) return ""; fprintf (listfile, "#EXTM3U"); 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, "%s", t->getSourceFile ().c_str ()); } fclose (listfile); return fn; } bool mgSelection::empty() { if (m_level>= keys.size ()-1) return ( getNumTracks () == 0); else return ( values.size () == 0); } void mgSelection::setPosition (unsigned int position) { if (m_level < keys.size ()) m_position[m_level] = position; if (m_level >= keys.size ()-1) m_tracks_position = position; } void mgSelection::setTrack (unsigned int position) { m_tracks_position = position; } unsigned int mgSelection::getPosition (unsigned int level) const { if (level == keys.size ()) return getTrackPosition(); else return m_position[m_level]; } unsigned int mgSelection::gotoPosition (unsigned int level) { if (level>keys.size()) mgError("mgSelection::gotoPosition: level %u > keys.size %u", level,keys.size()); if (level == keys.size ()) return gotoTrackPosition(); else { unsigned int valsize = values.size(); if (valsize==0) m_position[m_level] = 0; else if (m_position[m_level] >= valsize) m_position[m_level] = valsize -1; return m_position[m_level]; } } unsigned int mgSelection::getTrackPosition() const { return m_tracks_position; } unsigned int mgSelection::gotoTrackPosition() { unsigned int tracksize = getNumTracks (); if (tracksize == 0) m_tracks_position = 0; else if (m_tracks_position >= tracksize) m_tracks_position = 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 = getTrackPosition(); 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) { clearCache(); tracksize = getNumTracks(); if (new_pos >= tracksize) { if (m_loop_mode == LM_NONE) return false; new_pos = 0; } } setTrack (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 () { unsigned long result = 0; tracks (); // make sure they are loaded for (unsigned int i = 0; i < m_tracks_position; i++) result += m_tracks[i].getDuration (); return result; } string mgSelection::getListname () { string result = ""; for (unsigned int i = 0; i < m_level; i++) addsep (result, ":", keys[i]->value ()); if (result.empty ()) result = string(tr(keys[0]->choice ().c_str())); return result; } string mgSelection::ListFilename () { string res = getListname (); #if 0 geht so noch gar nicht ... while (string::size_type p = res.find (" ")) res.replace (p, ""); while (string::size_type p = res.find ("/")) res.replace (p, '-'); while (string::size_type p = res.find ("\\")) res.replace (p, '-'); #endif return res; } void mgSelection::AddOrder(const string sql,list& orderlist, const string item) { string::size_type dot = item.rfind ('.'); string itemtable = item.substr(0,dot); if (sql.find(itemtable) != string::npos) orderlist.push_back(item); } const vector < mgContentItem > & mgSelection::tracks () { list < string > orderby; orderby.clear(); if (keys.empty()) mgError("mgSelection::tracks(): keys is empty"); if (genres.size () == 0) loadgenres (); string sql = "SELECT tracks.id, tracks.title, tracks.mp3file, " "tracks.artist, album.title, tracks.genre1, tracks.genre2, " "tracks.bitrate, tracks.year, tracks.rating, " "tracks.length, tracks.samplerate, tracks.channels "; sql += where (true); for (unsigned int i = m_level; iorder ()); } if (m_level>= keys.size ()-1) if (inCollection()) AddOrder(sql,orderby,"playlistitem.tracknumber"); else AddOrder(sql,orderby,"tracks.title"); sql += commalist("ORDER BY",orderby,false); optimize (sql); if (m_current_tracks != sql) { m_current_tracks = sql; m_tracks.clear (); MYSQL_RES *rows = exec_sql (sql); if (rows) { MYSQL_ROW row; while ((row = mysql_fetch_row (rows)) != NULL) { m_tracks.push_back (mgContentItem (row, m_ToplevelDir)); } mysql_free_result (rows); } 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; } 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 = 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; } mgContentItem::mgContentItem (const MYSQL_ROW row, const string ToplevelDir) { m_id = atol (row[0]); m_title = row[1]; m_mp3file = ToplevelDir + row[2]; m_artist = row[3]; m_albumtitle = row[4]; m_genre1 = row[5]; m_genre2 = row[6]; m_bitrate = row[7]; m_year = atol (row[8]); if (row[9]) m_rating = atol (row[9]); m_duration = atol (row[10]); m_samplerate = atol (row[11]); m_channels = atol (row[12]); }; string mgContentItem::getAlbum () { return m_albumtitle; } string mgContentItem::getImageFile () { return "Name of Imagefile"; } void mgSelection::initkey (keyfield & f) { f.setOwner(this); all_keys[f.choice ()] = &f; trall_keys[string(tr(f.choice ().c_str()))] = &f; } void mgSelection::InitSelection() { m_Directory="."; m_ToplevelDir = string("/"); InitDatabase(); m_level = 0; m_position.reserve (20); m_tracks_position = 0; m_trackid = -1; m_shuffle_mode = SM_NONE; m_loop_mode = LM_NONE; clearCache(); initkey (kartist); initkey (kgenre1); initkey (kgenre2); initkey (klanguage); initkey (krating); initkey (kyear); initkey (kdecade); initkey (ktitle); initkey (ktrack); initkey (kalbum); initkey (kcollection); initkey (kcollectionitem); keys.clear(); keys.push_back (&kartist); keys.push_back (&kalbum); keys.push_back (&ktitle); values.setOwner(this); } mgSelection::mgSelection() { m_db = NULL; m_Host = ""; m_User = ""; m_Password = ""; InitSelection (); m_fall_through = false; } mgSelection::mgSelection (const string Host, const string User, const string Password, const bool fall_through) { m_db = NULL; m_Host = Host; m_User = User; m_Password = Password; InitSelection (); m_fall_through = fall_through; } mgSelection::mgSelection (const mgSelection &s) { m_db = NULL; InitFrom(&s); } mgSelection::mgSelection (const mgSelection* s) { m_db = NULL; InitFrom(s); } mgSelection::mgSelection (mgValmap& nv) { // this is analog to the copy constructor, please keep in sync. m_db = NULL; InitFrom(nv); } void mgSelection::InitFrom(mgValmap& nv) { m_Host = nv.getstr("Host"); m_User = nv.getstr("User"); m_Password = nv.getstr("Password"); InitSelection(); m_fall_through = nv.getbool("FallThrough"); m_Directory = nv.getstr("Directory"); m_ToplevelDir = nv.getstr("ToplevelDir"); for (unsigned int i = 0; i < 99 ; i++) { char *idx; asprintf(&idx,"Keys.%u.Choice",i); string v = nv.getstr(idx); free(idx); if (v.empty()) break; setKey (i,v ); } while (m_level < nv.getuint("Level")) { char *idx; asprintf(&idx,"Keys.%u.Position",m_level); unsigned int newpos = nv.getuint(idx); free(idx); if (!enter (newpos)) if (!select (newpos)) break; } m_trackid = nv.getlong("TrackId"); // TODO do we really need Position AND TrackPosition in muggle.state? setPosition(nv.getlong("Position")); if (m_level>=keys.size()-1) setTrack(nv.getlong("TrackPosition")); setShuffleMode(ShuffleMode(nv.getuint("ShuffleMode"))); setLoopMode(LoopMode(nv.getuint("LoopMode"))); } mgSelection::~mgSelection () { mysql_close (m_db); } void mgSelection::InitFrom(const mgSelection* s) { m_Host = s->m_Host; m_User = s->m_User; m_Password = s->m_Password; InitSelection(); m_fall_through = s->m_fall_through; m_Directory = s->m_Directory; m_ToplevelDir = s->m_ToplevelDir; keys.clear(); for (unsigned int i = 0; i < s->keys.size (); i++) { keys.push_back(findKey(s->keys[i]->choice())); keys[i]->set(s->keys[i]->id(),s->keys[i]->value()); } m_level = s->m_level; m_position.reserve (s->m_position.capacity()); for (unsigned int i = 0; i < s->m_position.capacity(); i++) m_position[i] = s->m_position[i]; m_trackid = s->m_trackid; m_tracks_position = s->m_tracks_position; setShuffleMode (s->getShuffleMode ()); setLoopMode (s->getLoopMode ()); } const mgSelection& mgSelection::operator=(const mgSelection &s) { if ((&m_Host)==&(s.m_Host)) { // prevent s = s return *this; } InitFrom(&s); return *this; } void mgSelection::writeAt (ostream & s) { for (unsigned int i = 0; i < keys.size (); i++) { if (i == level ()) s << '*'; s << *keys[i] << ' '; if (i == level ()) { for (unsigned int j = 0; j < values.size (); j++) { s << values[j]; if (values[j] != m_ids[j]) s << '(' << m_ids[j] << ")"; s << ", "; if (j == 7) { s << "(von " << values.size () << ") "; break; } } } } s << endl; } ostream & operator<< (ostream & s, mgSelection & sl) { sl.writeAt (s); return s; } unsigned int mgSelection::size () { return keys.size (); } unsigned int mgSelection::valindex (const string val,const bool second_try) { for (unsigned int i = 0; i < values.size (); i++) { if (values[i] == val) return i; } // nochmal mit neuen Werten: clearCache(); if (second_try) { mgWarning("valindex: Gibt es nicht:%s",val.c_str()); return 0; } else return valindex(val,true); } string mgSelection::where (bool want_trackinfo) { m_from = ""; m_where = ""; m_fromtables.clear(); if (m_level < keys.size ()) { for (unsigned int i = 0; i <= m_level; i++) { keyfield * k = keys[i]; k->lookup = want_trackinfo || (i == m_level); list < string > l = tables (k->join () + ' ' + k->basefield ()); m_fromtables.merge (l); und (m_where, k->join ()); k->restrict (m_where); } } else { m_fromtables.push_back ("tracks"); m_where = "tracks.id='" + ltos (m_trackid) + "'"; } if (want_trackinfo) { if (m_level == keys.size () || !UsedBefore (&kalbum, m_level + 1)) { kalbum.lookup = false; list < string > l = tables (kalbum.join () + ' ' + kalbum.basefield ()); m_fromtables.merge (l); und (m_where, kalbum.join ()); } } m_from = commalist ("FROM",m_fromtables); if (!m_where.empty ()) m_where.insert (0, " WHERE "); return m_from + m_where; } void mgSelection::refreshValues () { if (m_current_values.empty()) { m_current_values = sql_values(); values.strings.clear (); m_ids.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)) != NULL) { values.strings.push_back (row[0]); if (num_fields==2) m_ids.push_back (row[1]); else m_ids.push_back (row[0]); } mysql_free_result (rows); } if (m_position[m_level]>=values.size()) if (values.size()==0) m_position[m_level]=0; else m_position[m_level] = values.size()-1; } } string mgSelection::sql_values () { if (keys.empty()) mgError("mgSelection::sql_values(): keys is empty"); string result; if (m_level < keys.size ()) { keyfield * last = keys[m_level]; result = "SELECT "; if (m_levelvaluefield (); if (last->valuefield() != last->idfield()) result += ',' + last->idfield (); result += where (false); result += " ORDER BY " + last->order (); } else { result = "SELECT title,id from tracks where id='" + ltos (m_trackid) + "'"; } optimize (result); return result; } unsigned int mgSelection::count () { return values.size (); } void mgSelection::InitDatabase () { if (m_db) { mysql_close (m_db); m_db = NULL; } if (m_Host == "") return; m_db = mysql_init (0); if (m_db == NULL) return; if (mysql_real_connect (m_db, m_Host.c_str (), m_User.c_str (), m_Password.c_str (), "GiantDisc", 0, NULL, 0) == NULL) { mgWarning("Failed to connect to host '%s' as User '%s', Password '%s': Error: %s", m_Host.c_str(),m_User.c_str(),m_Password.c_str(),mysql_error(m_db)); mysql_close (m_db); m_db = NULL; return; } return; } string keyfield::KeyCountquery () { lookup = false; string from; from = commalist ("FROM",tables (countfield () + ' ' + countjoin ())); string query = "SELECT COUNT(DISTINCT " + countfield () + ") " + from; if (!countjoin ().empty ()) query += " WHERE " + countjoin (); optimize (query); return query; } keyfield* mgSelection::findKey(const string name) { if (all_keys.find(name) != all_keys.end()) return all_keys.find(name)->second; if (trall_keys.find(name) != trall_keys.end()) return trall_keys.find(name)->second; return NULL; } void mgSelection::setKey (const unsigned int level, const string name) { keyfield *newkey = findKey(name); if (newkey == NULL) mgError("mgSelection::setKey(%u,%s): keyname wrong", level,name.c_str()); if (level == 0 && newkey == &kcollection) { keys.clear (); keys.push_back (&kcollection); keys.push_back (&kcollectionitem); return; } if (level == keys.size ()) { keys.push_back (newkey); } else { if (level >= keys.size()) mgError("mgSelection::setKey(%u,%s): level greater than keys.size() %u", level,name.c_str(),keys.size()); keys[level] = newkey; // remove this key from following lines: for (unsigned int i = level + 1; i < keys.size (); i++) if (keys[i] == keys[level]) keys.erase (keys.begin () + i); } // remove redundant lines: bool album_found = false; bool track_found = false; bool title_found = false; for (unsigned int i = 0; i < keys.size (); i++) { album_found |= (keys[i] == &kalbum); track_found |= (keys[i] == &ktrack); title_found |= (keys[i] == &ktitle); if (track_found || (album_found && title_found)) { keys.erase (keys.begin () + i + 1, keys.end ()); break; } } // clear values for this and following levels (needed for copy constructor) for (unsigned int i = level; i < keys.size (); i++) keys[i]->set (EMPTY, ""); if (m_level > level) m_level = level; if (m_level == level) setPosition(0); } bool mgSelection::enter (unsigned int position) { if (keys.empty()) mgError("mgSelection::enter(%u): keys is empty", position); if (empty()) return false; setPosition (position); position = gotoPosition(); // reload adjusted position string value = values[position]; string id = m_ids[position]; while (1) { mgDebug(2,"enter(level=%u,pos=%u, value=%s)",m_level,position,value.c_str()); if (m_level >= keys.size () - 1) return false; keys[m_level++]->set (id, value); if (m_level >= keys.size()) mgError("mgSelection::enter(%u): level greater than keys.size() %u", m_level,keys.size()); if (m_position.capacity () == m_position.size ()) m_position.reserve (m_position.capacity () + 10); m_position[m_level] = 0; if (!m_fall_through) break; if (count () > 1) break; if (count () == 1) { id = m_ids[0]; value = values[0]; } } return true; } bool mgSelection::select (unsigned int position) { mgDebug(2,"select(pos=%u)",position); if (m_level == keys.size () - 1) { if (getNumTracks () <= position) return false; m_level++; m_trackid = m_tracks[position].getId (); clearCache(); return true; } else return enter (position); } bool mgSelection::leave () { if (keys.empty()) mgError("mgSelection::leave(): keys is empty"); if (m_level == keys.size ()) { m_level--; m_trackid = -1; clearCache(); return true; } while (1) { if (m_level < 1) return false; keys[--m_level]->set (EMPTY, ""); if (!m_fall_through) break; if (count () > 1) break; } return true; } bool mgSelection::UsedBefore (keyfield const *k, unsigned int level) { if (level >= keys.size ()) level = keys.size () - 1; for (unsigned int i = 0; i < level; i++) if (keys[i] == k) return true; return false; } bool mgSelection::isCollectionlist () { return (keys[0] == &kcollection && m_level == 0); } bool mgSelection::inCollection(const string Name) { bool result = (keys[0] == &kcollection && m_level == 1); if (result) if (keys[1] != &kcollectionitem) mgError("inCollection: key[1] is not kcollectionitem"); if (!Name.empty()) result &= (keys[0]->value() == Name); return result; } const strvector & mgSelection::keychoice (const unsigned int level) { m_keychoice.clear (); if (level > keys.size ()) return m_keychoice; map < string, keyfield * >::iterator it; map < string, keyfield * > possible_keys; for (it = all_keys.begin (); it != all_keys.end (); it++) { keyfield*f = (*it).second; if (keycounts.find (f->choice ()) == keycounts.end ()) { keycounts[f->choice ()] = exec_count (f->KeyCountquery ()); } unsigned int i = keycounts[f->choice ()]; if ((&(*f) != &kcollection) && (&(*f) != &kcollectionitem) && (i < 2)) ; else possible_keys[string(tr(f->choice ().c_str()))] = &(*f); } for (it = possible_keys.begin (); it != possible_keys.end (); it++) { keyfield *k = (*it).second; if (level != 0 && k == &kcollection) continue; if (level != 1 && k == &kcollectionitem) continue; if (level == 1 && keys[0] != &kcollection && k == &kcollectionitem) continue; if (level == 1 && keys[0] == &kcollection && k != &kcollectionitem) continue; if (level > 1 && keys[0] == &kcollection) break; if (k == &kdecade && UsedBefore (&kyear, level)) continue; if (!UsedBefore (k, level)) m_keychoice.push_back (string(tr((*it).second->choice ().c_str()))); } return m_keychoice; } void mgSelection::DumpState(mgValmap& nv) { nv.put("Host",m_Host); nv.put("User",m_User); nv.put("Password",m_Password); nv.put("FallThrough",m_fall_through); nv.put("ShuffleMode",int(m_shuffle_mode)); nv.put("LoopMode",int(m_loop_mode)); nv.put("Directory",m_Directory); nv.put("ToplevelDir",m_ToplevelDir); nv.put("Level",int(m_level)); for (unsigned int i=0;ichoice()); asprintf(&n,"Keys.%d.Filter",i); nv.put(n,keys[i]->filter()); if (i * mgSelection::UsedKeyValues() { map *result = new map; for (unsigned int idx = 0 ; idx < level() ; idx++) { (*result)[keys[idx]->choice()] = keys[idx]->value(); } if (level() < keys.size()-1) { string ch = keys[level()]->choice(); (*result)[ch] = getCurrentValue(); } return result; }