diff options
Diffstat (limited to 'mg_db.c')
-rw-r--r-- | mg_db.c | 1137 |
1 files changed, 391 insertions, 746 deletions
@@ -13,20 +13,9 @@ #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 @@ -38,16 +27,6 @@ randrange (const unsigned int high) } -//! \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) { @@ -55,46 +34,6 @@ comma (string & s, string 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 @@ -122,75 +61,8 @@ 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() +mgSelection::clearCache() const { m_current_values = ""; m_current_tracks = ""; @@ -202,25 +74,29 @@ mgSelection::getCurrentValue() return values[gotoPosition()]; } -MYSQL_RES * -mgSelection::exec_sql (string query) +string +mgSelection::getKeyValue(const unsigned int level) const { - 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); + return order.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) +string mgSelection::get_col0 (string query) const { MYSQL_RES * sql_result = exec_sql (query); if (!sql_result) @@ -239,87 +115,12 @@ string mgSelection::get_col0 (string query) unsigned long -mgSelection::exec_count (string query) +mgSelection::exec_count (string query) const { 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 @@ -331,140 +132,96 @@ mgSelection::sql_string (const string s) const return result; } +string +mgContentItem::getKeyValue(mgKeyTypes kt) +{ + if (m_id<0) + return ""; + switch (kt) { + case keyGenre1: return getGenre1(); + case keyGenre2: return getGenre2(); + 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 keyfield::restrict (string & result) const +mgContentItem * +mgSelection::getTrack (unsigned int position) { - 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; + if (position >= getNumTracks ()) + return NULL; + return &(m_tracks[position]); } -string keyfield::join () const +string mgContentItem::getGenre () const { - string result; - if (need_join ()) - return undequal (result, basefield (), "=", idfield ()); - else - return ""; + return m_genre1; } -bool keyfield::need_join () const +string mgContentItem::getGenre1 () const { - return lookup; + return m_genre1; } -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 +string mgContentItem::getGenre2 () const { - if (m_id == EMPTY) - s << choice () << '/'; - else - s << choice () << '=' << m_id; + return m_genre2; } -const char * -toCString (mgSelection::ShuffleMode m) +string mgContentItem::getBitrate () const { - static const char *modes[] = - { - "SM_NONE", "SM_NORMAL", "SM_PARTY" - }; - return modes[m]; + return m_bitrate; } -string -toString (mgSelection::ShuffleMode m) +string mgContentItem::getImageFile () const { - return toCString (m); + return "Name of Imagefile"; } -//! \brief dump keyfield -ostream & operator<< (ostream & s, keyfield & k) + +string mgContentItem::getAlbum () const { - k.writeAt (s); - return s; + return m_albumtitle; } -string -addsep (string & s, string sep, string n) +int mgContentItem::getYear () const { - if (!n.empty ()) - { - if (!s.empty ()) - s.append (sep); - s.append (n); - } - return s; + return m_year; } -mgContentItem * -mgSelection::getTrack (unsigned int position) +int mgContentItem::getRating () const { - if (position >= getNumTracks ()) - return NULL; - return &(m_tracks[position]); + return m_rating; } -void -mgSelection::loadgenres () +int mgContentItem::getDuration () const { - MYSQL_RES *rows = exec_sql ("select id,genre from genre;"); - if (rows) - { - MYSQL_ROW row; - while ((row = mysql_fetch_row (rows)) != NULL) - { - if (row[0] && row[1]) - genres[row[0]] = row[1]; - } - mysql_free_result (rows); - } + return m_duration; } -string mgContentItem::getGenre1 () +int mgContentItem::getSampleRate () const { - return genres[m_genre1]; + return m_samplerate; } -string mgContentItem::getGenre2 () +int mgContentItem::getChannels () const { - return genres[m_genre2]; + return m_channels; } @@ -476,13 +233,13 @@ mgSelection::ShuffleMode mgSelection::toggleShuffleMode () { case SM_NONE: { - long id = m_tracks[m_tracks_position].getId (); + 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) { - m_tracks_position = i; + setTrackPosition(i); break; } } @@ -491,10 +248,10 @@ mgSelection::ShuffleMode mgSelection::toggleShuffleMode () case SM_NORMAL: { // play all, beginning with current track: - mgContentItem tmp = m_tracks[m_tracks_position]; - m_tracks[m_tracks_position]=m_tracks[0]; + mgContentItem tmp = m_tracks[getTrackPosition()]; + m_tracks[getTrackPosition()]=m_tracks[0]; m_tracks[0]=tmp; - m_tracks_position=0; + setTrackPosition(0); // randomize all other tracks for (unsigned int i = 1; i < tracksize; i++) { @@ -551,9 +308,9 @@ mgSelection::AddToCollection (const string Name) string sql = ""; for (unsigned int i = 0; i < tracksize; i++) { - string value = "(" + listid + "," + ltos (high + 1 + i) + "," + + string item = "(" + listid + "," + ltos (high + 1 + i) + "," + ltos (m_tracks[i].getId ()) + ")"; - comma(sql, value); + comma(sql, item); if ((i%100)==99) { exec_sql (sql_prefix+sql); @@ -570,14 +327,8 @@ 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; + mgParts p = order.Parts(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 (); @@ -588,6 +339,7 @@ mgSelection::RemoveFromCollection (const string Name) 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); @@ -597,9 +349,8 @@ bool mgSelection::DeleteCollection (const string Name) 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)); + string listid = id(keyCollection,Name); + exec_sql ("DELETE FROM playlistitem WHERE playlist="+listid); if (inCollection(Name)) clearCache (); } @@ -628,10 +379,10 @@ string mgSelection::exportM3U () 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 ()); + 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; @@ -640,7 +391,7 @@ string mgSelection::exportM3U () bool mgSelection::empty() { - if (m_level>= keys.size ()-1) + if (m_level>= order.size ()-1) return ( getNumTracks () == 0); else return ( values.size () == 0); @@ -649,24 +400,24 @@ mgSelection::empty() void mgSelection::setPosition (unsigned int position) { - if (m_level < keys.size ()) + if (m_level < order.size ()) m_position[m_level] = position; - if (m_level >= keys.size ()-1) - m_tracks_position = position; + if (m_level >= order.size ()-1) + setTrackPosition(position); } void -mgSelection::setTrack (unsigned int position) +mgSelection::setTrackPosition (unsigned int position) { m_tracks_position = position; } unsigned int -mgSelection::getPosition (unsigned int level) const +mgSelection::getPosition (unsigned int level) const { - if (level == keys.size ()) + if (level == order.size ()) return getTrackPosition(); else return m_position[m_level]; @@ -675,10 +426,10 @@ mgSelection::getPosition (unsigned int level) const 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 ()) + if (level>order.size()) + mgError("mgSelection::gotoPosition: level %u > order.size %u", + level,order.size()); + if (level == order.size ()) return gotoTrackPosition(); else { @@ -694,6 +445,11 @@ mgSelection::gotoPosition (unsigned int level) 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; } @@ -702,9 +458,9 @@ mgSelection::gotoTrackPosition() { unsigned int tracksize = getNumTracks (); if (tracksize == 0) - m_tracks_position = 0; + setTrackPosition(0); else if (m_tracks_position >= tracksize) - m_tracks_position = tracksize -1; + setTrackPosition(tracksize -1); return m_tracks_position; } @@ -715,7 +471,7 @@ bool mgSelection::skipTracks (int steps) return false; if (m_loop_mode == LM_SINGLE) return true; - unsigned int old_pos = getTrackPosition(); + unsigned int old_pos = m_tracks_position; unsigned int new_pos; if (old_pos + steps < 0) { @@ -727,16 +483,11 @@ bool mgSelection::skipTracks (int steps) 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; - } + if (m_loop_mode == LM_NONE) + return false; + new_pos = 0; } - setTrack (new_pos); + setTrackPosition (new_pos); return (new_pos == gotoTrackPosition()); } @@ -753,102 +504,72 @@ mgSelection::getLength () unsigned long -mgSelection::getCompletedLength () +mgSelection::getCompletedLength () const { unsigned long result = 0; tracks (); // make sure they are loaded - for (unsigned int i = 0; i < m_tracks_position; i++) + for (unsigned int i = 0; i < getTrackPosition(); i++) result += m_tracks[i].getDuration (); return result; } -string mgSelection::getListname () + +string mgSelection::getListname () const { string result = ""; for (unsigned int i = 0; i < m_level; i++) - addsep (result, ":", keys[i]->value ()); - if (m_level==keys.size()) - addsep (result,":",getCurrentValue()); + addsep (result, ":", getKeyValue(i)); if (result.empty ()) - result = string(tr(keys[0]->choice ().c_str())); + if (order.size()>0) + result = string(ktName(order.Key(0)->Type ())); 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 + // convert char set ? return res; } -void -mgSelection::AddOrder(const string sql,list<string>& 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; i<keys.size(); i++) - { - AddOrder(sql,orderby,keys[i]->order ()); -} - 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); +mgSelection::tracks () const +{ + if (!m_db) return m_tracks; + if (!m_current_tracks.empty()) return m_tracks; + mgParts p = order.Parts(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; i<order.size(); i++) + p.orders += order.Key(i)->Parts(true).orders; + 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)) != NULL) { - m_tracks.push_back (mgContentItem (row, m_ToplevelDir)); + m_tracks.push_back (mgContentItem (this,row)); } 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; } @@ -870,7 +591,7 @@ mgContentItem::mgContentItem (const mgContentItem* c) m_channels = c->m_channels; } -mgContentItem::mgContentItem (const MYSQL_ROW row, const string ToplevelDir) +mgContentItem::mgContentItem (const mgSelection* sel,const MYSQL_ROW row) { m_id = atol (row[0]); if (row[1]) @@ -878,8 +599,7 @@ mgContentItem::mgContentItem (const MYSQL_ROW row, const string ToplevelDir) else m_title = "NULL"; if (row[2]) - // m_mp3file = ToplevelDir + row[2]; - m_mp3file = row[2]; + m_mp3file = row[2]; else m_mp3file = "NULL"; if (row[3]) @@ -891,11 +611,11 @@ mgContentItem::mgContentItem (const MYSQL_ROW row, const string ToplevelDir) else m_albumtitle = "NULL"; if (row[5]) - m_genre1 = row[5]; + m_genre1 = sel->value(keyGenre1,row[5]); else m_genre1 = "NULL"; if (row[6]) - m_genre2 = row[6]; + m_genre2 = sel->value(keyGenre2,row[5]); else m_genre2 = "NULL"; if (row[7]) @@ -924,29 +644,8 @@ mgContentItem::mgContentItem (const MYSQL_ROW row, const string ToplevelDir) m_channels = 0; }; -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); @@ -955,28 +654,12 @@ void mgSelection::InitSelection() { 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; + setDB(0); m_Host = ""; m_User = ""; m_Password = ""; @@ -986,7 +669,7 @@ mgSelection::mgSelection() mgSelection::mgSelection (const string Host, const string User, const string Password, const bool fall_through) { - m_db = NULL; + setDB(0); m_Host = Host; m_User = User; m_Password = Password; @@ -996,42 +679,44 @@ mgSelection::mgSelection (const string Host, const string User, const string Pas 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::setDB(MYSQL *db) +{ + m_db = db; + order.setDB(db); +} + +void mgSelection::InitFrom(mgValmap& nv) { + setDB(0); 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); + unsigned int v = nv.getuint(idx); free(idx); - if (v.empty()) break; - setKey (i,v ); + if (v==0) break; + setKey (i,mgKeyTypes(v) ); } while (m_level < nv.getuint("Level")) { @@ -1045,8 +730,8 @@ mgSelection::InitFrom(mgValmap& nv) 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")); + if (m_level>=order.size()-1) + setTrackPosition(nv.getlong("TrackPosition")); setShuffleMode(ShuffleMode(nv.getuint("ShuffleMode"))); setLoopMode(LoopMode(nv.getuint("LoopMode"))); } @@ -1054,24 +739,25 @@ mgSelection::InitFrom(mgValmap& nv) mgSelection::~mgSelection () { - mysql_close (m_db); + if (m_db) + { + mgDebug(3,"%X: closing m_db %X",this,m_db); + mysql_close (m_db); + } } void mgSelection::InitFrom(const mgSelection* s) { + setDB(0); 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()); - } + map_values = s->map_values; + map_ids = s->map_ids; + order = s->order; m_level = s->m_level; m_position.reserve (s->m_position.capacity()); for (unsigned int i = 0; i < s->m_position.capacity(); i++) @@ -1092,48 +778,12 @@ const mgSelection& mgSelection::operator=(const mgSelection &s) } -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 () +mgSelection::ordersize () { - return keys.size (); + return order.size (); } - unsigned int mgSelection::valindex (const string val,const bool second_try) { @@ -1153,213 +803,107 @@ mgSelection::valindex (const string val,const bool second_try) } -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 () +mgSelection::refreshValues () const { + if (!m_db) return; + mgOrder o1 = order; if (m_current_values.empty()) { - m_current_values = sql_values(); + mgParts p = order.Parts(m_level); + m_current_values = p.sql_select(); values.strings.clear (); m_ids.clear (); MYSQL_RES *rows = exec_sql (m_current_values); if (rows) { - unsigned int num_fields = mysql_num_fields(rows); + unsigned int num_fields = mysql_num_fields(rows); MYSQL_ROW row; while ((row = mysql_fetch_row (rows)) != NULL) { - string r0,r1; + string r0 = "NULL"; if (row[0]) r0 = row[0]; - else - r0 = "NULL"; - if (row[1]) - r1 = row[1]; - else - r1 = "NULL"; - values.strings.push_back (r0); if (num_fields==2) + { + string r1 = "NULL"; + if (row[1]) + r1 = row[1]; + values.strings.push_back (r0); m_ids.push_back (r1); + } else + { + values.strings.push_back (value(order.Key(m_level),r0)); m_ids.push_back (r0); + } } 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_level<keys.size()-1) result += "DISTINCT "; - result += last->valuefield (); - 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 () +mgSelection::count () const { return values.size (); } - void mgSelection::InitDatabase () { if (m_db) { + mgDebug(3,"%X: InitDatabase closes %X",this,m_db); mysql_close (m_db); - m_db = NULL; + setDB(0); } if (m_Host == "") return; - m_db = mysql_init (0); - if (m_db == NULL) + setDB(mysql_init (0)); + mgDebug(3,"%X: InitDatabase opens %X",this, m_db); + if (!m_db) 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; + setDB(0); 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) +mgSelection::setKey (const unsigned int level, const mgKeyTypes kt) { - keyfield *newkey = findKey(name); - if (newkey == NULL) - mgError("mgSelection::setKey(%u,%s): keyname wrong", - level,name.c_str()); - if (level == 0 && newkey == &kcollection) + mgKey *newkey = ktGenerate(kt,m_db); + if (level == 0 && kt == keyCollection) { - keys.clear (); - keys.push_back (&kcollection); - keys.push_back (&kcollectionitem); + order.clear (); + order += newkey; + order += ktGenerate(keyCollectionItem,m_db); return; } - if (level == keys.size ()) + if (level == order.size ()) { - keys.push_back (newkey); + order += 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); + if (level >= order.size()) + mgError("mgSelection::setKey(%u,%s): level greater than order.size() %u", + level,ktName(kt),order.size()); + delete order[level]; + order[level] = newkey; } -// 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; - } - } + order.clean(); // clear values for this and following levels (needed for copy constructor) - for (unsigned int i = level; i < keys.size (); i++) - keys[i]->set (EMPTY, ""); + for (unsigned int i = level; i < order.size (); i++) + order[i]->set ("",EMPTY); if (m_level > level) m_level = level; @@ -1369,8 +913,8 @@ mgSelection::setKey (const unsigned int level, const string name) bool mgSelection::enter (unsigned int position) { - if (keys.empty()) - mgError("mgSelection::enter(%u): keys is empty", position); + if (order.empty()) + mgWarning("mgSelection::enter(%u): order is empty", position); if (empty()) return false; setPosition (position); @@ -1379,13 +923,14 @@ bool mgSelection::enter (unsigned int 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) + mgDebug(3,"enter(level=%u,pos=%u, id=%s)",m_level,position,id.c_str()); + if (m_level >= order.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()); + 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()); if (m_position.capacity () == m_position.size ()) m_position.reserve (m_position.capacity () + 10); m_position[m_level] = 0; @@ -1394,10 +939,10 @@ bool mgSelection::enter (unsigned int position) if (count () > 1) break; if (count () == 1) - { + { + value = values[0]; id = m_ids[0]; - value = values[0]; - } + } } return true; } @@ -1405,13 +950,15 @@ bool mgSelection::enter (unsigned int position) bool mgSelection::select (unsigned int position) { - mgDebug(2,"select(pos=%u)",position); - if (m_level == keys.size () - 1) + mgDebug(3,"select(%u) on Level %d",position,m_level); + 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; } @@ -1422,9 +969,12 @@ bool mgSelection::select (unsigned int position) bool mgSelection::leave () { - if (keys.empty()) - mgError("mgSelection::leave(): keys is empty"); - if (m_level == keys.size ()) + if (order.empty()) + { + mgWarning("mgSelection::leave(): order is empty"); + return false; + } + if (m_level == order.size ()) { m_level--; m_trackid = -1; @@ -1435,7 +985,8 @@ bool mgSelection::leave () { if (m_level < 1) return false; - keys[--m_level]->set (EMPTY, ""); + order[--m_level]->set ("",EMPTY); + clearCache(); if (!m_fall_through) break; if (count () > 1) @@ -1444,81 +995,141 @@ bool mgSelection::leave () return true; } +string +mgSelection::value(mgKeyTypes kt, string id) const +{ + if (kt==keyGenre2) kt = keyGenre1; + if (loadvalues (kt)) + { + map<string,string>& valmap = map_values[kt]; + map<string,string>::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); +} -bool mgSelection::UsedBefore (keyfield const *k, unsigned int level) +string +mgSelection::value(mgKey* k) const +{ + return value(k,k->id()); +} + +string +mgSelection::id(mgKeyTypes kt, string val) const { - if (level >= keys.size ()) - level = keys.size () - 1; - for (unsigned int i = 0; i < level; i++) - if (keys[i] == k) - return true; - return false; + if (kt==keyGenre2) kt = keyGenre1; + if (loadvalues (kt)) + { + map<string,string>& idmap = map_ids[kt]; + return idmap[val]; + } + else + return val; } +string +mgSelection::id(mgKey* k, string val) const +{ + return id(k->Type(),val); +} -bool mgSelection::isCollectionlist () +string +mgSelection::id(mgKey* k) const { - return (keys[0] == &kcollection && m_level == 0); + return k->id(); } bool -mgSelection::inCollection(const string Name) +mgSelection::UsedBefore(const mgKeyTypes kt,unsigned int level) const { - bool result = (keys[0] == &kcollection && m_level == 1); + if (level>=order.size()) + level = order.size() -1; + for (unsigned int lx = 0; lx < level; lx++) + if (order.Key(lx)->Type()==kt) + return true; + return false; +} + + +bool mgSelection::isCollectionlist () const +{ + if (order.size()==0) return false; + return (order.Key(0)->Type() == keyCollection && m_level == 0); +} + +bool +mgSelection::inCollection(const string Name) const +{ + if (order.size()==0) return false; + bool result = (order.Key(0)->Type() == keyCollection && m_level == 1); if (result) - if (keys[1] != &kcollectionitem) - mgError("inCollection: key[1] is not kcollectionitem"); + if (order.Key(1)->Type() != keyCollectionItem) + mgError("inCollection: key[1] is not keyCollectionItem"); if (!Name.empty()) - result &= (keys[0]->value() == Name); + result &= (order.getKeyValue(0) == Name); return result; } -const strvector & -mgSelection::keychoice (const unsigned int level) +#if 0 +void +keychoice(mgOrder& o,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++) + if (level > o.size ()) + return; + std::cout<<"possible choices:"; + for (mgKeyTypes kt = mgKeyTypes(1); kt <= mgKeyTypesHigh; kt = mgKeyTypes(int(kt)+1)) { - 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()))); + if (level !=0 && kt == keyCollection) + continue; + if (level !=1 && kt == keyCollectionItem) + continue; + if (level == 1 && o[0]->Type() != keyCollection && kt == keyCollectionItem) + continue; + if (level == 1 && o[0]->Type() == keyCollection && kt != keyCollectionItem) + continue; + if (level >1 && o[0]->Type() == keyCollection) + break; + if (kt == keyDecade && UsedBefore(o,keyYear,level)) + continue; + if (o[0]->Type() == keyCollection) + { + std::cout<<" "<<ktName(kt); + } + else if (!UsedBefore(o,kt,level)) + { + if (keycounts[kt]==-1) + { + mgOrder oc(db); + oc += ktGenerate(kt,db); + keycounts[kt]=atol(get_col0(oc.Parts(0).sql_count()).c_str()); + } + if (keycounts[kt]>1) + std::cout<<" "<<ktName(kt)<<"("<<keycounts[kt]<<")"; + } } - return m_keychoice; + std::cout<<endl; } +#endif - -void mgSelection::DumpState(mgValmap& nv) +void mgSelection::DumpState(mgValmap& nv) const { nv.put("Host",m_Host); nv.put("User",m_User); @@ -1527,40 +1138,74 @@ void mgSelection::DumpState(mgValmap& nv) 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;i<keys.size();i++) + for (unsigned int i=0;i<order.size();i++) { char *n; asprintf(&n,"Keys.%d.Choice",i); - nv.put(n,keys[i]->choice()); - asprintf(&n,"Keys.%d.Filter",i); - nv.put(n,keys[i]->filter()); + nv.put(n,int(order.Key(i)->Type())); + free(n); if (i<m_level) { asprintf(&n,"Keys.%d.Position",i); nv.put(n,m_position[i]); + free(n); } } nv.put("TrackId",m_trackid); - if (m_level == keys.size ()) - nv.put("Position",m_tracks_position); + if (m_level == order.size ()) + nv.put("Position",getTrackPosition()); else nv.put("Position",m_position[m_level]); - nv.put("TrackPosition",m_tracks_position); + nv.put("TrackPosition",getTrackPosition()); } -map <string, string> * -mgSelection::UsedKeyValues() +map <mgKeyTypes, string> * +mgSelection::UsedKeyValues() { - map <string, string> *result = new map<string, string>; + map <mgKeyTypes, string> *result = new map<mgKeyTypes, string>; for (unsigned int idx = 0 ; idx < level() ; idx++) { - (*result)[keys[idx]->choice()] = keys[idx]->value(); + (*result)[order.Key(idx)->Type()] = order.getKeyValue(idx); } - if (level() < keys.size()-1) + if (level() < order.size()-1) { - string ch = keys[level()]->choice(); + mgKeyTypes ch = order.getKeyType(level()); (*result)[ch] = getCurrentValue(); } return result; } + +bool +mgSelection::loadvalues (mgKeyTypes kt) const +{ + mgKey* k = ktGenerate(kt,m_db); + if (k->map_idfield().empty()) + { + delete k; + return false; + } + map<string,string>& idmap = map_ids[kt]; + if (!idmap.empty()) + { + delete k; + return true; + } + map<string,string>& 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)) != NULL) + { + if (row[0] && row[1]) + valmap[row[0]] = row[1]; + idmap[row[1]] = row[0]; + } + mysql_free_result (rows); + } + delete k; + return true; +} |