#include "mg_order.h" #include "mg_tools.h" #include "i18n.h" #include #include mgSelItem zeroitem; bool iskeyGenre(mgKeyTypes kt) { return kt>=keyGenre1 && kt <= keyGenres; } class mgRefParts : public mgParts { public: mgRefParts(const mgReference& r); }; strlist& operator+=(strlist&a, strlist b) { a.insert(a.end(), b.begin(),b.end()); return a; } /*! \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& addsep (string & s, string sep, string n) { if (!n.empty ()) { if (!s.empty ()) s.append (sep); s.append (n); } return s; } static string sql_list (string prefix,strlist v,string sep=",",string postfix="") { string result = ""; for (list < string >::iterator it = v.begin (); it != v.end (); ++it) { addsep (result, sep, *it); } if (!result.empty()) { result.insert(0," "+prefix+" "); result += postfix; } 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 (); } mgSelItem::mgSelItem() { m_valid=false; m_count=0; } mgSelItem::mgSelItem(string v,string i,unsigned int c) { set(v,i,c); } void mgSelItem::set(string v,string i,unsigned int c) { m_valid=true; m_value=v; m_id=i; m_count=c; } void mgSelItem::operator=(const mgSelItem& from) { m_valid=from.m_valid; m_value=from.m_value; m_id=from.m_id; m_count=from.m_count; } void mgSelItem::operator=(const mgSelItem* from) { m_valid=from->m_valid; m_value=from->m_value; m_id=from->m_id; m_count=from->m_count; } bool mgSelItem::operator==(const mgSelItem& other) const { return m_value == other.m_value && m_id == other.m_id; } class mgKeyNormal : public mgKey { public: mgKeyNormal(const mgKeyNormal& k); mgKeyNormal(const mgKeyTypes kt, string table, string field); virtual mgParts Parts(mgmySql &db,bool orderby=false) const; string value() const; string id() const; bool valid() const; void set(mgSelItem& item); mgSelItem& get(); mgKeyTypes Type() const { return m_kt; } virtual string expr() const { return m_table + "." + m_field; } virtual string table() const { return m_table; } protected: string IdClause(mgmySql &db,string what,string::size_type start=0,string::size_type len=string::npos) const; void AddIdClause(mgmySql &db,mgParts &result,string what) const; mgSelItem m_item; string m_field; private: mgKeyTypes m_kt; string m_table; }; class mgKeyABC : public mgKeyNormal { public: mgKeyABC(const mgKeyNormal& k) : mgKeyNormal(k) {} mgKeyABC(const mgKeyTypes kt, string table, string field) : mgKeyNormal(kt,table,field) {} virtual string expr() const { return "substring("+mgKeyNormal::expr()+",1,1)"; } protected: //void AddIdClause(mgmySql &db,mgParts &result,string what) const; }; class mgKeyDate : public mgKeyNormal { public: mgKeyDate(mgKeyTypes kt,string table, string field) : mgKeyNormal(kt,table,field) {} }; class mgKeyTrack : public mgKeyNormal { public: mgKeyTrack() : mgKeyNormal(keyTrack,"tracks","tracknb") {}; mgParts Parts(mgmySql &db,bool orderby=false) const; }; class mgKeyAlbum : public mgKeyNormal { public: mgKeyAlbum() : mgKeyNormal(keyAlbum,"album","title") {}; mgParts Parts(mgmySql &db,bool orderby=false) const; }; mgParts mgKeyAlbum::Parts(mgmySql &db,bool orderby) const { mgParts result = mgKeyNormal::Parts(db,orderby); result.tables.push_back("tracks"); return result; } class mgKeyFolder : public mgKeyNormal { public: mgKeyFolder(mgKeyTypes kt,const char *fname) : mgKeyNormal(kt,"tracks",fname) { m_enabled=-1;}; bool Enabled(mgmySql &db); private: int m_enabled; }; class mgKeyFolder1 : public mgKeyFolder { public: mgKeyFolder1() : mgKeyFolder(keyFolder1,"folder1") {}; }; class mgKeyFolder2 : public mgKeyFolder { public: mgKeyFolder2() : mgKeyFolder(keyFolder2,"folder2") {}; }; class mgKeyFolder3 : public mgKeyFolder { public: mgKeyFolder3() : mgKeyFolder(keyFolder3,"folder3") {}; }; class mgKeyFolder4 : public mgKeyFolder { public: mgKeyFolder4() : mgKeyFolder(keyFolder4,"folder4") {}; }; bool mgKeyFolder::Enabled(mgmySql &db) { if (m_enabled<0) { if (!db.Connected()) return false; char *b; asprintf(&b,"DESCRIBE tracks %s",m_field.c_str()); MYSQL_RES * rows = db.exec_sql (b); free(b); if (rows) { m_enabled = mysql_num_rows(rows); mysql_free_result (rows); } } return (m_enabled==1); } class mgKeyGenres : public mgKeyNormal { public: mgKeyGenres() : mgKeyNormal(keyGenres,"tracks","genre1") {}; mgKeyGenres(mgKeyTypes kt) : mgKeyNormal(kt,"tracks","genre1") {}; mgParts Parts(mgmySql &db,bool orderby=false) const; string map_idfield() const { return "id"; } string map_valuefield() const { return "genre"; } string map_valuetable() const { return "genre"; } protected: virtual unsigned int genrelevel() const { return 4; } private: string GenreClauses(mgmySql &db,bool orderby) const; }; class mgKeyGenre1 : public mgKeyGenres { public: mgKeyGenre1() : mgKeyGenres(keyGenre1) {} unsigned int genrelevel() const { return 1; } }; class mgKeyGenre2 : public mgKeyGenres { public: mgKeyGenre2() : mgKeyGenres(keyGenre2) {} unsigned int genrelevel() const { return 2; } }; class mgKeyGenre3 : public mgKeyGenres { public: mgKeyGenre3() : mgKeyGenres(keyGenre3) {} unsigned int genrelevel() const { return 3; } }; string mgKeyGenres::GenreClauses(mgmySql &db,bool orderby) const { strlist g1; strlist g2; if (orderby) if (genrelevel()==4) { g1.push_back("tracks.genre1=genre.id"); g2.push_back("tracks.genre2=genre.id"); } else { g1.push_back("substring(tracks.genre1,1,"+ltos(genrelevel())+")=genre.id"); g2.push_back("substring(tracks.genre2,1,"+ltos(genrelevel())+")=genre.id"); } if (valid()) { unsigned int len=genrelevel(); if (len==4) len=0; g1.push_back(IdClause(db,"tracks.genre1",0,genrelevel())); g2.push_back(IdClause(db,"tracks.genre2",0,genrelevel())); } extern bool needGenre2; if (needGenre2) { string o1=sql_list("(",g1," AND ",")"); if (o1.empty()) return ""; string o2=sql_list("(",g2," AND ",")"); return string("(") + o1 + " OR " + o2 + string(")"); } else return sql_list("",g1," AND "); } mgParts mgKeyGenres::Parts(mgmySql &db,bool orderby) const { mgParts result; result.clauses.push_back(GenreClauses(db,orderby)); result.tables.push_back("tracks"); if (orderby) { result.fields.push_back("genre.genre"); result.fields.push_back("genre.id"); result.tables.push_back("genre"); result.orders.push_back("genre.genre"); } return result; } class mgKeyLanguage : public mgKeyNormal { public: mgKeyLanguage() : mgKeyNormal(keyLanguage,"tracks","lang") {}; mgParts Parts(mgmySql &db,bool orderby=false) const; string map_idfield() const { return "id"; } string map_valuefield() const { return "language"; } string map_valuetable() const { return "language"; } }; class mgKeyCollection: public mgKeyNormal { public: mgKeyCollection() : mgKeyNormal(keyCollection,"playlist","id") {}; mgParts Parts(mgmySql &db,bool orderby=false) const; string map_idfield() const { return "id"; } string map_valuefield() const { return "title"; } string map_valuetable() const { return "playlist"; } }; class mgKeyCollectionItem : public mgKeyNormal { public: mgKeyCollectionItem() : mgKeyNormal(keyCollectionItem,"playlistitem","tracknumber") {}; mgParts Parts(mgmySql &db,bool orderby=false) const; }; class mgKeyDecade : public mgKeyNormal { public: mgKeyDecade() : mgKeyNormal(keyDecade,"tracks","year") {} string expr() const { return "substring(convert(10 * floor(tracks.year/10), char),3)"; } }; string mgKeyNormal::id() const { return m_item.id(); } bool mgKeyNormal::valid() const { return m_item.valid(); } string mgKeyNormal::value() const { return m_item.value(); } mgKeyNormal::mgKeyNormal(const mgKeyNormal& k) { m_kt = k.m_kt; m_table = k.m_table; m_field = k.m_field; m_item = k.m_item; } mgKeyNormal::mgKeyNormal(const mgKeyTypes kt, string table, string field) { m_kt = kt; m_table = table; m_field = field; } void mgKeyNormal::set(mgSelItem& item) { m_item=item; } mgSelItem& mgKeyNormal::get() { return m_item; } mgParts::mgParts() { m_sql_select=""; orderByCount = false; } mgParts::~mgParts() { } mgParts mgKeyNormal::Parts(mgmySql &db, bool orderby) const { mgParts result; result.tables.push_back(table()); AddIdClause(db,result,expr()); if (orderby) { result.fields.push_back(expr()); result.orders.push_back(expr()); } return result; } string mgKeyNormal::IdClause(mgmySql &db,string what,string::size_type start,string::size_type len) const { if (len==0) len=string::npos; if (id() == "'NULL'") return what + " is NULL"; else if (len==string::npos) return what + "=" + db.sql_string(id()); else { return "substring("+what + ","+ltos(start+1)+","+ltos(len)+")=" + db.sql_string(id().substr(start,len)); } } void mgKeyNormal::AddIdClause(mgmySql &db,mgParts &result,string what) const { if (valid()) result.clauses.push_back(IdClause(db,what)); } mgParts mgKeyTrack::Parts(mgmySql &db,bool orderby) const { mgParts result; result.tables.push_back("tracks"); AddIdClause(db,result,"tracks.title"); if (orderby) { // if you change tracks.title, please also // change mgContentItem::getKeyItem() result.fields.push_back("tracks.title"); result.orders.push_back("tracks.tracknb"); } return result; } mgParts mgKeyLanguage::Parts(mgmySql &db,bool orderby) const { mgParts result; AddIdClause(db,result,"tracks.lang"); result.tables.push_back("tracks"); if (orderby) { result.fields.push_back("language.language"); result.fields.push_back("tracks.lang"); result.tables.push_back("language"); result.orders.push_back("language.language"); } return result; } mgParts mgKeyCollection::Parts(mgmySql &db,bool orderby) const { mgParts result; if (orderby) { result.tables.push_back("playlist"); AddIdClause(db,result,"playlist.id"); result.fields.push_back("playlist.title"); result.fields.push_back("playlist.id"); result.orders.push_back("playlist.title"); } else { result.tables.push_back("playlistitem"); AddIdClause(db,result,"playlistitem.playlist"); } return result; } mgParts mgKeyCollectionItem::Parts(mgmySql &db,bool orderby) const { mgParts result; result.tables.push_back("playlistitem"); AddIdClause(db,result,"playlistitem.tracknumber"); if (orderby) { // tracks nur hier, fuer sql_delete_from_coll wollen wir es nicht result.tables.push_back("tracks"); result.fields.push_back("tracks.title"); result.fields.push_back("playlistitem.tracknumber"); result.orders.push_back("playlistitem.tracknumber"); } return result; } mgParts& mgParts::operator+=(mgParts a) { fields += a.fields; tables += a.tables; clauses += a.clauses; orders += a.orders; return *this; } mgRefParts::mgRefParts(const mgReference& r) { tables.push_back(r.t1()); tables.push_back(r.t2()); clauses.push_back(r.t1() + '.' + r.f1() + '=' + r.t2() + '.' + r.f2()); } void mgParts::Prepare() { tables.sort(); tables.unique(); strlist::reverse_iterator it; string prevtable = ""; for (it = tables.rbegin(); it != tables.rend(); ++it) { if (!prevtable.empty()) *this += ref.Connect(prevtable,*it); prevtable = *it; } tables.sort(); tables.unique(); clauses.sort(); clauses.unique(); orders.unique(); } string mgParts::sql_select(bool distinct) { if (!m_sql_select.empty()) return m_sql_select; Prepare(); string result; if (distinct) { fields.push_back("COUNT(*) AS mgcount"); result = sql_list("SELECT",fields); fields.pop_back(); } else result = sql_list("SELECT",fields); if (result.empty()) return result; result += sql_list("FROM",tables); result += sql_list("WHERE",clauses," AND "); if (distinct) { result += sql_list("GROUP BY",fields); if (orderByCount) orders.insert(orders.begin(),"mgcount desc"); } result += sql_list("ORDER BY",orders); optimize(result); return result; } string mgParts::sql_count() { Prepare(); string result = sql_list("SELECT COUNT(DISTINCT",fields,",",")"); if (result.empty()) return result; result += sql_list("FROM",tables); result += sql_list("WHERE",clauses," AND "); optimize(result); return result; } bool mgParts::UsesTracks() { for (list < string >::iterator it = tables.begin (); it != tables.end (); ++it) if (*it == "tracks") return true; return false; } string mgParts::sql_delete_from_collection(string pid) { if (pid.empty()) return ""; Prepare(); // del nach vorne, weil DELETE playlistitem die erste Table nimmt, // die passt, egal ob alias oder nicht. tables.push_front("playlistitem as del"); clauses.push_back("del.playlist="+pid); // todo geht so nicht fuer andere selections if (UsesTracks()) clauses.push_back("del.trackid=tracks.id"); else clauses.push_back("del.trackid=playlistitem.trackid"); string result = "DELETE playlistitem"; result += sql_list(" FROM",tables); result += sql_list(" WHERE",clauses," AND "); optimize(result); return result; } string mgParts::sql_update(strlist new_values) { Prepare(); assert(fields.size()==new_values.size()); string result = sql_list("UPDATE",fields); result += sql_list(" FROM",tables); result += sql_list(" WHERE",clauses," AND "); result += sql_list("VALUES(",new_values,",",")"); optimize(result); return result; } mgReference::mgReference(string t1,string f1,string t2,string f2) { m_t1 = t1; m_f1 = f1; m_t2 = t2; m_f2 = f2; } mgOrder::mgOrder() { clear(); setKey (keyArtist); setKey (keyAlbum); setKey (keyTrack); m_orderByCount = false; } mgOrder::~mgOrder() { truncate(0); } mgKey* mgOrder::Key(unsigned int idx) const { return Keys[idx]; } mgKey*& mgOrder::operator[](unsigned int idx) { assert(idxType()==b.Key(i)->Type(); if (!result) break; } return result; } const mgOrder& mgOrder::operator=(const mgOrder& from) { clear(); InitFrom(from); return *this; } mgOrder::mgOrder(const mgOrder &from) { InitFrom(from); } void mgOrder::InitFrom(const mgOrder &from) { for (unsigned int i = 0; i < from.size();i++) { mgKey *k = ktGenerate(from.getKeyType(i)); k->set(from.getKeyItem(i)); Keys.push_back(k); } m_orderByCount=from.m_orderByCount; } string mgOrder::Name() { string result=""; for (unsigned int idx=0;idxType()); } return result; } void mgOrder::setKey (const mgKeyTypes kt) { mgKey *newkey = ktGenerate(kt); if (newkey) Keys.push_back(newkey); } mgOrder::mgOrder(mgValmap& nv,char *prefix) { char *idx; asprintf(&idx,"%s.OrderByCount",prefix); m_orderByCount = nv.getbool(idx); free(idx); clear(); for (unsigned int i = 0; i < 999 ; i++) { asprintf(&idx,"%s.Keys.%u.Type",prefix,i); unsigned int v = nv.getuint(idx); free(idx); if (v==0) break; setKey (mgKeyTypes(v) ); } if (size()>0) clean(); } void mgOrder::DumpState(mgValmap& nv, char *prefix) const { char n[100]; sprintf(n,"%s.OrderByCount",prefix); nv.put(n,m_orderByCount); for (unsigned int i=0;iType())); } } mgOrder::mgOrder(vector kt) { m_orderByCount = false; setKeys(kt); } void mgOrder::setKeys(vector kt) { clear(); for (unsigned int i=0;iType(); } mgSelItem& mgOrder::getKeyItem(unsigned int idx) const { assert(idxget(); } void mgOrder::truncate(unsigned int i) { while (size()>i) { delete Keys.back(); Keys.pop_back(); } } void mgOrder::clear() { truncate(0); } void mgOrder::clean() { // remove double entries: keyvector::iterator i; keyvector::iterator j; bool collection_found = false; bool collitem_found = false; bool album_found = false; bool tracknb_found = false; bool title_found = false; bool is_unique = false; for (i = Keys.begin () ; i != Keys.end (); ++i) { mgKeyNormal* k = dynamic_cast(*i); collection_found |= (k->Type()==keyCollection); collitem_found |= (k->Type()==keyCollectionItem); album_found |= (k->Type()==keyAlbum); tracknb_found |= (k->Type()==keyTrack); title_found |= (k->Type()==keyTitle); is_unique = tracknb_found || (album_found && title_found) || (collection_found && collitem_found); if (is_unique) { for (j = i+1 ; j !=Keys.end(); ++j) delete *j; Keys.erase(i+1,Keys.end ()); break; } if (k->Type()==keyYear) { for (j = i+1 ; j != Keys.end(); ++j) if ((*j)->Type() == keyDecade) { delete *j; Keys.erase(j); break; } } cleanagain: for (j = i+1 ; j != Keys.end(); ++j) if ((*i)->Type() == (*j)->Type()) { delete *j; Keys.erase(j); goto cleanagain; } } if (!is_unique) { if (!album_found) Keys.push_back(ktGenerate(keyAlbum)); if (!title_found) Keys.push_back(ktGenerate(keyTitle)); } } bool mgOrder::isCollectionOrder() const { return (size()==2 && (Keys[0]->Type()==keyCollection) && (Keys[1]->Type()==keyCollectionItem)); } mgParts mgOrder::Parts(mgmySql &db,unsigned int level,bool orderby) const { mgParts result; result.orderByCount = m_orderByCount; if (level==0 && isCollectionOrder()) { // sql command contributed by jarny result.m_sql_select = string("select playlist.title,playlist.id, " "count(*) * (playlistitem.playlist is not null) from playlist " "left join playlistitem on playlist.id = playlistitem.playlist " "group by playlist.title"); return result; } for (unsigned int i=0;i<=level;i++) { if (i==Keys.size()) break; mgKeyNormal *k = dynamic_cast(Keys[i]); mgKeyTypes kt = k->Type(); if (iskeyGenre(kt)) { for (unsigned int j=i+1;j<=level;j++) { if (j>=Keys.size()) break; mgKeyNormal *kn = dynamic_cast(Keys[j]); if (kn) { mgKeyTypes knt = kn->Type(); if (iskeyGenre(knt) && knt>kt && !kn->id().empty()) goto next; } } } result += k->Parts(db,orderby && (i==level)); next: continue; } return result; } //! \brief right now thread locking should not be needed here mgReferences::mgReferences() { push_back(mgReference ("tracks","id","playlistitem","trackid")); push_back(mgReference ("playlist","id","playlistitem","playlist")); push_back(mgReference ("tracks","sourceid","album","cddbid")); push_back(mgReference ("tracks","lang","language","id")); } bool mgReferences::Equal(unsigned int i,string table1, string table2) const { const mgReference& r = operator[](i); string s1 = r.t1(); string s2 = r.t2(); return ((s1==table1) && (s2==table2)) || ((s1==table2) && (s2==table1)); } mgParts mgReferences::FindConnectionBetween(string table1, string table2) const { for (unsigned int i=0 ; i ktNames() { static vector result; for (unsigned int i = int(mgKeyTypesLow); i <= int(mgKeyTypesHigh); i++) result.push_back(ktName(mgKeyTypes(i))); return result; }