diff options
-rw-r--r-- | Makefile | 10 | ||||
-rw-r--r-- | README | 4 | ||||
-rw-r--r-- | i18n.c | 187 | ||||
-rw-r--r-- | mg_actions.c | 690 | ||||
-rw-r--r-- | mg_actions.h | 81 | ||||
-rw-r--r-- | mg_database.h | 1 | ||||
-rw-r--r-- | mg_db.c | 1137 | ||||
-rw-r--r-- | mg_db.h | 642 | ||||
-rw-r--r-- | mg_filters.c | 297 | ||||
-rw-r--r-- | mg_filters.h | 171 | ||||
-rw-r--r-- | mg_order.c | 775 | ||||
-rw-r--r-- | mg_order.h | 154 | ||||
-rw-r--r-- | mg_valmap.c | 70 | ||||
-rw-r--r-- | mg_valmap.h | 50 | ||||
-rw-r--r-- | muggle.c | 1 | ||||
-rw-r--r-- | vdr_decoder.c | 49 | ||||
-rw-r--r-- | vdr_decoder.h | 150 | ||||
-rw-r--r-- | vdr_decoder_flac.c | 380 | ||||
-rw-r--r-- | vdr_decoder_flac.h | 80 | ||||
-rw-r--r-- | vdr_menu.c | 325 | ||||
-rw-r--r-- | vdr_menu.h | 78 | ||||
-rw-r--r-- | vdr_player.c | 30 |
22 files changed, 2997 insertions, 2365 deletions
@@ -44,18 +44,16 @@ PACKAGE = vdr-$(ARCHIVE) INCLUDES += -I$(VDRDIR) -I$(VDRDIR)/include -I$(DVBDIR)/include \ -I/usr/include/mysql/ -I/usr/include/taglib -DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"' -DHAVE_VORBISFILE -# DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"' -DEFINES += -D_GNU_SOURCE +DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"' -DHAVE_VORBISFILE -DHAVE_FLAC MIFLAGS += -I/usr/include/taglib -lmysqlclient ### The object files (add further files here): -OBJS = $(PLUGIN).o i18n.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_db.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 vdr_decoder_ogg.o + vdr_setup.o vdr_decoder_ogg.o vdr_decoder_flac.o -LIBS = -lmad -lmysqlclient -lvorbisfile -lvorbis +LIBS = -lmad -lmysqlclient -lvorbisfile -lvorbis -lFLAC++ MILIBS = -lmysqlclient -ltag ### Targets: @@ -34,8 +34,8 @@ parameters are descibed in Section 5. \section prereq PREREQUISITES -The plugin currently runs on versions 1.3.17- of VDR. It also compiles on 1.3.18 but -your mileage may vary. In addition, the following pieces of software are required: +The plugin currently runs on versions 1.3.17- of VDR (including 1.2.6). It also compiles on 1.3.18 +but your mileage may vary. In addition, the following pieces of software are required: - mySQL server (tested with 4.0.18) (Debian packages mysql-server, mysql-client) - mySQL client libraries @@ -19,7 +19,24 @@ const tI18nPhrase Phrases[] = "", // TODO "", // TODO "", // TODO - "Chercher", // TODO + "Rechercher", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Order", + "Sortierung", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Ordre", // TODO "", // TODO "", // TODO "", // TODO @@ -47,6 +64,40 @@ const tI18nPhrase Phrases[] = "", // TODO }, { + "Clear the collection?", + "Sammlung leeren?", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Vider la collection?", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Delete the collection?", + "Sammlung löschen?", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Effacer la collection?", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { "Create collection", "Sammlung neu anlegen", "", // TODO @@ -64,7 +115,7 @@ const tI18nPhrase Phrases[] = "", // TODO }, { - "Delete collection", + "Delete the collection", "Sammlung löschen", "", // TODO "", // TODO @@ -285,6 +336,23 @@ const tI18nPhrase Phrases[] = "", // TODO }, { + "Export", + "Exportieren", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Exporter", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { "Export track list", "Stückliste exportieren", "", // TODO @@ -523,13 +591,13 @@ const tI18nPhrase Phrases[] = "", // TODO }, { - "Set default to collection '%s'", - "Setze Ziel auf Sammlung '%s'", + "'%s' to collection", + "'%s' zu Sammlung", "", // TODO "", // TODO "", // TODO "", // TODO - "Changer destination à la collection '%s'", // TODO + "Ajoute '%s' à une collection", // TODO "", // TODO "", // TODO "", // TODO @@ -540,13 +608,13 @@ const tI18nPhrase Phrases[] = "", // TODO }, { - "Default collection now is '%s'", - "Zielsammlung ist nun '%s'", + "Set default to collection '%s'", + "Setze Ziel auf Sammlung '%s'", "", // TODO "", // TODO "", // TODO "", // TODO - "La collection destinataire est maintenant '%s'", // TODO + "Changer destination à la collection '%s'", // TODO "", // TODO "", // TODO "", // TODO @@ -557,13 +625,13 @@ const tI18nPhrase Phrases[] = "", // TODO }, { - "Add", - "Hinzu", + "Default collection now is '%s'", + "Zielsammlung ist nun '%s'", "", // TODO "", // TODO "", // TODO "", // TODO - "Ajouter", // TODO + "La collection destinataire est maintenant '%s'", // TODO "", // TODO "", // TODO "", // TODO @@ -574,13 +642,13 @@ const tI18nPhrase Phrases[] = "", // TODO }, { - "Add to '%s'", - "Zu '%s' hinzufügen", + "Add", + "Hinzu", "", // TODO "", // TODO "", // TODO "", // TODO - "Ajouter à '%s'", // TODO + "Ajouter", // TODO "", // TODO "", // TODO "", // TODO @@ -591,13 +659,13 @@ const tI18nPhrase Phrases[] = "", // TODO }, { - "Add '%s' to '%s'", - "'%s' zu '%s' hinzufügen", + "Add to a collection", + "Zu einer Sammlung hinzufügen", "", // TODO "", // TODO "", // TODO "", // TODO - "Ajouter '%s' à '%s'", // TODO + "Ajouter à une collection", // TODO "", // TODO "", // TODO "", // TODO @@ -608,13 +676,13 @@ const tI18nPhrase Phrases[] = "", // TODO }, { - "Add all to '%s'", - "Alles '%s' hinzufügen", + "Add to '%s'", + "Zu '%s' hinzufügen", "", // TODO "", // TODO "", // TODO "", // TODO - "Ajouter tout à '%s'", // TODO + "Ajouter à '%s'", // TODO "", // TODO "", // TODO "", // TODO @@ -626,29 +694,12 @@ const tI18nPhrase Phrases[] = }, { "Remove", - "Entfernen", - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "Effacer", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - }, - { - "Remove from '%s'", - "Aus '%s' entfernen", + "Weg", "", // TODO "", // TODO "", // TODO "", // TODO - "Effacer de '%s'", // TODO + "Effacer", // TODO "", // TODO "", // TODO "", // TODO @@ -659,13 +710,13 @@ const tI18nPhrase Phrases[] = "", // TODO }, { - "Remove '%s' from '%s'", - "'%s' aus '%s' entfernen", + "Clear", + "Leeren", "", // TODO "", // TODO "", // TODO "", // TODO - "Effacer '%s' de '%s'", // TODO + "Vider", // TODO "", // TODO "", // TODO "", // TODO @@ -676,13 +727,13 @@ const tI18nPhrase Phrases[] = "", // TODO }, { - "Remove all entries from '%s'", - "Alle Einträge aus '%s' entfernen", + "Clear the collection", + "Sammlung leeren", "", // TODO "", // TODO "", // TODO "", // TODO - "Effacer tout de '%s'", // TODO + "Vider la collection", // TODO "", // TODO "", // TODO "", // TODO @@ -693,13 +744,13 @@ const tI18nPhrase Phrases[] = "", // TODO }, { - "Remove all from '%s'", - "Alles aus '%s' entfernen", + "Remove from a collection", + "Aus einer Sammlung entfernen", "", // TODO "", // TODO "", // TODO "", // TODO - "Effacer tout de '%s'", // TODO + "Effacer d'une collection", // TODO "", // TODO "", // TODO "", // TODO @@ -965,23 +1016,6 @@ const tI18nPhrase Phrases[] = "", // TODO }, { - "Tree View Selection", - "Suchschema wählen", - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "Choisir le schéma de recherche", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - }, - { "Title -> Album -> Track", "Titel -> Album -> Track", "", // TODO @@ -1033,23 +1067,6 @@ const tI18nPhrase Phrases[] = "", // TODO }, { - "Search", - "Suche", - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "Chercher", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - }, - { "play", "spielen", "", // TODO @@ -1118,13 +1135,13 @@ const tI18nPhrase Phrases[] = "", // TODO }, { - "Select search order", - "Suchschema wählen", + "Select an order", + "Sortierung wählen", "", // TODO "", // TODO "", // TODO "", // TODO - "Choisir le schéma de recherchage", // TODO + "Choisir un ordre", // TODO "", // TODO "", // TODO "", // TODO diff --git a/mg_actions.c b/mg_actions.c index 502b1f6..48b06f3 100644 --- a/mg_actions.c +++ b/mg_actions.c @@ -25,27 +25,163 @@ #define DEBUG #include "mg_tools.h" +#include "mg_order.h" + +static bool +IsEntry(mgActions i) +{ + return i == actEntry; +} + +class mgOsdItem : public mgAction, public cOsdItem +{ + public: + eOSState ProcessKey(eKeys key); + protected: + virtual eOSState Process(eKeys key) { return osUnknown; } +}; + +eOSState +mgOsdItem::ProcessKey(eKeys key) +{ + eOSState result = Process(key); + if (result != osUnknown) + IgnoreNextEvent = true; + return result; +} + +//! \brief used for normal data base items +class mgEntry : public mgOsdItem +{ + public: + void Notify(); + bool Enabled(mgActions on) { return IsEntry(on);} + const char *ButtonName() { return ""; } + const char *MenuName (const unsigned int idx,const string value); + virtual eOSState Process(eKeys key); + void Execute(); + eOSState Back(); +}; + +class mgDoCollEntry : public mgEntry +{ + public: + virtual eOSState Process(eKeys key); + protected: + string getTarget(); +}; + +class mgAddCollEntry : public mgDoCollEntry +{ + public: + void Execute(); +}; + +class mgRemoveCollEntry : public mgDoCollEntry +{ + public: + void Execute(); +}; + +void +mgAction::TryNotify() +{ + if (IgnoreNextEvent) + IgnoreNextEvent = false; + else + Notify(); +} + +eOSState +mgDoCollEntry::Process(eKeys key) +{ + mgMenu *n = osd ()->newmenu; + osd ()->newmenu = NULL; + eOSState result = osContinue; + switch (key) + { + case kBlue: + case kBack: + break; + case kOk: + Execute (); + break; + default: + osd ()->newmenu = n; // wrong key: stay in submenu + result = osUnknown; + break; + } + return result; +} + +string +mgDoCollEntry::getTarget() +{ + string result = cOsdItem::Text(); + if (result[0]==' ') + result.erase(0,5); + else + result.erase(0,3); + return result; +} + +void +mgAddCollEntry::Execute() +{ + string target = getTarget(); + osd()->default_collection = target; + if (target == osd()->play_collection) + if (!PlayerControl()) + collselection()->ClearCollection(target); + + osd()->Message1 ("Added %s entries",itos (osd()->moveselection->AddToCollection (target))); + osd()->CollectionChanged(target); +} + + +void +mgRemoveCollEntry::Execute() +{ + string target = getTarget(); + osd()->Message1 ("Removed %s entries",itos (osd()->moveselection->RemoveFromCollection (target))); + osd()->CollectionChanged(target); +} + void mgAction::Notify() { - m->SetHelpKeys(); + m->SetHelpKeys(Type()); } void mgAction::SetMenu(mgMenu *menu) { m = menu; + m_osd = m->osd(); } -eOSState -mgAction::Back() +void +mgAction::SetText(const char *text,bool copy) +{ + cOsdItem *c = dynamic_cast<cOsdItem*>(this); + if (!c) + mgError("mgAction::SetText() on wrong type"); + c->SetText(text,copy); +} + +const char * +mgAction::Text() { - return osUnknown; + cOsdItem *c = dynamic_cast<cOsdItem*>(this); + if (!c) + mgError("mgAction::Text() on wrong type"); + return c->Text(); } + bool -mgAction::Enabled() +mgAction::Enabled(mgActions on) { return true; } @@ -53,33 +189,27 @@ mgAction::Enabled() mgAction::mgAction() { m = NULL; + m_osd = NULL; + IgnoreNextEvent = false; } mgAction::~mgAction() { } - -//! \brief used for normal data base items -class mgEntry : public mgOsdItem -{ - public: - void Notify(); - bool Enabled() { return true;} - const char *ButtonName() { return ""; } - const char *MenuName (const unsigned int idx,const string value); - eOSState ProcessKey(eKeys key); - void Execute(); - eOSState Back(); -}; - class mgCommand : public mgOsdItem { public: - virtual eOSState ProcessKey(eKeys key); + bool Enabled(mgActions on); + virtual eOSState Process(eKeys key); void Execute(); }; +bool +mgCommand::Enabled(mgActions on) +{ + return IsEntry(on); +} mgSelection* mgAction::playselection () @@ -89,11 +219,11 @@ mgAction::playselection () mgMainMenu* mgAction::osd () { - return m->osd (); + return m_osd; } eOSState -mgOsdItem::Back() +mgAction::Back() { osd()->newmenu = NULL; return osContinue; @@ -106,7 +236,7 @@ mgEntry::Notify() selection()->setPosition(osd()->Current()); selection()->gotoPosition(); osd()->SaveState(); - mgAction::Notify(); + mgAction::Notify(); // only after selection is updated } const char * @@ -133,30 +263,22 @@ mgEntry::Execute() if (selection ()->enter ()) { osd()->forcerefresh = true; - osd()->m_Status->IgnoreNextEventOn = this; } else { - m->ExecuteAction(actInstantPlay); + m->ExecuteAction(actInstantPlay,Type()); } } eOSState -mgEntry::ProcessKey(eKeys key) +mgEntry::Process(eKeys key) { - if (key!=kNone) - mgDebug(3,"mgEntry(%s):ProcessKey(%d)",Text(),(int)key); - switch (key) { case kOk: Execute(); return osContinue; case kBack: - osd()->m_Status->IgnoreNextEventOn = this; return Back(); - case kBlue: - osd ()->newmenu = new mgSubmenu; - return osContinue; default: return osUnknown; } @@ -173,10 +295,8 @@ mgEntry::Back() } eOSState -mgCommand::ProcessKey(eKeys key) +mgCommand::Process(eKeys key) { - if (key!=kNone) - mgDebug(3,"mgCommand::ProcessKey(%d)",(int)key); mgMenu *parent = osd ()->Parent (); mgMenu *n = osd ()->newmenu; osd ()->newmenu = NULL; @@ -226,10 +346,12 @@ class mgExternal : public mgCommand const char *ButtonName(); const char *MenuName (const unsigned int idx,const string value); void Execute(); + bool Enabled(mgActions on) { return true; } private: cCommand * Command(); }; + class mgExternal0 : public mgExternal { }; class mgExternal1 : public mgExternal { }; class mgExternal2 : public mgExternal { }; @@ -287,7 +409,6 @@ mgExternal::Execute() cCommand *command = Command(); if (command) { - mgDebug(1,"external command:%s",command->Title()); bool confirmed = true; if (command->Confirm ()) { @@ -319,43 +440,41 @@ mgExternal::Execute() } //! \brief select search order -class mgChooseSearch : public mgCommand +class mgChooseOrder : public mgCommand { public: - bool Enabled(); - eOSState ProcessKey(eKeys key); + bool Enabled(mgActions on=mgActions(0)); + virtual eOSState Process(eKeys key); void Execute (); - const char *ButtonName() { return tr("Search"); } + const char *ButtonName() { return tr("Order"); } const char *MenuName(const unsigned int idx,const string value) - { return strdup(tr("Select search order")); } + { return strdup(tr("Select an order")); } }; bool -mgChooseSearch::Enabled() +mgChooseOrder::Enabled(mgActions on) { - bool result = mgOsdItem::Enabled(); - result &= (!selection()->isCollectionlist()); + bool result = !osd()->UsingCollection; + result &= IsEntry(on); return result; } eOSState -mgChooseSearch::ProcessKey(eKeys key) +mgChooseOrder::Process(eKeys key) { - if (key!=kNone) - mgDebug(3,"mgChooseSearch::ProcessKey(%d)",int(key)); if (key == kOk) { - osd()->Menus.pop_back(); + osd()->CloseMenu(); Execute(); return osContinue; } else - return mgCommand::ProcessKey(key); + return mgCommand::Process(key); } -void mgChooseSearch::Execute() +void mgChooseOrder::Execute() { - osd ()->newmenu = new mgTreeViewSelector; + osd ()->newmenu = new mgMenuOrders; } @@ -363,6 +482,7 @@ void mgChooseSearch::Execute() class mgToggleSelection:public mgCommand { public: + bool Enabled(mgActions on = mgActions(0)) { return true; } void Execute (); const char *ButtonName (); const char *MenuName (const unsigned int idx,const string value); @@ -403,10 +523,10 @@ mgToggleSelection::Execute () //! \brief sets the default collection selection -class mgSetDefault:public mgCommand +class mgSetDefaultCollection:public mgCommand { public: - bool Enabled(); + bool Enabled(mgActions on = mgActions(0)); void Execute (); const char *ButtonName () { @@ -415,7 +535,7 @@ class mgSetDefault:public mgCommand const char *MenuName (const unsigned int idx,const string value); }; -const char * mgSetDefault::MenuName(const unsigned int idx,const string value) +const char * mgSetDefaultCollection::MenuName(const unsigned int idx,const string value) { char *b; asprintf (&b, tr("Set default to collection '%s'"), @@ -423,20 +543,11 @@ const char * mgSetDefault::MenuName(const unsigned int idx,const string value) return b; } -class mgSetButton : public mgCommand -{ - const char *ButtonName() - { - return tr("Set"); - } - const char *MenuName(const unsigned int idx,const string value) { return strdup(""); } -}; - bool -mgSetDefault::Enabled() +mgSetDefaultCollection::Enabled(mgActions on) { - bool result = mgOsdItem::Enabled(); + bool result = IsEntry(on); result &= (!osd()->DefaultCollectionSelected()); result &= osd()->UsingCollection; result &= (selection ()->level () == 0); @@ -444,16 +555,26 @@ mgSetDefault::Enabled() } void -mgSetDefault::Execute () +mgSetDefaultCollection::Execute () { - if (!Enabled()) - mgError("mgSetDefault not enabled"); osd ()->default_collection = selection ()->getCurrentValue(); osd()->Message1 ("Default collection now is '%s'", osd ()->default_collection); } +class mgSetButton : public mgCommand +{ + public: + bool Enabled(mgActions on) { return true; } + const char *ButtonName() + { + return tr("Set"); + } + const char *MenuName(const unsigned int idx,const string value) { return strdup(""); } +}; + + //! \brief instant play class mgInstantPlay : public mgCommand { public: @@ -477,23 +598,66 @@ mgInstantPlay::Execute() osd()->PlayInstant(true); } -//! \brief add selected items to default collection +//! \brief add selected items to a collection class mgAddAllToCollection:public mgCommand { public: void Execute (); //! \brief adds the whole selection to a collection // \param collection the target collection. Default is the default collection - void ExecuteSelection (mgSelection *s,const string collection=""); const char *ButtonName () { return tr ("Add"); } const char *MenuName (const unsigned int idx,const string value); + protected: + void ExecuteMove(); }; const char * mgAddAllToCollection::MenuName (const unsigned int idx,const string value) { + return strdup(tr("Add all to a collection")); +} + +void +mgAddAllToCollection::Execute() +{ +// work on a copy, so we don't have to clear the cache of selection() +// which would result in an osd()->forcerefresh which could scroll. + osd()->moveselection = new mgSelection(selection()); + ExecuteMove(); +} + +void +mgAddAllToCollection::ExecuteMove() +{ + if (osd() ->Menus.size()>1) + osd ()->CloseMenu(); // TODO Gebastel... + char *b; + asprintf(&b,tr("'%s' to collection"),selection()->getCurrentValue().c_str()); + osd ()->newmenu = new mgTreeAddToCollSelector(string(b)); + osd ()->newposition = osd()->collselection()->getPosition(0); + free(b); +} + + +//! \brief add selected items to default collection +class mgAddAllToDefaultCollection:public mgCommand { + public: + void Execute (); + //! \brief adds the whole selection to the default collection + // \param collection the default collection. + void ExecuteSelection (mgSelection *s); + const char *ButtonName () + { + return tr ("Add"); + } + const char *MenuName (const unsigned int idx,const string value); +}; + +const char * +mgAddAllToDefaultCollection::MenuName (const unsigned int idx,const string value) +{ char *b; asprintf (&b, tr ("Add all to '%s'"), osd ()->default_collection.c_str ()); @@ -501,17 +665,19 @@ mgAddAllToCollection::MenuName (const unsigned int idx,const string value) } void -mgAddAllToCollection::Execute() +mgAddAllToDefaultCollection::Execute() { - ExecuteSelection(selection()); + mgSelection *sel = new mgSelection(selection()); + sel->select (); + ExecuteSelection(sel); + delete sel; } + void -mgAddAllToCollection::ExecuteSelection (mgSelection *s, const string collection) +mgAddAllToDefaultCollection::ExecuteSelection (mgSelection *s) { - string target = collection; - if (target.empty()) - target = osd()->default_collection; + string target = osd()->default_collection; if (target == osd()->play_collection) if (!PlayerControl()) collselection()->ClearCollection(target); @@ -529,11 +695,11 @@ mgAddAllToCollection::ExecuteSelection (mgSelection *s, const string collection) } } -//! \brief add selected items to default collection +//! \brief add selected items to a collection class mgAddThisToCollection:public mgAddAllToCollection { public: - bool Enabled(); + bool Enabled(mgActions on = mgActions(0)); void Execute (); const char *ButtonName (); const char *MenuName (const unsigned int idx,const string value); @@ -545,66 +711,127 @@ mgAddThisToCollection::Execute () { // work on a copy, so we don't have to clear the cache of selection() // which would result in an osd()->forcerefresh which could scroll. - mgSelection *s = new mgSelection(selection()); - s->select (); - mgAddAllToCollection::ExecuteSelection(s); - delete s; + osd()->moveselection = new mgSelection(selection()); + osd()->moveselection->select (); + mgAddAllToCollection::ExecuteMove(); } -class mgSearchNew : public mgCommand +const char * +mgAddThisToCollection::ButtonName () { - virtual void NewSearch() = 0; - void Execute(); -}; + return tr("Add"); +} -void -mgSearchNew::Execute() +bool +mgAddThisToCollection::Enabled(mgActions on) { - mgSelection *oldsel = osd()->selection(); - map<string,string>* oldkeys = oldsel->UsedKeyValues(); - osd()->UseNormalSelection(); // Default for all searches - NewSearch(); - mgSelection *newsel = osd()->selection(); - for (unsigned int idx = 0; idx < newsel->size(); idx++) - { - string new_keyname = newsel->getKeyChoice(idx); - if (oldkeys->count(new_keyname)==0) break; - newsel->select((*oldkeys)[new_keyname]); - } - newsel->leave(); - delete oldkeys; + return IsEntry(on); } -class mgSearchCollItem : public mgSearchNew +const char * +mgAddThisToCollection::MenuName (const unsigned int idx,const string value) { - const char *MenuName(const unsigned int idx,const string value) { return strdup(tr("Collection -> Item")); } - void NewSearch() { osd ()->UseCollectionSelection (); } + return strdup(tr("Add to a collection")); +} + +//! \brief add selected items to default collection +class mgAddThisToDefaultCollection:public mgAddAllToDefaultCollection +{ + public: + bool Enabled(mgActions on = mgActions(0)); + void Execute (); + const char *ButtonName (); + const char *MenuName (const unsigned int idx,const string value); }; +void +mgAddThisToDefaultCollection::Execute () +{ +// work on a copy, so we don't have to clear the cache of selection() +// which would result in an osd()->forcerefresh which could scroll. + mgSelection *sel = new mgSelection(selection()); + sel->select (); + mgAddAllToDefaultCollection::ExecuteSelection(sel); + delete sel; +} + const char * -mgAddThisToCollection::ButtonName () +mgAddThisToDefaultCollection::ButtonName () { return tr("Add"); } bool -mgAddThisToCollection::Enabled() +mgAddThisToDefaultCollection::Enabled(mgActions on) { - bool result = mgOsdItem::Enabled(); + bool result = IsEntry(on); result &= (!osd()->DefaultCollectionSelected()); return result; } const char * -mgAddThisToCollection::MenuName (const unsigned int idx,const string value) +mgAddThisToDefaultCollection::MenuName (const unsigned int idx,const string value) { char *b; - asprintf (&b, tr ("Add to '%s'"), - osd ()->default_collection.c_str ()); + asprintf (&b, tr ("Add to '%s'"), osd ()->default_collection.c_str ()); return b; } +class mgOrderNew : public mgCommand +{ + protected: + virtual void NewOrder() = 0; + public: + bool Enabled(mgActions on = mgActions(0)) { return true; } + void Execute(); +}; + +void +mgOrderNew::Execute() +{ + mgSelection *s = osd()->selection(); + map<mgKeyTypes,string>* oldkeys = s->UsedKeyValues(); + mgContentItem o; + s->select(); + if (s->getNumTracks()==1) + { + o = s->getTrack(0); + } + osd()->UseNormalSelection(); // Default for all orders + NewOrder(); + mgSelection *newsel = osd()->selection(); + newsel->leave(0); + for (unsigned int idx = 0; idx < newsel->ordersize(); idx++) + { + string selval = ""; + mgKeyTypes new_kt = newsel->getKeyType(idx); + if (oldkeys->count(new_kt)>0) + selval=(*oldkeys)[new_kt]; + else if (o.getId()>=0) + selval=o.getKeyValue(new_kt); + if (!selval.empty()) + { + if (idx<newsel->ordersize()-1) + newsel->select(selval); + else + newsel->setPosition(selval); + continue; + } + break; + } + delete oldkeys; + osd()->newposition = newsel->getPosition(newsel->level()); + newsel->clearCache(); +} + +class mgOrderCollItem : public mgOrderNew +{ + const char *MenuName(const unsigned int idx,const string value) { return strdup(tr("Collection -> Item")); } + void NewOrder() { osd ()->UseCollectionSelection (); } +}; + + //! \brief remove selected items from default collection class mgRemoveAllFromCollection:public mgCommand { @@ -620,25 +847,49 @@ class mgRemoveAllFromCollection:public mgCommand void mgRemoveAllFromCollection::Execute () { - osd()->Message1 ("Removed %s entries", - itos (collselection ()->RemoveFromCollection ( - osd ()->default_collection.c_str ()))); - if (osd()->default_collection == osd()->play_collection) - { - mgPlayerControl *c = PlayerControl(); - if (c) - c->ReloadPlaylist(); - } + if (osd() ->Menus.size()>1) + osd ()->CloseMenu(); // TODO Gebastel... + char *b; + asprintf(&b,tr("Remove '%s' from collection"),osd()->moveselection->getListname().c_str()); + osd ()->newmenu = new mgTreeRemoveFromCollSelector(string(b)); + free(b); } - const char * mgRemoveAllFromCollection::MenuName (const unsigned int idx,const string value) { - char *b; - asprintf (&b, tr ("Remove all from '%s'"), - osd ()->default_collection.c_str ()); - return b; + return strdup(tr("Remove all from a collection")); +} + +class mgClearCollection : public mgCommand +{ + public: + bool Enabled(mgActions on = mgActions(0)); + void Execute (); + const char *ButtonName () + { + return tr ("Clear"); + } + const char *MenuName (const unsigned int idx,const string value); +}; + +const char * +mgClearCollection::MenuName (const unsigned int idx,const string value) +{ + return strdup(tr("Clear the collection")); +} + +bool +mgClearCollection::Enabled(mgActions on) +{ + return selection()->isCollectionlist(); +} + +void +mgClearCollection::Execute() +{ + if (Interface->Confirm(tr("Clear the collection?"))) + selection()->ClearCollection(selection()->getCurrentValue()); } //! \brief remove selected items from default collection @@ -653,42 +904,41 @@ class mgRemoveThisFromCollection:public mgRemoveAllFromCollection const char *MenuName (const unsigned int idx,const string value); }; + void mgRemoveThisFromCollection::Execute () { - if (osd()->DefaultCollectionSelected()) - { - selection()->ClearCollection(selection()->getCurrentValue().c_str()); - osd()->Message ("Removed all entries"); - } - else - { - selection ()->select (); - osd()->Message1 ("Removed %s entries", - itos (selection ()->RemoveFromCollection (osd ()->default_collection.c_str ()))); - selection ()->leave (); - if (osd()->DefaultCollectionEntered()) - { - selection()->clearCache(); - osd()->forcerefresh = true; - } - } +// work on a copy, so we don't have to clear the cache of selection() +// which would result in an osd()->forcerefresh which could scroll. + osd()->moveselection = new mgSelection(selection()); + osd()->moveselection->select (); + mgRemoveAllFromCollection::Execute(); } const char * mgRemoveThisFromCollection::MenuName (const unsigned int idx,const string value) { - char *b; - string this_sel = selection()->getCurrentValue(); - if (osd()->DefaultCollectionSelected()) - asprintf(&b,tr("Remove all entries from '%s'"),this_sel.c_str()); - else - asprintf (&b, tr ("Remove from '%s'"), - trim(osd ()->default_collection).c_str ()); - return b; + return strdup(tr("Remove from a collection")); } +/*! \brief create collection directly in the collection list + */ +class mgCreateCollection: public mgAction, public cMenuEditStrItem +{ + public: + mgCreateCollection(); + void Notify(); + bool Enabled(mgActions on = mgActions(0)); + eOSState ProcessKey(eKeys key); + void Execute (); + const char *MenuName (const unsigned int idx=0,const string value=""); + private: + bool Editing(); + char value[30]; +}; + + bool mgCreateCollection::Editing() { @@ -699,8 +949,8 @@ mgCreateCollection::Editing() void mgCreateCollection::Notify() { - if (!Editing()) - osd()->SetHelpKeys(NULL,NULL,NULL,NULL); + if (!Editing()) + m->SetHelpKeys(); } const char* @@ -709,7 +959,7 @@ mgCreateCollection::MenuName(const unsigned int idx,const string value) return strdup(tr ("Create collection")); } -mgCreateCollection::mgCreateCollection() : cMenuEditStrItem(MenuName(),value,30,tr(FileNameChars)), mgAction() +mgCreateCollection::mgCreateCollection() : mgAction(), cMenuEditStrItem(MenuName(),value,30,tr(FileNameChars)) { value[0]=0; } @@ -729,11 +979,9 @@ mgCreateCollection::ProcessKey(eKeys key) } bool -mgCreateCollection::Enabled() +mgCreateCollection::Enabled(mgActions on) { - bool result = mgAction::Enabled(); - result &= selection()->isCollectionlist(); - return result; + return selection()->isCollectionlist(); } void @@ -747,6 +995,11 @@ mgCreateCollection::Execute () mgDebug(1,"created collection %s",name.c_str()); osd()->default_collection = name; selection ()->clearCache(); + if (selection()->isCollectionlist()) + { +// selection ()->setPosition(selection()->id(keyCollection,name)); + selection ()->setPosition(name); + } osd()->forcerefresh = true; } else @@ -758,7 +1011,7 @@ class mgDeleteCollection:public mgCommand { public: void Execute (); - bool Enabled(); + bool Enabled(mgActions on = mgActions(0)); const char *ButtonName () { return tr ("Delete"); @@ -767,11 +1020,10 @@ class mgDeleteCollection:public mgCommand }; bool -mgDeleteCollection::Enabled() +mgDeleteCollection::Enabled(mgActions on) { - bool result = mgOsdItem::Enabled(); + bool result = IsEntry(on); result &= selection()->isCollectionlist(); - result &= (!osd()->DefaultCollectionSelected()); if (result) { string name = selection ()->getCurrentValue(); @@ -782,13 +1034,14 @@ mgDeleteCollection::Enabled() const char* mgDeleteCollection::MenuName(const unsigned int idx,const string value) { - return strdup(tr("Delete collection")); + return strdup(tr("Delete the collection")); } void mgDeleteCollection::Execute () { + if (!Interface->Confirm(tr("Delete the collection?"))) return; string name = selection ()->getCurrentValue(); if (selection ()->DeleteCollection (name)) { @@ -826,87 +1079,93 @@ mgExportTracklist::Execute () osd()->Message1 ("written to %s", m3u_file); } -class mgSearchArtistAlbumTitle : public mgSearchNew +class mgOrderArtistAlbumTitle : public mgOrderNew { - const char *MenuName(const unsigned int idx,const string value) { return strdup(tr("Artist -> Album -> Title")); } - void NewSearch() + const char *MenuName(const unsigned int idx,const string value) { return strdup(tr("Artist -> Album -> Track")); } + void NewOrder() { - selection ()->setKey (0, tr ("Artist")); - selection ()->setKey (1, tr ("Album")); - selection ()->setKey (2, tr ("Title")); + selection ()->setKey (0, keyArtist); + selection ()->setKey (1, keyAlbum); + selection ()->setKey (2, keyTrack); } }; -class mgSearchAlbumTitle : public mgSearchNew +class mgOrderAlbumTitle : public mgOrderNew { - const char *MenuName(const unsigned int idx,const string value) { return strdup(tr("Album -> Title")); } - void NewSearch() + const char *MenuName(const unsigned int idx,const string value) { return strdup(tr("Album -> Track")); } + void NewOrder() { - selection ()->setKey (0, tr ("Album")); - selection ()->setKey (1, tr ("Title")); + selection ()->setKey (0, keyAlbum); + selection ()->setKey (1, keyTrack); } }; -class mgSearchGenreYearTitle : public mgSearchNew +class mgOrderGenreYearTitle : public mgOrderNew { - const char *MenuName(const unsigned int idx,const string value) { return strdup(tr("Genre 1 -> Year -> Title")); } - void NewSearch() + const char *MenuName(const unsigned int idx,const string value) { return strdup(tr("Genre 1 -> Year -> Track")); } + void NewOrder() { - selection ()->setKey (0, tr ("Genre 1")); - selection ()->setKey (1, tr ("Year")); - selection ()->setKey (2, tr ("Title")); + selection ()->setKey (0, keyGenre1); + selection ()->setKey (1, keyYear); + selection ()->setKey (2, keyTrack); } }; -class mgSearchGenreArtistAlbumTitle : public mgSearchNew +class mgOrderGenreArtistAlbumTitle : public mgOrderNew { - const char *MenuName(const unsigned int idx,const string value) { return strdup(tr("Genre 1 -> Artist ->Album -> Title")); } - void NewSearch() + const char *MenuName(const unsigned int idx,const string value) { return strdup(tr("Genre 1 -> Artist ->Album -> Track")); } + void NewOrder() { - selection ()->setKey (0, tr ("Genre 1")); - selection ()->setKey (1, tr ("Artist")); - selection ()->setKey (2, tr ("Album")); - selection ()->setKey (3, tr ("Title")); + selection ()->setKey (0, keyGenre1); + selection ()->setKey (1, keyArtist); + selection ()->setKey (2, keyAlbum); + selection ()->setKey (3, keyTrack); } }; -class mgSearchArtistTitle : public mgSearchNew +class mgOrderArtistTitle : public mgOrderNew { - const char *MenuName(const unsigned int idx,const string value) { return strdup(tr("Artist -> Title")); } - void NewSearch() + const char *MenuName(const unsigned int idx,const string value) { return strdup(tr("Artist -> Track")); } + void NewOrder() { - selection ()->setKey (0, tr ("Artist")); - selection ()->setKey (1, tr ("Title")); + selection ()->setKey (0, keyArtist); + selection ()->setKey (1, keyTrack); } }; mgActions -mgOsdItem::Type() +mgAction::Type() { const type_info& t = typeid(*this); - if (t == typeid(mgChooseSearch)) return actChooseSearch; + if (t == typeid(mgChooseOrder)) return actChooseOrder; if (t == typeid(mgToggleSelection)) return actToggleSelection; - if (t == typeid(mgSetDefault)) return actSetDefault; + if (t == typeid(mgClearCollection)) return actClearCollection; + if (t == typeid(mgCreateCollection)) return actCreateCollection; if (t == typeid(mgInstantPlay)) return actInstantPlay; if (t == typeid(mgAddAllToCollection)) return actAddAllToCollection; + if (t == typeid(mgAddAllToDefaultCollection)) return actAddAllToDefaultCollection; if (t == typeid(mgRemoveAllFromCollection)) return actRemoveAllFromCollection; if (t == typeid(mgDeleteCollection)) return actDeleteCollection; if (t == typeid(mgExportTracklist)) return actExportTracklist; + if (t == typeid(mgAddCollEntry)) return actAddCollEntry; + if (t == typeid(mgRemoveCollEntry)) return actRemoveCollEntry; if (t == typeid(mgAddThisToCollection)) return actAddThisToCollection; + if (t == typeid(mgAddThisToDefaultCollection)) return actAddThisToDefaultCollection; if (t == typeid(mgRemoveThisFromCollection)) return actRemoveThisFromCollection; if (t == typeid(mgEntry)) return actEntry; if (t == typeid(mgSetButton)) return actSetButton; - if (t == typeid(mgSearchCollItem)) return actSearchCollItem; - if (t == typeid(mgSearchArtistAlbumTitle)) return actSearchArtistAlbumTitle; - if (t == typeid(mgSearchArtistTitle)) return actSearchArtistTitle; - if (t == typeid(mgSearchAlbumTitle)) return actSearchAlbumTitle; - if (t == typeid(mgSearchGenreYearTitle)) return actSearchGenreYearTitle; - if (t == typeid(mgSearchGenreArtistAlbumTitle)) return actSearchArtistAlbumTitle; + if (t == typeid(mgOrderCollItem)) return ActOrderCollItem; + if (t == typeid(mgOrderArtistAlbumTitle)) return ActOrderArtistAlbumTitle; + if (t == typeid(mgOrderArtistTitle)) return ActOrderArtistTitle; + if (t == typeid(mgOrderAlbumTitle)) return ActOrderAlbumTitle; + if (t == typeid(mgOrderGenreYearTitle)) return ActOrderGenreYearTitle; + if (t == typeid(mgOrderGenreArtistAlbumTitle)) return ActOrderArtistAlbumTitle; + if (t == typeid(mgSetDefaultCollection)) return actSetDefaultCollection; if (t == typeid(mgExternal0)) return actExternal0; if (t == typeid(mgExternal1)) return actExternal1; if (t == typeid(mgExternal2)) return actExternal2; @@ -927,37 +1186,40 @@ mgOsdItem::Type() if (t == typeid(mgExternal17)) return actExternal17; if (t == typeid(mgExternal18)) return actExternal18; if (t == typeid(mgExternal19)) return actExternal19; - mgError("Unknown mgOsdItem %s",t.name()); + mgError("Unknown mgAction %s",t.name()); return mgActions(0); } -mgOsdItem* +mgAction* actGenerate(const mgActions action) { - mgOsdItem * result = NULL; + mgAction * result = NULL; switch (action) { - case actChooseSearch: result = new mgChooseSearch;break; + case actChooseOrder: result = new mgChooseOrder;break; case actToggleSelection: result = new mgToggleSelection;break; - case actSetDefault: result = new mgSetDefault;break; - case actUnused1: break; + case actClearCollection: result = new mgClearCollection;break; + case actCreateCollection: result = new mgCreateCollection;break; case actInstantPlay: result = new mgInstantPlay;break; case actAddAllToCollection: result = new mgAddAllToCollection;break; + case actAddAllToDefaultCollection: result = new mgAddAllToDefaultCollection;break; case actRemoveAllFromCollection:result = new mgRemoveAllFromCollection;break; case actDeleteCollection: result = new mgDeleteCollection;break; case actExportTracklist: result = new mgExportTracklist;break; - case actUnused2: break; - case actUnused3: break; + case actAddCollEntry: result = new mgAddCollEntry;break; + case actRemoveCollEntry: result = new mgRemoveCollEntry;break; case actAddThisToCollection: result = new mgAddThisToCollection;break; + case actAddThisToDefaultCollection: result = new mgAddThisToDefaultCollection;break; case actRemoveThisFromCollection: result = new mgRemoveThisFromCollection;break; case actEntry: result = new mgEntry;break; case actSetButton: result = new mgSetButton;break; - case actSearchCollItem: result = new mgSearchCollItem;break; - case actSearchArtistAlbumTitle: result = new mgSearchArtistAlbumTitle;break; - case actSearchArtistTitle: result = new mgSearchArtistTitle;break; - case actSearchAlbumTitle: result = new mgSearchAlbumTitle;break; - case actSearchGenreYearTitle: result = new mgSearchGenreYearTitle;break; - case actSearchGenreArtistAlbumTitle: result = new mgSearchGenreArtistAlbumTitle;break; + case ActOrderCollItem: result = new mgOrderCollItem;break; + case ActOrderArtistAlbumTitle: result = new mgOrderArtistAlbumTitle;break; + case ActOrderArtistTitle: result = new mgOrderArtistTitle;break; + case ActOrderAlbumTitle: result = new mgOrderAlbumTitle;break; + case ActOrderGenreYearTitle: result = new mgOrderGenreYearTitle;break; + case ActOrderGenreArtistAlbumTitle: result = new mgOrderGenreArtistAlbumTitle;break; + case actSetDefaultCollection: result = new mgSetDefaultCollection;break; case actExternal0: result = new mgExternal0;break; case actExternal1: result = new mgExternal1;break; case actExternal2: result = new mgExternal2;break; diff --git a/mg_actions.h b/mg_actions.h index ecb5913..3712191 100644 --- a/mg_actions.h +++ b/mg_actions.h @@ -24,35 +24,37 @@ using namespace std; class mgSelection; class mgMenu; class mgMainMenu; -class mgOsdItem; /*! \brief defines all actions which can appear in command submenus. * Since these values are saved in muggle.state, new actions should * always be appended. The order does not matter. Value 0 means undefined. */ enum mgActions { - actChooseSearch=1, //!< show a menu with all possible search schemas + actChooseOrder=1, //!< show a menu with all possible orders actToggleSelection, //!< toggle between search and collection view - actSetDefault, //!< set default collection - actUnused1, + actClearCollection, //!< clear a collection, + actCreateCollection, actInstantPlay, //!< instant play actAddAllToCollection, //!< add all items of OSD list to default collection actRemoveAllFromCollection,//!< remove from default collection actDeleteCollection, //!< delete collection actExportTracklist, //!< export track list into a *.m3u file - actUnused2, - actUnused3, + actAddCollEntry, + actRemoveCollEntry, actAddThisToCollection, //!< add selected item to default collection actRemoveThisFromCollection, //!< remove selected item from default collection actEntry, //!< used for normal data base items actSetButton, //!< connect a button with an action - actSearchCollItem, //!< search in the collections - actSearchArtistAlbumTitle, //!< search by Artist/Album/Title - actSearchArtistTitle, //!< search by Artist/Title - actSearchAlbumTitle, //!< search by Album/Title - actSearchGenreYearTitle, //!< search by Genre1/Year/Title - actSearchGenreArtistAlbumTitle, //!< search by Genre1/Artist/Album/Title - actExternal0, //!< used for external commands, the number is the entry number in the .conf file starting with line 0 + ActOrderCollItem, //!< order by collections + ActOrderArtistAlbumTitle, //!< order by Artist/Album/Title + ActOrderArtistTitle, //!< order by Artist/Title + ActOrderAlbumTitle, //!< order by Album/Title + ActOrderGenreYearTitle, //!< order by Genre1/Year/Title + ActOrderGenreArtistAlbumTitle, //!< order by Genre1/Artist/Album/Title + actAddAllToDefaultCollection, + actAddThisToDefaultCollection, + actSetDefaultCollection, + actExternal0 = 1000, //!< used for external commands, the number is the entry number in the .conf file starting with line 0 actExternal1, //!< used for external commands, the number is the entry number in the .conf file actExternal2, //!< used for external commands, the number is the entry number in the .conf file actExternal3, //!< used for external commands, the number is the entry number in the .conf file @@ -83,7 +85,7 @@ class mgAction public: //! \brief if true, can be displayed - virtual bool Enabled(); + virtual bool Enabled(mgActions on = mgActions(0)); //! \brief the action to be executed virtual void Execute () = 0; @@ -101,7 +103,7 @@ class mgAction /*! \brief the name for a menu entry. If empty, no button will be able * to execute this. The returned C string must be freeable at any time. - * \todo use virtual Set() instead? Compare definition of mgMenu::AddAction + * \param value a string that can be used for building the menu name. */ virtual const char *MenuName (const unsigned int idx=0,const string value="") = 0; @@ -116,8 +118,21 @@ class mgAction //! \brief what to do when mgStatus::OsdCurrentItem is called //for this one - virtual void Notify(); + mgActions Type(); + void SetText(const char *text,bool copy=true); + const char *Text(); + + void TryNotify(); + + /*! \brief vdr calls OsdCurrentItem more often than we + * want. This tells mgStatus to ignore the next call + * for a specific item. + * \todo is this behaviour intended or a bug in vdr + * or in muggle ? + */ + bool IgnoreNextEvent; protected: + //! \brief returns the OSD owning the menu owning this item mgMainMenu* osd (); @@ -132,38 +147,14 @@ class mgAction //! \brief returns the playselection mgSelection* playselection (); -}; - -//! \brief a generic class for all actions that can be used in the -// command submenu -class mgOsdItem : public mgAction, public cOsdItem -{ - public: - //! \brief the enum mgActions value of this class - virtual mgActions Type(); - //! \brief to be executed for kBack. We might want to call this - // directly, so expose it. - virtual eOSState Back(); + virtual void Notify(); + private: + mgMainMenu *m_osd; }; -//! \brief generate a mgOsdItem for action -mgOsdItem* actGenerate(const mgActions action); +//! \brief generate an mgAction for action +mgAction* actGenerate(const mgActions action); -/*! \brief create collection directly in the collection list - */ -class mgCreateCollection:public cMenuEditStrItem, public mgAction -{ - public: - mgCreateCollection(); - void Notify(); - bool Enabled(); - eOSState ProcessKey(eKeys key); - void Execute (); - const char *MenuName (const unsigned int idx=0,const string value=""); - private: - bool Editing(); - char value[30]; -}; #endif diff --git a/mg_database.h b/mg_database.h index 54baef1..d974a33 100644 --- a/mg_database.h +++ b/mg_database.h @@ -17,7 +17,6 @@ /*! * \brief an abstract database class * - * \todo Currently unused. This class could abstract database connection and query handling, but this will be tedious as an abstract representation of results would be needed. */ class mgDB { @@ -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; +} @@ -9,8 +9,8 @@ * */ -#ifndef _DB_H -#define _DB_H +#ifndef _MG_DB_H +#define _MG_DB_H #include <stdlib.h> #include <mysql/mysql.h> #include <string> @@ -25,443 +25,29 @@ using namespace std; #include "mg_tools.h" +#include "mg_valmap.h" +#include "mg_order.h" typedef vector<string> strvector; -//! \brief a map for reading / writing configuration data. -class mgValmap : public map<string,string> { - private: - const char *m_key; - public: - /*! \brief constructor - * \param key all names will be prefixed with key. - */ - mgValmap(const char *key); - //! \brief read from file - void Read(FILE *f); - //! \brief write to file - void Write(FILE *f); - //! \brief enter a string value - void put(const char*name, string value); - //! \brief enter a C string value - void put(const char*name, const char* value); - //! \brief enter a long value - void put(const char*name, long value); - //! \brief enter a int value - void put(const char*name, int value); - //! \brief enter a unsigned int value - void put(const char*name, unsigned int value); - //! \brief enter a bool value - void put(const char*name, bool value); - //! \brief return a string - string getstr(const char* name) { - return (*this)[name]; - } - //! \brief return a C string - bool getbool(const char* name) { - return (getstr(name)=="true"); - } - //! \brief return a long - long getlong(const char* name) { - return atol(getstr(name).c_str()); - } - //! \brief return an unsigned int - unsigned int getuint(const char* name) { - return (unsigned long)getlong(name); - } -}; - -static const string EMPTY = "XNICHTGESETZTX"; class mgSelection; -//! \brief a generic keyfield -class keyfield -{ - public: - //! \brief set the owning selection. - void setOwner(mgSelection *owner) { selection = owner; } - - //! \brief default constructor - keyfield () - { - }; - - /*! \brief constructs a simple key field which only needs info from - * the tracks table. - * \param choice the internationalized name of this key field, e.g. "Jahr" - */ - keyfield (const string choice); - - //! \brief default destructor - virtual ~ keyfield () - { - }; - -/*! \brief assigns a new id and value to the key field. - * This also invalidates the cache of the owning mgSelection. - * \param id used for lookups in the data base - * \param value used for display - */ - void set (const string id, const string value); - -//! \brief helper function for streaming debug info - void writeAt (ostream &) const; - -//! \brief adds lookup data to a WHERE SQL statement - string restrict (string & result) const; - -//! \brief returns the internationalized name of this key field - string choice () const - { - return m_choice; - } - -/*! \brief returns the id of this key field. This is the string used - * for lookups in the data base, not for display - */ - string id () const - { - return m_id; - } - -/*! \brief returns the filter for this key field. - * \todo filters are not yet implemented but we already dump them in DumpState - */ - string filter () const - { - return m_filter; - } - -/*! \brief returns the value of this key field. This is the string used - * for the display. - */ - string value () const - { - return m_value; - } - - virtual string order() const - { - return valuefield (); - } - -//! \brief returns the name of the corresponding field in the tracks table - virtual string basefield () const { return ""; } - -//! \brief returns the name of the field to be shown in the selection list - virtual string valuefield () const - { - return basefield (); - } - -//! \brief returns the name of the identification field - virtual string idfield () const - { - return basefield (); - } - -/*! \brief returns the name of the field needed to count how many - * different values for this key field exist - */ - virtual string countfield () const - { - return basefield (); - } - -/*! \brief returns a join clause needed for the composition of - * a WHERE statement - */ - virtual string join () const; - -/*! \brief returns a join clause needed for the composition of - * a WHERE statement especially for counting the number of items - */ - virtual string countjoin () const - { - return join (); - } - -/*! \brief if true, the WHERE clause should also return values from - * join tables - */ - bool lookup; - -//! \brief returns a SQL query command for counting different key -//values - string KeyCountquery (); - - protected: -//! \brief the owning selection. - mgSelection* selection; - //! \brief the english name for this key field - string m_choice; - //! \brief used for lookup in the data base - string m_id; - //! \brief used for OSD display - string m_value; - //! \brief an SQL restriction like 'tracks.year=1982' - string m_filter; - /*! \brief should be defined as true if we need to join another - * table for getting user friendly values (like the name of a genre) - */ - virtual bool need_join () const; - //! \brief escape the string as needed for calls to mysql - string sql_string(const string s) const; -}; - - -//! \brief orders by collection -class collectionkeyfield:public keyfield -{ - public: - collectionkeyfield ():keyfield ("Collection") - { - } - string basefield () const - { - return "playlist.id"; - } - string valuefield () const - { - return "playlist.title"; - } -/* this join() would ensure that empty collections be suppressed. But we - * want them all. so we don't need the join - */ - string join () const - { - return ""; - } -}; - -//! \brief orders by position in collection -class collectionitemkeyfield:public keyfield -{ - public: - collectionitemkeyfield ():keyfield ("Collection item") - { - } - string basefield () const - { - return "playlistitem.tracknumber"; - } - string valuefield () const - { - return "tracks.title"; - } - string order () const - { - return basefield (); - } - string join () const - { - return - "tracks.id=playlistitem.trackid and playlist.id=playlistitem.playlist"; - } -}; - -//! \brief orders by album.title -class albumkeyfield:public keyfield -{ - public: - albumkeyfield ():keyfield ("Album") - { - } - string basefield () const - { - return "tracks.sourceid"; - } - string valuefield () const - { - return "album.title"; - } - string idfield () const - { - return "album.title"; - } - string countfield () const - { - return "album.title"; - } - string join () const - { - return "tracks.sourceid=album.cddbid"; - } - protected: - //!brief we always need to join table album - bool need_join () const - { - return true; - }; -}; - -//! \brief orders by genre1 -class genre1keyfield:public keyfield -{ - public: - genre1keyfield ():keyfield ("Genre 1") - { - } - string basefield () const - { - return "tracks.genre1"; - } - string valuefield () const - { - return "genre.genre"; - } - string idfield () const - { - return "genre.id"; - } -}; - -//! \brief orders by genre2 -class genre2keyfield:public keyfield -{ - public: - genre2keyfield ():keyfield ("Genre 2") - { - } - string basefield () const - { - return "tracks.genre2"; - } - string valuefield () const - { - return "genre.genre"; - } - string idfield () const - { - return "genre.id"; - } -}; - -//! \brief orders by language -class langkeyfield:public keyfield -{ - public: - langkeyfield ():keyfield ("Language") - { - } - string basefield () const - { - return "tracks.lang"; - } - string valuefield () const - { - return "language.language"; - } - string idfield () const - { - return "language.id"; - } -}; - -//! \brief orders by tracks.artist -class artistkeyfield:public keyfield -{ - public: - artistkeyfield ():keyfield ("Artist") - { - } - string basefield () const - { - return "tracks.artist"; - } -}; - -//! \brief orders by tracks.rating -class ratingkeyfield:public keyfield -{ - public: - ratingkeyfield ():keyfield ("Rating") - { - } - string basefield () const - { - return "tracks.rating"; - } -}; - -//! \brief orders by tracks.year -class yearkeyfield:public keyfield -{ - public: - yearkeyfield ():keyfield ("Year") - { - } - string basefield () const - { - return "tracks.year"; - } -}; - -//! \brief orders by tracks.title -class titlekeyfield:public keyfield -{ - public: - titlekeyfield ():keyfield ("Title") - { - } - string basefield () const - { - return "tracks.title"; - } -}; - -//! \brief orders by tracks.tracknb and tracks.title -class trackkeyfield:public keyfield -{ - public: - trackkeyfield ():keyfield ("Track") - { - } - string basefield () const - { - return "tracks.tracknb"; - } - string valuefield () const - { - return - "concat(" - "if(tracks.tracknb>0," - "concat(" - "if(tracks.tracknb<10,' ','')," - "tracks.tracknb," - "' '" - "),''" - ")," - "tracks.title)"; - } -}; - -//! \brief orders by decade (deduced from tracks.year) -class decadekeyfield:public keyfield -{ - public: - decadekeyfield ():keyfield ("Decade") - { - } - string basefield () const - { - return "substring(convert(10 * floor(tracks.year/10), char),3)"; - } -}; - -//! \brief represents a content item like an mp3 file +//! \brief represents a content item like an mp3 file. class mgContentItem { public: mgContentItem () { } + + string getKeyValue(mgKeyTypes kt); + //! \brief copy constructor mgContentItem(const mgContentItem* c); //! \brief construct an item from an SQL row - mgContentItem (const MYSQL_ROW row, const string ToplevelDir); + mgContentItem (const mgSelection* sel, const MYSQL_ROW row); //! \brief returns track id long getId () const { @@ -487,58 +73,38 @@ class mgContentItem } //! \brief returns the name of the album - string getAlbum (); + string getAlbum () const; //! \brief returns the name of genre 1 - string getGenre1 (); + string getGenre1 () const; //! \brief returns the name of genre 2 - string getGenre2 (); + string getGenre2 () const; //! \brief returns the name of genre 1 - string getGenre () - { - return getGenre1 (); - } + string getGenre () const; //! \brief returns the bitrate - string getBitrate () const - { - return m_bitrate; - } + string getBitrate () const; //! \brief returns the file name of the album image - string getImageFile (); + string getImageFile () const; //! \brief returns year - int getYear () const - { - return m_year; - } + int getYear () const; //! \brief returns rating - int getRating () const - { - return m_rating; - } + int getRating () const; //! \brief returns duration - int getDuration () const - { - return m_duration; - } + int getDuration () const; //! \brief returns samplerate - int getSampleRate () const - { - return m_samplerate; - } + int getSampleRate () const; //! \brief returns # of channels - int getChannels () const - { - return m_channels; - } + int getChannels () const; + private: long m_id; string m_title; @@ -557,6 +123,10 @@ class mgContentItem /*! * \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 { @@ -577,6 +147,13 @@ class mgSelection /*! \brief define various ways to play music in random order * \todo Party mode is not implemented, does same as SM_NORMAL */ +/*! \brief defines a field to be used as key for selection + * + * \param level 0 is the top level + * \param kt type of the key field. For possible values see mg_order.h + */ + void setKey (const unsigned int level, const mgKeyTypes kt); + enum ShuffleMode { SM_NONE, //!< \brief play normal sequence @@ -636,54 +213,32 @@ class mgSelection //! \brief the normal destructor ~mgSelection (); - //! \brief sets the top level directory where content is stored - void setToplevelDir(string ToplevelDir) { m_ToplevelDir = ToplevelDir; } - /*! \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 - * \todo we should do more caching. The last 5 result sets should be cached. */ - mgSelStrings values; + mutable mgSelStrings values; -/*! \brief defines a field to be used as key for selection - * - * \param level 0 is the top level - * \param name of the key field, internationalized. Possible values - * are defined by keychoice() +/*! \brief returns the name of a key */ - void setKey (const unsigned int level, const string name); + mgKeyTypes getKeyType (const unsigned int level) const; -/*! \brief returns the name of a key +//! \brief return the current value of this key + string getKeyValue (const unsigned int level) const; + +/*! \brief returns the current item from the value() list */ - string getKeyChoice (const unsigned int level) - { - return keys[level]->choice (); - } - //! \brief return the current value of this key - string getKeyValue (const unsigned int level) - { - return keys[level]->value (); - } + string getCurrentValue(); //! \brief returns a map (new allocated) for all used key fields and their values - map<string,string> * UsedKeyValues(); - -//! \brief helper function for << operator (dumps debug info) - void writeAt (ostream &); + map<mgKeyTypes,string> * UsedKeyValues(); -/*! \brief returns FROM and WHERE clauses for the current state - * of the selection. - * \param want_trackinfo work in progress, should disappear I hope - */ - string where (bool want_trackinfo = false); +//! \brief the number of key fields used for the query + unsigned int ordersize (); //! \brief the number of music items currently selected - unsigned int count (); - -//! \brief the number of key fields used for the query - unsigned int size (); + unsigned int count () const; //! \brief the current position in the current level unsigned int gotoPosition () @@ -692,12 +247,13 @@ class mgSelection } //! \brief the current position - unsigned int getPosition (unsigned int level) const; + unsigned int getPosition (unsigned int level)const; //! \brief go to the current position. If it does not exist, // go to the nearest. unsigned int gotoPosition (unsigned int level); + //! \brief the current position in the tracks list unsigned int getTrackPosition () const; @@ -779,28 +335,17 @@ class mgSelection return m_level; } -/*! \brief the possible choices for a keyfield in this level. - * keyfields already used in upper levels are no possible - * choices, neither are most keyfields if their usage would - * allow less than 2 choices. - */ - const strvector &keychoice (const unsigned int level); - -/*! \brief returns the current item from the value() list - */ - string getCurrentValue(); - //! \brief true if the selection holds no items bool empty(); -/*! \brief returns detailled info about all selected tracks. +/*! \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 vector < mgContentItem > &tracks () const; /*! \brief returns an item from the tracks() list * \param position the position in the tracks() list @@ -899,7 +444,7 @@ class mgSelection * last existing position * \return only if no position exists, false will be returned */ - void setTrack (unsigned int position); + void setTrackPosition (unsigned int position); /*! \brief skip some tracks in the track list * \return false if new position does not exist @@ -928,7 +473,7 @@ class mgSelection /*! \brief returns the sum of the durations of completed tracks * those are tracks before the current track position */ - unsigned long getCompletedLength (); + unsigned long getCompletedLength () const; /*! returns the number of tracks in the track list * \todo should not call tracks () which loads all track info. @@ -949,19 +494,19 @@ class mgSelection /*! 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 (); + string getListname () const; /*! \brief true if this selection currently selects a list of collections */ - bool isCollectionlist (); + bool isCollectionlist () const; //! \brief true if we have entered a collection - bool inCollection(const string Name=""); + 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); + void DumpState(mgValmap& nv) const; /*! \brief creates a new selection using saved definitions * \param nv this map contains the saved definitions @@ -969,60 +514,49 @@ class mgSelection mgSelection(mgValmap& nv); //! \brief clear the cache, next access will reload from data base - void clearCache(); + void clearCache() const; - //! \todo soll sql_values() nur noch bei Bedarf bauen, also muessen - // alle Aenderungen, die Einfluss darauf haben, clearCache machen - void refreshValues(); + void refreshValues() const; //! \brief true if values and tracks need to be reloaded - bool cacheIsEmpty() + 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; + private: - void AddOrder(const string sql,list<string>& orderlist, const string item); - list < string > m_fromtables; //!< \brief part result from previous where() - string m_from; //!< \brief part result from previous where() - string m_where; //!< \brief part result from previous where() + mutable map <mgKeyTypes, map<string,string> > map_values; + mutable map <mgKeyTypes, map<string,string> > 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; + //! \brief initializes maps for id/value mapping in both direction + bool loadvalues (mgKeyTypes kt) const; bool m_fall_through; vector < unsigned int >m_position; - unsigned int m_tracks_position; + mutable unsigned int m_tracks_position; ShuffleMode m_shuffle_mode; LoopMode m_loop_mode; MYSQL *m_db; + void setDB(MYSQL *db); string m_Host; string m_User; string m_Password; - string m_ToplevelDir; unsigned int m_level; long m_trackid; - string m_current_values; - string m_current_tracks; -//! \brief be careful when accessing this, see mgSelection::tracks() - vector < mgContentItem > m_tracks; - strvector m_ids; - strvector m_keychoice; - artistkeyfield kartist; - ratingkeyfield krating; - yearkeyfield kyear; - decadekeyfield kdecade; - albumkeyfield kalbum; - collectionkeyfield kcollection; - collectionitemkeyfield kcollectionitem; - genre1keyfield kgenre1; - genre2keyfield kgenre2; - langkeyfield klanguage; - titlekeyfield ktitle; - trackkeyfield ktrack; - map < string, keyfield * >all_keys; - map < string, keyfield * >trall_keys; - vector < keyfield * >keys; - bool UsedBefore (keyfield const *k, unsigned int level); + mgOrder order; + bool UsedBefore (const mgKeyTypes kt, unsigned int level) const; void InitSelection (); void InitDatabase (); - void initkey (keyfield & f); /*! \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. @@ -1031,13 +565,12 @@ class mgSelection * entries and the wrong tracks might be played. */ string sql_values (); - //! \todo das nach mgSelStrings verlagern unsigned int valindex (const string val,const bool second_try=false); string ListFilename (); string m_Directory; void loadgenres (); - MYSQL_RES *exec_sql (string query); - string get_col0 (string query); + MYSQL_RES * exec_sql(string query) const; + string get_col0 (string query) const; void InitFrom(const mgSelection* s); @@ -1046,25 +579,12 @@ class mgSelection * returning only one row. * \param query the SQL query to be executed */ - unsigned long mgSelection::exec_count (string query); + unsigned long mgSelection::exec_count (string query) const; - keyfield* findKey (const string name); - map < string, unsigned int > keycounts; -}; -//! \brief streams debug info about a selection -ostream & operator<< (ostream &, mgSelection & s); - -//! \brief convert the shuffle mode into a string -// \return strings "SM_NONE" etc. -string toString (mgSelection::ShuffleMode); - -//! \brief same as toString but returns a C string -const char *toCString (mgSelection::ShuffleMode); +}; -//string toString(long int l); -string itos (int i); unsigned int randrange (const unsigned int high); diff --git a/mg_filters.c b/mg_filters.c deleted file mode 100644 index e0457da..0000000 --- a/mg_filters.c +++ /dev/null @@ -1,297 +0,0 @@ -/*******************************************************************/ -/*! \file mg_filters.c - * \brief - ******************************************************************** - * \version $Revision: 1.3 $ - * \date $Date$ - * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author$ - */ -/*******************************************************************/ - -/* makes sure we dont parse the same declarations twice */ -#include <stdio.h> -#include "mg_filters.h" -#include "mg_tools.h" - - -//------------------------------------------------------------------- -// mgFilter -//------------------------------------------------------------------- -mgFilter::mgFilter(const char* name) -{ - m_name = strdup(name); -} -mgFilter::~mgFilter() -{ - free(m_name); -} - -const char* mgFilter::getName() -{ - return m_name; -} - -mgFilter::filterType mgFilter::getType() -{ - return m_type; -} - -//------------------------------------------------------------------- -// mgFilterInt -//------------------------------------------------------------------- -mgFilterInt::mgFilterInt(const char *name, int value, int min, int max) - : mgFilter(name) -{ - m_type = INT; - m_intval = value; - m_default_val = value; - m_stored_val = value; - m_max = max; - m_min = min; -} -mgFilterInt::~mgFilterInt() -{ -} - -std::string mgFilterInt::getStrVal() -{ - char buffer[20]; - sprintf(buffer, "%d", m_intval); - - return (std::string)buffer; -} - -int mgFilterInt::getIntVal() -{ - return (int) m_intval; -} - -int mgFilterInt::getVal() -{ - return m_intval; -} - -int mgFilterInt::getMin() -{ - return m_min; -} - -int mgFilterInt::getMax() -{ - return m_max; -} - -void mgFilterInt::store() -{ - m_stored_val = m_intval; -} -void mgFilterInt::restore() -{ - m_intval = m_stored_val; -} -void mgFilterInt::clear() -{ - m_stored_val = m_default_val; - m_intval = m_default_val; -} - -bool mgFilterInt::isSet() -{ - if(m_stored_val == m_default_val) - { - return false; - } - return true; -} - -//------------------------------------------------------------------- -// mgFilterString -//------------------------------------------------------------------- -mgFilterString::mgFilterString(const char *name, const char* value, - int maxlen, std::string allowedchar) - : mgFilter(name) -{ - m_type = STRING; - m_strval = strdup(value); - m_default_val = strdup(value); - m_stored_val = strdup(value); - m_allowedchar = allowedchar; - m_maxlen = maxlen; -} -mgFilterString::~mgFilterString() -{ - if(m_strval) - { - free(m_strval); - } -} - -int mgFilterString::getMaxLength() -{ - return m_maxlen; -} - -std::string mgFilterString::getAllowedChars() -{ - return m_allowedchar; -} -std::string mgFilterString::getStrVal() -{ - - return (std::string) m_strval; -} -void mgFilterString::store() -{ - if(m_stored_val) free(m_stored_val); - m_stored_val = strdup(m_strval); -} -void mgFilterString::restore() -{ - if(m_strval) free(m_strval); - m_strval = strdup(m_stored_val); -} -void mgFilterString::clear() -{ - if(m_stored_val) free(m_stored_val); - if(m_strval) free(m_strval); - - m_stored_val = strdup(m_default_val); - m_strval = strdup(m_default_val); -} - -bool mgFilterString::isSet() -{ - if(strlen(m_stored_val) == 0) - { - return false; - } - return true; -} -//------------------------------------------------------------------- -// mgFilterBool -//------------------------------------------------------------------- -mgFilterBool::mgFilterBool(const char *name, bool value, - std::string truestr, std::string falsestr) - : mgFilter(name) -{ - m_type = BOOL; - m_bval = (int) value; - m_default_val = value; - m_stored_val = value; - m_truestr = truestr; - m_falsestr = falsestr; - -} - -mgFilterBool::~mgFilterBool() -{ -} - -std::string mgFilterBool::getStrVal() -{ - if(m_bval) - return "true"; - else - return "false"; -} - -int mgFilterBool::getIntVal() -{ - return (int) m_bval; -} - -std::string mgFilterBool::getTrueString() -{ - return m_truestr; -} - -std::string mgFilterBool::getFalseString() -{ - return m_falsestr; -} - -bool mgFilterBool::getVal() -{ - return (bool) m_bval; -} - -void mgFilterBool::store() -{ - m_stored_val = (bool) m_bval; -} - -void mgFilterBool::restore() -{ - m_bval = (int) m_stored_val; -} - -void mgFilterBool::clear() -{ - m_stored_val = (int) m_default_val; - m_bval = (int) m_default_val; -} - -bool mgFilterBool::isSet() -{ - if(m_stored_val == m_default_val ) - { - return false; - } - return true; -} -//------------------------------------------------------------------- -// mgFilterChoice -//------------------------------------------------------------------- -mgFilterChoice::mgFilterChoice(const char *name, int value, std::vector<std::string> *choices) - : mgFilter(name) -{ - m_type = CHOICE; - m_choices = *choices; - m_selval = value; - m_default_val = value; - if( m_selval < 0 || m_selval >= (int) m_choices.size() ) - { - mgError("mgFilterChoice::mgFilterChoice(..): Illegal index %d", m_selval); - } -} -mgFilterChoice::~mgFilterChoice() -{ - m_choices.clear(); -} - -std::string mgFilterChoice::getStrVal() -{ - if( m_selval < 0 || m_selval >= (int) m_choices.size() ) - { - mgError("mgFilterChoice::getStrVal(): Illegal index %d", m_selval); - } - return m_choices[m_selval]; -} -std::vector<std::string> &mgFilterChoice::getChoices() -{ - return m_choices; -} -void mgFilterChoice::store() -{ - m_stored_val = m_selval; - -} -void mgFilterChoice::restore() -{ - m_selval = m_stored_val; -} -void mgFilterChoice::clear() -{ - m_stored_val = m_default_val; - m_selval = m_default_val; -} - -bool mgFilterChoice::isSet() -{ - if(m_stored_val == m_default_val) - { - return false; - } - return true; -} diff --git a/mg_filters.h b/mg_filters.h deleted file mode 100644 index 658d943..0000000 --- a/mg_filters.h +++ /dev/null @@ -1,171 +0,0 @@ -/*! \file mg_filters.h - * \brief Top level access to media in vdr plugin muggle - * for the vdr muggle plugindatabase - * - * \version $Revision: 1.2 $ - * \date $Date$ - * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author$ - */ - -#ifndef _MG_FILTERS_H -#define _MG_FILTERS_H - -#include <string> -#include <vector> - -/*! - * \brief abstract base class for representation of filter values with boundaries - */ -class mgFilter -{ - public: - - typedef enum filterType - { - UNDEF = 0, - INT, - STRING, - BOOL, - CHOICE - } filterType; - - protected: - filterType m_type; - char* m_name; - - public: - - mgFilter(const char* name); - - virtual ~mgFilter(); - - filterType getType(); - - const char* getName(); - - virtual std::string getStrVal() = 0; - - virtual int getIntVal() - { return 0; } - - virtual void store() = 0; - - virtual void restore() = 0; - - virtual void clear() = 0; - - virtual bool isSet() = 0; -}; - -/*! - * \class mgFilterInt - */ -class mgFilterInt : public mgFilter -{ - private: - int m_min; - int m_max; - int m_stored_val; - int m_default_val; - - public: - int m_intval; - - mgFilterInt(const char *name, int value, int min = 0, int max = 9999); - virtual ~mgFilterInt(); - - int getVal(); - int getMin(); - int getMax(); - virtual std::string getStrVal(); - virtual int getIntVal(); - virtual void store(); - virtual void restore(); - virtual void clear(); - virtual bool isSet(); -}; - -/*! - * \class mgFilterString - */ -class mgFilterString : public mgFilter -{ - private: - std::string m_allowedchar; - int m_maxlen; - char* m_stored_val; - char* m_default_val; - - public: - char* m_strval; - - mgFilterString(const char *name, const char* value, int maxlen=255, - std::string allowedchar="abcdefghijklmnopqrstuvwxyz0123456789-"); - - virtual ~mgFilterString(); - - int getMaxLength(); - std::string getAllowedChars(); - virtual std::string getStrVal(); - virtual void store(); - virtual void restore(); - virtual void clear(); - virtual bool isSet(); -}; - -/*! - * \class mgFilterBool - */ -class mgFilterBool : public mgFilter -{ - private: - std::string m_truestr; - std::string m_falsestr; - bool m_stored_val; - bool m_default_val; - - public: - int m_bval; - - mgFilterBool(const char *name, bool value, - std::string truestr="yes", std::string falsestr="no"); - virtual ~mgFilterBool(); - - virtual std::string getStrVal(); - virtual int getIntVal(); - std::string getTrueString(); - std::string getFalseString(); - bool getVal(); - virtual void store(); - virtual void restore(); - virtual void clear(); - virtual bool isSet(); -}; - -/*! - * \class mgFilterChoice - */ -class mgFilterChoice : public mgFilter -{ - private: - std::vector<std::string> m_choices; - int m_stored_val; - int m_default_val; - - public: - int m_selval; // index of the currently selected item - - mgFilterChoice(const char *name, int val, std::vector<std::string> *choices); - virtual ~mgFilterChoice(); - - virtual std::string getStrVal(); - virtual std::vector<std::string> &getChoices(); - virtual void store(); - virtual void restore(); - virtual void clear(); - virtual bool isSet(); -}; - - -#endif diff --git a/mg_order.c b/mg_order.c new file mode 100644 index 0000000..ead9436 --- /dev/null +++ b/mg_order.c @@ -0,0 +1,775 @@ +#include "mg_order.h" +#include "mg_tools.h" +#include "i18n.h" + +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; +} + + +string +sql_string (MYSQL *db, const string s) +{ + if (!db) + return ""; + char *buf = (char *) malloc (s.size () * 2 + 1); + mysql_real_escape_string (db, buf, s.c_str (), s.size ()); + string result = "'" + std::string (buf) + "'"; + free (buf); + 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; +} + +/*! \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; +} + +MYSQL_RES * +exec_sql (MYSQL *db,string query) +{ + if (!db) + return 0; + if (query.empty()) + return 0; + mgDebug(3,"exec_sql(%X,%s)",db,query.c_str()); + if (mysql_query (db, (query + ';').c_str ())) + { + mgError("SQL Error in %s: %s",query.c_str(),mysql_error (db)); + std::cout<<"ERROR in " << query << ":" << mysql_error(db)<<std::endl; + return 0; + } + return mysql_store_result (db); +} + + +class mgKeyNormal : public mgKey { + public: + mgKeyNormal(const mgKeyNormal& k); + mgKeyNormal(const mgKeyTypes kt, string table, string field); + virtual mgParts Parts(bool orderby=false) const; + string value() const; + string id() const; + void set(string value,string id); + mgKeyTypes Type() const { return m_kt; } + virtual string expr() const { return field(); } + virtual string field() const { return m_table + "." + m_field; } + virtual string table() const { return m_table; } + protected: + virtual string orderfield() const { return expr(); } + void AddIdClause(mgParts &result,string what) const; + private: + mgKeyTypes m_kt; + string m_field; + string m_table; + string m_value; + string m_id; +}; + +class mgKeyTrack : public mgKeyNormal { + public: + mgKeyTrack() : mgKeyNormal(keyTrack,"tracks","tracknb") {}; + mgParts Parts(bool orderby=false) const; +}; + +class mgKeyGenre1 : public mgKeyNormal { + public: + mgKeyGenre1() : mgKeyNormal(keyGenre1,"tracks","genre1") {}; + mgParts Parts(bool orderby=false) const; + string map_idfield() const { return "id"; } + string map_valuefield() const { return "genre"; } + string map_valuetable() const { return "genre"; } +}; + +class mgKeyGenre2 : public mgKeyNormal { + public: + mgKeyGenre2() : mgKeyNormal(keyGenre2,"tracks","genre2") {}; + mgParts Parts(bool orderby=false) const; + string map_idfield() const { return "id"; } + string map_valuefield() const { return "genre"; } + string map_valuetable() const { return "genre"; } +}; + +class mgKeyLanguage : public mgKeyNormal { + public: + mgKeyLanguage() : mgKeyNormal(keyLanguage,"tracks","lang") {}; + mgParts Parts(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(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(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_id; +} + +string +mgKeyNormal::value() const +{ + return m_value; +} + + +mgKey::mgKey() +{ + m_db = 0; +} + +mgKey::~mgKey() +{ +} + + +mgKeyNormal::mgKeyNormal(const mgKeyNormal& k) +{ + m_kt = k.m_kt; + m_table = k.m_table; + m_field = k.m_field; + m_id = k.m_id; + m_db = k.m_db; +} + +mgKeyNormal::mgKeyNormal(const mgKeyTypes kt, string table, string field) +{ + m_kt = kt; + m_table = table; + m_field = field; + m_id = EMPTY; + m_db = 0; +} + +void +mgKeyNormal::set(string value, string id) +{ + m_value = value; + m_id = id; +} + +mgParts::mgParts() +{ +} + +mgParts::~mgParts() +{ +} + +mgParts +mgKeyNormal::Parts(bool orderby) const +{ + assert(strlen(m_db->host)); + mgParts result; + result.tables.push_back(table()); + AddIdClause(result,expr()); + if (orderby) + { + result.fields.push_back(expr()); + result.orders.push_back(orderfield()); + } + return result; +} + +void +mgKeyNormal::AddIdClause(mgParts &result,string what) const +{ + assert(strlen(m_db->host)); + if (id() != EMPTY) + { + string op; + string xid; + if (id() == "'NULL'") + { + op = "is"; + xid = "NULL"; + } + else + { + op = "="; + xid = sql_string(m_db,id()); + } + string clause = ""; + result.clauses.push_back(undequal(clause,what,op,xid)); + } +} + +mgParts +mgKeyTrack::Parts(bool orderby) const +{ + mgParts result; + result.tables.push_back("tracks"); + AddIdClause(result,"tracks.title"); + if (orderby) + { + // if you change tracks.title, please also + // change mgContentItem::getKeyValue() + result.fields.push_back("tracks.title"); + result.orders.push_back("tracks.tracknb"); + } + return result; +} + +mgParts +mgKeyGenre1::Parts(bool orderby) const +{ + mgParts result; + AddIdClause(result,"tracks.genre1"); + result.tables.push_back("tracks"); + if (orderby) + { + result.fields.push_back("genre.genre"); + result.fields.push_back("tracks.genre1"); + result.tables.push_back("genre"); + result.orders.push_back("genre.genre"); + } + return result; +} + +mgParts +mgKeyGenre2::Parts(bool orderby) const +{ + mgParts result; + AddIdClause(result,"tracks.genre2"); + result.tables.push_back("tracks"); + if (orderby) + { + result.fields.push_back("genre.genre"); + result.fields.push_back("tracks.genre2"); + result.tables.push_back("genre"); + result.orders.push_back("genre.genre"); + } + return result; +} + +mgParts +mgKeyLanguage::Parts(bool orderby) const +{ + mgParts result; + AddIdClause(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(bool orderby) const +{ + mgParts result; + if (orderby) + { + result.tables.push_back("playlist"); + AddIdClause(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(result,"playlistitem.playlist"); + } + return result; +} + +mgParts +mgKeyCollectionItem::Parts(bool orderby) const +{ + assert(strlen(m_db->host)); + mgParts result; + result.tables.push_back("playlistitem"); + AddIdClause(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; +} + + +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,list < string > 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 (); +} + +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) +{ + Prepare(); + string result = ""; + if (distinct) + result += sql_list("SELECT DISTINCT",fields); + else + result += sql_list("SELECT",fields); + if (result.empty()) + return result; + result += sql_list("FROM",tables); + result += sql_list("WHERE",clauses," AND "); + 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() +{ +} + +mgKey* +mgOrder::Key(unsigned int idx) const +{ + return Keys[idx]; +} + +mgKey*& +mgOrder::operator[](unsigned int idx) +{ + assert(idx<Keys.size()); + return Keys[idx]; +} + +const mgOrder& +mgOrder::operator=(const mgOrder& from) +{ + Name = from.Name; + Keys.clear(); + for (unsigned int i = 0; i < from.size();i++) + { + mgKey *k = ktGenerate(from.getKeyType(i),m_db); + k->set(from.getKeyValue(i),from.getKeyId(i)); + Keys.push_back(k); + } + setDB(from.m_db); + return *this; +} + +mgOrder& +mgOrder::operator+=(mgKey* k) { + k->setdb(m_db); + Keys.push_back(k); + return *this; +} + +mgKeyTypes +mgOrder::getKeyType(unsigned int idx) const +{ + assert(idx<Keys.size()); + return Keys[idx]->Type(); +} + +string +mgOrder::getKeyValue(unsigned int idx) const +{ + assert(idx<Keys.size()); + return Keys[idx]->value(); +} + +string +mgOrder::getKeyId(unsigned int idx) const +{ + assert(idx<Keys.size()); + return Keys[idx]->id(); +} + +void +mgOrder::setDB(MYSQL *db) +{ + m_db = db; + keyvector::iterator i; + for (i = Keys.begin () ; i != Keys.end (); i++) + { + (*i)->setdb(db); + } +} + +mgKey* +mgOrder::find(const mgKeyTypes kt) +{ + keyvector::iterator i; + for (i = Keys.begin () ; i != Keys.end (); i++) + { + if ((*i)->Type() == kt) + return *i; + } + return 0; +} + +void +mgOrder::truncate(unsigned int i) +{ + while (size()>i) + { + delete Keys.back(); + Keys.pop_back(); + } +} + +void +mgOrder::clean() +{ + // remove double entries: + keyvector::iterator i; + keyvector::iterator j; + bool album_found = false; + bool tracknb_found = false; + bool title_found = false; + for (i = Keys.begin () ; i != Keys.end (); i++) + { + mgKeyNormal* k = dynamic_cast<mgKeyNormal*>(*i); + album_found |= (k->Type()==keyAlbum); + tracknb_found |= (k->Type()==keyTrack); + title_found |= (k->Type()==keyTitle); + if (tracknb_found || (album_found && title_found)) + { + for (j = i+1 ; j !=Keys.end(); j++) + delete *j; + Keys.erase(i+1,Keys.end ()); + break; + } + for (j = i+1 ; j != Keys.end(); j++) + if (*i == *j) { + delete *j; + Keys.erase(j); + } + } +} + + +mgParts +mgOrder::Parts(unsigned int level,bool orderby) const +{ + assert(strlen(m_db->host)); + mgParts result; + for (unsigned int i=0;i<=level;i++) + { + if (i==Keys.size()) break; + mgKeyNormal *k = dynamic_cast<mgKeyNormal*>(Keys[i]); + k->setdb(m_db); + result += k->Parts(orderby && (i==level)); + } + return result; +} + +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","genre1","genre","id")); + push_back(mgReference ("tracks","genre2","genre","id")); +} + +bool +mgReferences::Equal(unsigned int i,string table1, string table2) const +{ + return (((at(i).t1()==table1) && (at(i).t2()==table2)) + || ((at(i).t1()==table2) && (at(i).t2()==table1))); +} + +mgParts +mgReferences::FindConnectionBetween(string table1, string table2) const +{ + for (unsigned int i=0 ; i<size(); i++ ) + if (Equal(i,table1,table2)) + return mgRefParts(at(i)); + return mgParts(); +} + +mgParts +mgReferences::ConnectToTracks(string table) const +{ + mgParts result; + result += FindConnectionBetween(table,"tracks"); + if (result.empty()) + { + result += FindConnectionBetween(table,"playlistitem"); + if (!result.empty()) + { + result += FindConnectionBetween("playlistitem","tracks"); + } + else + assert(false); + } + return result; +} + +mgParts +mgReferences::Connect(string table1, string table2) const +{ + mgParts result; + // same table? + if (table1 == table2) return result; + // do not connect aliases. See sql_delete_from_collection + if (table1.find(" as ")!=string::npos) return result; + if (table2.find(" as ")!=string::npos) return result; + if (table1.find(" AS ")!=string::npos) return result; + if (table2.find(" AS ")!=string::npos) return result; + // direct connection? + result += FindConnectionBetween(table1,table2); + if (result.empty()) + { + // indirect connection? try connecting via tracks + result += ConnectToTracks(table1); + result += ConnectToTracks(table2); + } + return result; +} + + +mgKey* +ktGenerate(const mgKeyTypes kt,MYSQL* db) +{ + mgKey* result = 0; + switch (kt) + { + case keyGenre1: result = new mgKeyGenre1;break; + case keyGenre2: result = new mgKeyGenre2;break; + case keyArtist: result = new mgKeyNormal(kt,"tracks","artist");break; + case keyTitle: result = new mgKeyNormal(kt,"tracks","title");break; + case keyTrack: result = new mgKeyTrack;break; + case keyDecade: result = new mgKeyDecade;break; + case keyAlbum: result = new mgKeyNormal(kt,"album","title");break; + case keyCollection: result = new mgKeyCollection;break; + case keyCollectionItem: result = new mgKeyCollectionItem;break; + case keyLanguage: result = new mgKeyLanguage;break; + case keyRating: result = new mgKeyNormal(kt,"tracks","rating");break; + case keyYear: result = new mgKeyNormal(kt,"tracks","year");break; + } + if (result) result->setdb(db); + return result; +} + +const char * const +ktName(const mgKeyTypes kt) +{ + const char * result = ""; + switch (kt) + { + case keyGenre1: result = "Genre";break; + case keyGenre2: result = "Genre 2";break; + case keyArtist: result = "Artist";break; + case keyTitle: result = "Title";break; + case keyTrack: result = "Track";break; + case keyDecade: result = "Decade";break; + case keyAlbum: result = "Album";break; + case keyCollection: result = "Collection";break; + case keyCollectionItem: result = "Collection item";break; + case keyLanguage: result = "Language";break; + case keyRating: result = "Rating";break; + case keyYear: result = "Year";break; + } + return tr(result); +} + diff --git a/mg_order.h b/mg_order.h new file mode 100644 index 0000000..d46f244 --- /dev/null +++ b/mg_order.h @@ -0,0 +1,154 @@ +#ifndef _MG_SQL_H +#define _MG_SQL_H +#include <stdlib.h> +#include <mysql/mysql.h> +#include <typeinfo> +#include <string> +#include <assert.h> +#include <list> +#include <vector> +#include <iostream> +#include <istream> +#include <sstream> +#include <ostream> + +using namespace std; + +typedef list<string> strlist; + +strlist& operator+=(strlist&a, strlist b); + +static const string EMPTY = "XNICHTGESETZTX"; + +//! \brief adds string n to string s, using string sep to separate them +string& addsep (string & s, string sep, string n); + +enum mgKeyTypes { + keyGenre1 = 1, + keyGenre2, + keyArtist, + keyTitle, + keyTrack, + keyDecade, + keyCollection, + keyCollectionItem, + keyAlbum, + keyLanguage, + keyRating, + keyYear, +}; +const mgKeyTypes mgKeyTypesHigh = keyYear; + +class mgParts; + +class mgReference { + public: + mgReference(string t1,string f1,string t2,string f2); + string t1() const { return m_t1; } + string t2() const { return m_t2; } + string f1() const { return m_f1; } + string f2() const { return m_f2; } + private: + string m_t1; + string m_t2; + string m_f1; + string m_f2; +}; + +class mgReferences : public vector<mgReference> { +public: + // \todo memory leak for vector ref? + mgReferences(); + mgParts Connect(string c1, string c2) const; +private: + bool Equal(unsigned int i,string table1, string table2) const; + mgParts FindConnectionBetween(string table1, string table2) const; + mgParts ConnectToTracks(string table) const; +}; + +class mgKey { + public: + mgKey(); + virtual ~mgKey(); + virtual mgParts Parts(bool orderby=false) const = 0; + virtual string id() const = 0; + virtual string value () const = 0; + //!\brief translate field into user friendly string + virtual void set(string value, string id) = 0; + virtual mgKeyTypes Type() const = 0; + virtual string map_idfield() const { return ""; } + virtual string map_valuefield() const { return ""; } + virtual string map_valuetable() const { return ""; } + void setdb(MYSQL *db) { m_db = db; } + protected: + MYSQL *m_db; +}; + + +mgKey* +ktGenerate(const mgKeyTypes kt,MYSQL *db); + +const char * const +ktName(const mgKeyTypes kt); + +typedef vector<mgKey*> keyvector; + +class mgParts { +public: + mgParts(); + ~mgParts(); + strlist fields; + strlist tables; + strlist clauses; + strlist orders; + mgParts& operator+=(mgParts a); + void Prepare(); + string sql_count(); + string sql_select(bool distinct=true); + string sql_delete_from_collection(string pid); + string sql_update(strlist new_values); + bool empty() const { return tables.size()==0;} +private: + bool UsesTracks(); + mgReferences ref; +}; + +string +sql_string (MYSQL *db, const string s); + +MYSQL_RES * exec_sql (MYSQL *db,string query); + +//! \brief converts long to string +string itos (int i); + +//! \brief convert long to string +string ltos (long l); + + +const unsigned int MaxKeys = 20; + +class mgOrder { +public: + mgOrder(); + void setDB(MYSQL *db); + mgParts Parts(unsigned int level,bool orderby=true) const; + string Name; + const mgOrder& operator=(const mgOrder& from); + mgOrder& operator+=(mgKey* k); + mgKey*& operator[](unsigned int idx); + unsigned int size() const { return Keys.size(); } + void truncate(unsigned int i); + bool empty() const { return Keys.empty(); } + void clear() { Keys.clear();} + void clean(); + mgKey* Key(unsigned int idx) const; + mgKey* find(const mgKeyTypes kt) ; + mgKeyTypes getKeyType(unsigned int idx) const; + string getKeyValue(unsigned int idx) const; + string getKeyId(unsigned int idx) const; +private: + MYSQL *m_db; + keyvector Keys; +}; + +#endif // _MG_SQL_H diff --git a/mg_valmap.c b/mg_valmap.c new file mode 100644 index 0000000..f5d8e02 --- /dev/null +++ b/mg_valmap.c @@ -0,0 +1,70 @@ +#include "mg_valmap.h" +#include "mg_order.h" + +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); +} + + diff --git a/mg_valmap.h b/mg_valmap.h new file mode 100644 index 0000000..a1da822 --- /dev/null +++ b/mg_valmap.h @@ -0,0 +1,50 @@ +#ifndef _MG_VALMAP_H +#define _MG_VALMAP_H +#include <string> +#include <map> + +using namespace std; + +//! \brief a map for reading / writing configuration data. +class mgValmap : public map<string,string> { + private: + const char *m_key; + public: + /*! \brief constructor + * \param key all names will be prefixed with key. + */ + mgValmap(const char *key); + //! \brief read from file + void Read(FILE *f); + //! \brief write to file + void Write(FILE *f); + //! \brief enter a string value + void put(const char*name, string value); + //! \brief enter a C string value + void put(const char*name, const char* value); + //! \brief enter a long value + void put(const char*name, long value); + //! \brief enter a int value + void put(const char*name, int value); + //! \brief enter a unsigned int value + void put(const char*name, unsigned int value); + //! \brief enter a bool value + void put(const char*name, bool value); + //! \brief return a string + string getstr(const char* name) { + return (*this)[name]; + } + //! \brief return a C string + bool getbool(const char* name) { + return (getstr(name)=="true"); + } + //! \brief return a long + long getlong(const char* name) { + return atol(getstr(name).c_str()); + } + //! \brief return an unsigned int + unsigned int getuint(const char* name) { + return (unsigned long)getlong(name); + } +}; +#endif @@ -63,7 +63,6 @@ mgMuggle::mgMuggle (void) mgMuggle::~mgMuggle () { -// Clean up after yourself! if (main) main->SaveState(); } diff --git a/vdr_decoder.c b/vdr_decoder.c index 9b47fa8..9a7bb7c 100644 --- a/vdr_decoder.c +++ b/vdr_decoder.c @@ -23,7 +23,14 @@ #include "vdr_decoder.h" #include "vdr_decoder_mp3.h" + +#ifdef HAVE_VORBISFILE #include "vdr_decoder_ogg.h" +#endif + +#ifdef HAVE_FLAC +#include "vdr_decoder_flac.h" +#endif #include "mg_db.h" @@ -34,8 +41,7 @@ mgMediaType mgDecoders::getMediaType (std::string s) { - mgMediaType - mt = MT_UNKNOWN; + mgMediaType mt = MT_UNKNOWN; // TODO: currently handles only mp3. LVW char * @@ -56,6 +62,13 @@ mgMediaType mgDecoders::getMediaType (std::string s) { mt = MT_OGG; } + else + { + if (!strcmp (p, ".flac")) + { + mt = MT_FLAC; + } + } } return mt; } @@ -76,17 +89,21 @@ mgDecoders::findDecoder (mgContentItem * item) } switch (getMediaType (filename)) { - case MT_MP3: - { - decoder = new mgMP3Decoder (item); - } - break; + case MT_MP3: + { + decoder = new mgMP3Decoder (item); + } break; #ifdef HAVE_VORBISFILE - case MT_OGG: - { - decoder = new mgOggDecoder (item); - } - break; + case MT_OGG: + { + decoder = new mgOggDecoder (item); + } break; +#endif +#ifdef HAVE_FLAC + case MT_FLAC: + { + decoder = new mgFlacDecoder( item ); + } break; #endif /* case MT_MP3_STREAM: decoder = new mgMP3StreamDecoder(full); break; @@ -124,23 +141,21 @@ mgDecoder::mgDecoder (mgContentItem * item) m_playing = false; } - mgDecoder::~mgDecoder () { } - void mgDecoder::lock (bool urgent) { m_locklock.Lock (); if (urgent && m_locked) - { + { m_urgentLock = true; // signal other locks to release quickly - } + } m_locked++; - + m_locklock.Unlock (); // don't hold the "locklock" when locking "lock", may cause a deadlock m_lock.Lock (); m_urgentLock = false; diff --git a/vdr_decoder.h b/vdr_decoder.h index ee943ce..3dd6c00 100644 --- a/vdr_decoder.h +++ b/vdr_decoder.h @@ -82,71 +82,71 @@ class mgDecoder { protected: -/*! \brief database handle to the track being decoded */ - mgContentItem * m_item; - -/*! \brief The currently playing file */ - std::string m_filename; - -/*! \brief Mutexes to coordinate threads */ - cMutex m_lock, m_locklock; - int m_locked; - bool m_urgentLock; - -/*! \brief Whether the decoder is currently active */ - bool m_playing; - -/*! \brief ??? */ - mgPlayInfo m_playinfo; - -/*! \brief Place a lock */ - virtual void lock (bool urgent = false); - -/*! \brief Release a lock */ - virtual void unlock (void); - -/*! \brief Try to obtain a lock */ - virtual bool tryLock (void); - - public: - -//@{ -/*! \brief The constructor */ - mgDecoder (mgContentItem * item); - -/*! \brief The destructor */ - virtual ~ mgDecoder (); -//@} - -/*! \brief Whether a decoder instance is able to play the given file */ - virtual bool valid () = 0; - -/*! \brief Whether a stream (i.e. from the network is being decoded */ - virtual bool isStream () - { - return false; - } - -/*! \brief Start decoding */ - virtual bool start () = 0; - -/*! \brief Stop decoding */ - virtual bool stop () = 0; - -/*! \brief Skip an amount of time. Impossible by default */ - virtual bool skip (int seconds, int avail, int rate) - { - return false; - } - -/*! \brief Return decoded data */ - virtual struct mgDecode *decode () = 0; - -/*! \brief Information about the current playback status */ - virtual mgPlayInfo *playInfo () - { - return 0; - } + /*! \brief database handle to the track being decoded */ + mgContentItem * m_item; + + /*! \brief The currently playing file */ + std::string m_filename; + + /*! \brief Mutexes to coordinate threads */ + cMutex m_lock, m_locklock; + int m_locked; + bool m_urgentLock; + + /*! \brief Whether the decoder is currently active */ + bool m_playing; + + /*! \brief ??? */ + mgPlayInfo m_playinfo; + + /*! \brief Place a lock */ + virtual void lock (bool urgent = false); + + /*! \brief Release a lock */ + virtual void unlock (void); + + /*! \brief Try to obtain a lock */ + virtual bool tryLock (void); + + public: + + //@{ + /*! \brief The constructor */ + mgDecoder (mgContentItem * item); + + /*! \brief The destructor */ + virtual ~ mgDecoder (); + //@} + + /*! \brief Whether a decoder instance is able to play the given file */ + virtual bool valid () = 0; + + /*! \brief Whether a stream (i.e. from the network is being decoded */ + virtual bool isStream () + { + return false; + } + + /*! \brief Start decoding */ + virtual bool start () = 0; + + /*! \brief Stop decoding */ + virtual bool stop () = 0; + + /*! \brief Skip an amount of time. Impossible by default */ + virtual bool skip (int seconds, int avail, int rate) + { + return false; + } + + /*! \brief Return decoded data */ + virtual struct mgDecode *decode () = 0; + + /*! \brief Information about the current playback status */ + virtual mgPlayInfo *playInfo () + { + return 0; + } }; // ---------------------------------------------------------------- @@ -156,15 +156,15 @@ class mgDecoder */ class mgDecoders { - public: - -/*! \brief Try to find a valid decoder for a file - */ - static mgDecoder *findDecoder (mgContentItem * item); - -/*! \brief determine the media type for a given source - */ - static mgMediaType getMediaType (std::string filename); - + public: + + /*! \brief Try to find a valid decoder for a file + */ + static mgDecoder *findDecoder (mgContentItem * item); + + /*! \brief determine the media type for a given source + */ + static mgMediaType getMediaType (std::string filename); + }; #endif //___DECODER_H diff --git a/vdr_decoder_flac.c b/vdr_decoder_flac.c new file mode 100644 index 0000000..1a66e8d --- /dev/null +++ b/vdr_decoder_flac.c @@ -0,0 +1,380 @@ +/*! \file vdr_decoder_flac.c + * \ingroup vdr + * + * The file implements a decoder which is used by the player to decode flac audio files. + * + * Based on code from + * MP3/MPlayer plugin to VDR (C++) + * (C) 2001-2003 Stefan Huelswitt <huels@iname.com> + */ + +#ifdef HAVE_FLAC + +#define DEBUG +#include "vdr_decoder_flac.h" +#include "mg_tools.h" +#include "mg_db.h" + +#include <mad.h> + +#include <string> +#include <stdlib.h> +#include <stdio.h> + +using namespace std; + +static const unsigned MAX_RES_SIZE = 16384; + +// --- mgFlacDecoder ------------------------------------------------------------- + +mgFlacDecoder::mgFlacDecoder( mgContentItem *item ) + : mgDecoder( item ), FLAC::Decoder::File() +{ + mgLog lg( "mgFlacDecoder::mgFlacDecoder" ); + + m_filename = item->getSourceFile(); + // m_filename = "/test.flac"; + m_pcm = 0; + m_reservoir = 0; + + initialize(); +} + +mgFlacDecoder::~mgFlacDecoder() +{ + mgLog lg( "mgFlacDecoder::~mgFlacDecoder" ); + clean(); +} + +bool mgFlacDecoder::valid() +{ + // how to check whether this is a valid flac file? + return is_valid(); +} + +mgPlayInfo *mgFlacDecoder::playInfo(void) +{ + return 0; +} + +bool mgFlacDecoder::initialize() +{ + mgLog lg( "mgFlacDecoder::initialize" ); + bool state = true; + + clean(); + + // set_metadata_ignore_all(); + set_metadata_respond( FLAC__METADATA_TYPE_STREAMINFO ); + set_filename( m_filename.c_str() ); + + m_first = true; + m_reservoir_count = 0; + m_current_time_ms = 0; + m_len_decoded = 0; + m_index = 0; + m_pcm = new struct mad_pcm; + + // init reservoir buffer; this should be according to the maximum + // frame/sample size that we can probably obtain from metadata + m_reservoir = new (FLAC__int32*)[2]; + m_reservoir[0] = new FLAC__int32[MAX_RES_SIZE]; + m_reservoir[1] = new FLAC__int32[MAX_RES_SIZE]; + + FLAC::Decoder::File::State d = init(); // TODO check this? + + cout << "Status of decoder: " << string( d.as_cstring() ) << endl; + FLAC::Decoder::SeekableStream::State ssd = get_seekable_stream_decoder_state(); + cout << "Status of seekable stream decoder: " << string( ssd.as_cstring() ) << endl; + FLAC::Decoder::Stream::State sd = get_stream_decoder_state(); + cout << "Status of stream decoder: " << string( sd.as_cstring() ) << endl; + // set state accordingly + + process_until_end_of_metadata(); // basically just skip metadata + + return state; +} + +bool mgFlacDecoder::clean() +{ + mgLog lg( "mgFlacDecoder::clean" ); + m_playing = false; + + delete m_pcm; + m_pcm = 0; + + if( m_reservoir ) + { + delete m_reservoir[0]; + delete m_reservoir[1]; + } + delete m_reservoir; + + // why false? true? + return true; +} + +bool mgFlacDecoder::start() +{ + MGLOG( "mgFlacDecoder::start" ); + bool res = false; + lock(true); + + /* + FLAC::Decoder::File::State d = get_state(); + cout << "Status of decoder: " << string( d.as_cstring() ) << endl; + FLAC::Decoder::SeekableStream::State ssd = get_seekable_stream_decoder_state(); + cout << "Status of seekable stream decoder: " << string( ssd.as_cstring() ) << endl; + FLAC::Decoder::Stream::State sd = get_stream_decoder_state(); + cout << "Status of stream decoder: " << string( sd.as_cstring() ) << endl; + */ + + // can FLAC handle more than 2 channels anyway? + if( m_item->getChannels() <= 2 ) + { + m_playing = true; + res = true; + } + else + { + mgError( "ERROR: cannot play flac file %s: more than 2 channels", m_filename.c_str() ); + clean(); + } + + unlock(); + return res; +} + +bool mgFlacDecoder::stop(void) +{ + MGLOG( "mgFlacDecoder::stop" ); + lock(); + finish(); + + if( m_playing ) + { + clean(); + } + unlock(); + + return true; +} + +struct mgDecode *mgFlacDecoder::done( eDecodeStatus status ) +{ + m_ds.status = status; + m_ds.index = m_index; + m_ds.pcm = m_pcm; + + unlock(); // release the lock from Decode() ! + + return &m_ds; +} + +struct mgDecode *mgFlacDecoder::decode() +{ + // mgLog lg( "mgFlacDecoder::decode" ); + m_decode_status = dsPlay; + + const unsigned SF_SAMPLES = (sizeof(m_pcm->samples[0])/sizeof(mad_fixed_t)); + + lock(true); // this is released in done() + + if( m_playing ) + { + m_pcm->samplerate = m_item->getSampleRate(); // from database + m_pcm->channels = m_item->getChannels(); // from database + + // if there is enough data in the reservoir, don't start decoding + // PROBLEM: but we need a first time! + bool finished; + if( m_first ) + { + finished = false; + m_first = false; + } + else + { + finished = m_reservoir_count >= SF_SAMPLES; + } + + while( !finished ) + { // decode single frames until m_reservoir_count >= SF_SAMPLES or eof/error + m_first = false; + + // decode a single sample into reservoir_buffer (done by the write callback) + process_single(); + if (get_stream_decoder_state()==FLAC__STREAM_DECODER_END_OF_STREAM) + { + m_decode_status = dsEof; + finished = true; + } + + // check termination criterion + finished |= m_reservoir_count >= SF_SAMPLES || m_len_decoded == 0; // or error? + } + + // transfer min( SF_SAMPLES, m_reservoir_count ) to pcm buffer + + int n = ( SF_SAMPLES <= m_reservoir_count )? SF_SAMPLES: m_reservoir_count; + + m_pcm->length = n; + m_index = m_current_time_ms; + + // fill pcm container from reservoir buffer + FLAC__int32 *data0 = m_reservoir[0]; + FLAC__int32 *data1 = m_reservoir[1]; + + mad_fixed_t *sam0 = m_pcm->samples[0]; + mad_fixed_t *sam1 = m_pcm->samples[1]; + + // determine shift value for mad_fixed conversion + // TODO -- check for real bitsize and shit accordingly (left/right) + const int s = MAD_F_FRACBITS + 1 - ( sizeof(short)*8 ); + // const int s = ( sizeof(int)*8 ) - 1 - MAD_F_FRACBITS; // from libsoundfile decoder + + if( m_pcm->channels > 1 ) + { + for( int j=n; j > 0 ; j-- ) + { + // copy buffer and transform (cf. libsoundfile decoder) + *sam0++ = (*data0++) << s; + *sam1++ = (*data1++) << s; + } + // "delete" transferred samples from reservoir buffer + memmove( m_reservoir[0], m_reservoir[0] + n, (m_reservoir_count - n)*sizeof(FLAC__int32) ); + memmove( m_reservoir[1], m_reservoir[1] + n, (m_reservoir_count - n)*sizeof(FLAC__int32) ); + } + else + { + for( int j=n; j > 0 ; j--) + { + *sam0++ = (*data0++) << s; + } + memmove( m_reservoir[0], m_reservoir[0] + n, (m_reservoir_count - n)*sizeof(FLAC__int32) ); + } + m_reservoir_count -= n; + + FLAC::Decoder::File::State d = get_state(); + FLAC::Decoder::SeekableStream::State ssd = get_seekable_stream_decoder_state(); + FLAC::Decoder::Stream::State sd = get_stream_decoder_state(); + // if any states are not okay, abort and override m_decode_state + + // and return, indicating that playing will continue (unless an error has occurred) + return done( m_decode_status ); + } + + return done(dsError); +} + +::FLAC__StreamDecoderWriteStatus +mgFlacDecoder::write_callback(const ::FLAC__Frame *frame, + const FLAC__int32 * const buffer[]) +{ + mgLog lg( "mgFlacDecoder::write_callback" ); + ::FLAC__StreamDecoderWriteStatus retval; + + // add decoded buffer to reservoir + m_len_decoded = frame->header.blocksize; + m_current_samples += m_len_decoded; + m_current_time_ms += (m_len_decoded*1000) / m_pcm->samplerate; // in milliseconds + + // cout << "Samples decoded: " << m_len_decoded << ", current time: " << m_current_time_ms << ", bits per sample: "<< m_nBitsPerSample << endl << flush; + + // append buffer to m_reservoir + if( m_len_decoded > 0 ) + { + memmove( m_reservoir[0] + m_reservoir_count, buffer[0], m_len_decoded*sizeof(FLAC__int32) ); + + if( m_pcm->channels > 1 ) + { + memmove( m_reservoir[1] + m_reservoir_count, buffer[1], m_len_decoded*sizeof(FLAC__int32) ); + } + + m_reservoir_count += m_len_decoded; + } + else + { + m_decode_status = dsEof; + } + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +void mgFlacDecoder::metadata_callback( const ::FLAC__StreamMetadata *metadata ) +{ + // not needed since metadata is ignored!? + mgLog lg( "mgFlacDecoder::metadata_callback" ); + + if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO) + { + m_nTotalSamples = (unsigned)(metadata->data.stream_info.total_samples & 0xfffffffful); + m_nBitsPerSample = metadata->data.stream_info.bits_per_sample; + m_nCurrentChannels = metadata->data.stream_info.channels; + m_nCurrentSampleRate = metadata->data.stream_info.sample_rate; + m_nFrameSize = metadata->data.stream_info.max_framesize; + m_nBlockSize = metadata->data.stream_info.max_blocksize; + + m_nCurrentSampleRate = (int)get_sample_rate(); + m_nCurrentChannels = (int)get_channels(); + m_nCurrentBitsPerSample = (int)get_bits_per_sample(); + + // m_nLengthMS = m_nTotalSamples * 10 / (m_nCurrentSampleRate / 100); + m_nBlockAlign = (m_nBitsPerSample / 8) * m_nCurrentChannels; + + // m_nAverageBitRate = ((m_pReader->GetLength() * 8) / (m_nLengthMS / 1000) / 1000); + // m_nCurrentBitrate = m_nAverageBitRate; + } +} + +void mgFlacDecoder::error_callback( ::FLAC__StreamDecoderErrorStatus status ) +{ + mgLog lg( "mgFlacDecoder::error_callback" ); + + // check status and return + switch( status ) + { + case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC: + { + m_error = "An error in the stream caused the decoder to lose synchronization"; + } break; + case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER: + { + m_error = "The decoder encountered a corrupted frame header."; + } break; + case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH: + { + m_error = "The frame's data did not match the CRC in the footer."; + }; + default: + { + m_error = "Unknown error occurred."; + } + } + + cout << "Error occured: " << m_error << endl; + m_decode_status = dsError; +} + +bool mgFlacDecoder::skip(int seconds, int avail, int rate) +{ + lock(); + bool res = false; + + if( m_playing ) + { + const long target_time_ms = ((seconds-avail)*rate / 1000) + m_current_time_ms; + const double distance = target_time_ms / (double)m_nLengthMS; + const unsigned target_sample = (unsigned)(distance * (double)m_nTotalSamples); + + if( seek_absolute( (FLAC__uint64)target_sample) ) + { + m_current_time_ms = target_time_ms; + res = true; + } + } + unlock(); + return res; +} + +#endif //HAVE_FLAC diff --git a/vdr_decoder_flac.h b/vdr_decoder_flac.h new file mode 100644 index 0000000..581d1af --- /dev/null +++ b/vdr_decoder_flac.h @@ -0,0 +1,80 @@ +/*! \file vdr_decoder_flac.h + * \ingroup vdr + * + * The file contains a decoder which is used by the player to decode flac audio files. + * + * Based on code from + * MP3/MPlayer plugin to VDR (C++) + * (C) 2001-2003 Stefan Huelswitt <huels@iname.com> + */ + +#ifndef ___DECODER_FLAC_H +#define ___DECODER_FLAC_H + +#define DEC_FLAC DEC_ID('F','L','A','C') +#define DEC_FLAC_STR "FLAC" + +#ifdef HAVE_FLAC + +#include "vdr_decoder.h" +#include <FLAC++/decoder.h> + +// ---------------------------------------------------------------- + +class mgFlacDecoder : public mgDecoder, + public FLAC::Decoder::File +{ + private: + + struct mgDecode m_ds; + struct mad_pcm *m_pcm; + unsigned long long m_index, m_current_time_ms, m_current_samples; + unsigned int m_reservoir_count, m_len_decoded, m_samplerate; + + long m_nCurrentSampleRate, m_nCurrentChannels, m_nCurrentBitsPerSample; + long m_nLengthMS, m_nBlockAlign, m_nAverageBitRate, m_nCurrentBitrate; + long m_nTotalSamples, m_nBitsPerSample, m_nFrameSize, m_nBlockSize; + + bool m_state, m_first, m_continue; + std::string m_error; + + FLAC__int32 **m_reservoir; + + bool initialize(); + bool clean(); + + struct mgDecode* done(eDecodeStatus status); + eDecodeStatus m_decode_status; + + protected: + + /*! \brief FLAC decoder callback routines */ + //@{ + virtual ::FLAC__StreamDecoderWriteStatus write_callback(const ::FLAC__Frame *frame, + const FLAC__int32 * const buffer[]); + virtual void metadata_callback(const ::FLAC__StreamMetadata *metadata); + virtual void error_callback(::FLAC__StreamDecoderErrorStatus status); + //@} + + public: + + mgFlacDecoder( mgContentItem *item ); + ~mgFlacDecoder(); + + virtual mgPlayInfo *playInfo(); + + virtual bool valid(); + + virtual bool start(); + + virtual struct mgDecode *decode(void); + + virtual bool skip(int Seconds, int Avail, int Rate); + + virtual bool stop(); +}; + +// ---------------------------------------------------------------- + +#endif //HAVE_FLAC +#endif //___DECODER_FLAC_H @@ -42,15 +42,10 @@ mgStatus::OsdCurrentItem(const char* Text) { cOsdItem* i = main->Get(main->Current()); if (!i) return; - if (i == IgnoreNextEventOn) - { - IgnoreNextEventOn = NULL; - return; - } mgAction * a = dynamic_cast<mgAction *>(i); if (!a) mgError("mgStatus::OsdCurrentItem expected an mgAction*"); - a->Notify(); + a->TryNotify(); } void Play(mgSelection *sel,const bool select) { @@ -86,6 +81,26 @@ mgMainMenu::PlayInstant(const bool select) Play(selection(),select); } +void +mgMainMenu::CollectionChanged(string name) +{ + delete moveselection; + moveselection = NULL; + forcerefresh = true; // TODO brauchen wir das? + if (name == play_collection) + { + playselection()->clearCache(); + mgPlayerControl *c = PlayerControl(); + if (c) + c->ReloadPlaylist(); + else + PlayQueue(); + } + if (CollectionEntered(name)) + { + selection()->clearCache(); + } +} bool mgMainMenu::ShowingCollections() @@ -101,14 +116,13 @@ mgMainMenu::DefaultCollectionSelected() return (ShowingCollections () && this_sel == default_collection); } - bool -mgMainMenu::DefaultCollectionEntered() +mgMainMenu::CollectionEntered(string name) { if (!UsingCollection) return false; if (selection()->level()==0) return false; string collection = trim(selection ()->getKeyValue(0)); - return (collection == default_collection); + return (collection == name); } @@ -121,14 +135,14 @@ mgMainMenu::Parent () } -mgOsdItem* -mgMenu::GenerateAction(const mgActions action) +mgAction* +mgMenu::GenerateAction(const mgActions action,mgActions on) { - mgOsdItem *result = actGenerate(action); + mgAction *result = actGenerate(action); if (result) { result->SetMenu(this); - if (!result->Enabled()) + if (!result->Enabled(on)) { delete result; result=NULL; @@ -138,9 +152,9 @@ mgMenu::GenerateAction(const mgActions action) } void -mgMenu::ExecuteAction(const mgActions action) +mgMenu::ExecuteAction(const mgActions action,mgActions on) { - mgAction *a = GenerateAction (action); + mgAction *a = GenerateAction (action,on); if (a) { a->Execute (); @@ -206,6 +220,8 @@ mgMainMenu::mgMainMenu ():cOsdMenu ("") { m_Status = new mgStatus(this); m_message = NULL; + moveselection = NULL; + external_commands = NULL; queue_playing=false; instant_playing=false; play_collection = tr("play"); @@ -214,8 +230,11 @@ mgMainMenu::mgMainMenu ():cOsdMenu ("") mgValmap nmain("MainMenu"); // define defaults for values missing in state file: - ncol.put("Keys.0.Choice","Collection"); - ncol.put("Keys.1.Choice","Collection item"); + nsel.put("Keys.0.Choice",keyArtist); + nsel.put("Keys.1.Choice",keyAlbum); + nsel.put("Keys.2.Choice",keyTitle); + ncol.put("Keys.0.Choice",keyCollection); + ncol.put("Keys.1.Choice",keyCollectionItem); nmain.put("DefaultCollection",play_collection); nmain.put("UsingCollection","false"); nmain.put("TreeRedAction",int(actAddThisToCollection)); @@ -249,11 +268,13 @@ mgMainMenu::mgMainMenu ():cOsdMenu ("") // initialize m_collectionsel.CreateCollection(default_collection); m_collectionsel.CreateCollection(play_collection); - m_playsel.setKey(0,"Collection"); - m_playsel.setKey(1,"Collection item"); + m_playsel.setKey(0,keyCollection); + m_playsel.setKey(1,keyCollectionItem); + m_playsel.leave(0); m_playsel.enter(play_collection); UseNormalSelection (); unsigned int posi = selection()->gotoPosition(); + LoadExternalCommands(); // before AddMenu() mgMenu *root = new mgTree; root->TreeRedAction = mgActions(nmain.getuint("TreeRedAction")); root->TreeGreenAction = mgActions(nmain.getuint("TreeGreenAction")); @@ -261,10 +282,16 @@ mgMainMenu::mgMainMenu ():cOsdMenu ("") root->CollRedAction = mgActions(nmain.getuint("CollRedAction")); root->CollGreenAction = mgActions(nmain.getuint("CollGreenAction")); root->CollYellowAction = mgActions(nmain.getuint("CollYellowAction")); - AddMenu (root); + AddMenu (root,posi); - SetCurrent (Get (posi)); + //SetCurrent (Get (posi)); + forcerefresh = false; +} + +void +mgMainMenu::LoadExternalCommands() +{ // Read commands for collections in etc. /video/muggle/playlist_commands.conf external_commands = new cCommands (); @@ -286,12 +313,13 @@ mgMainMenu::mgMainMenu ():cOsdMenu ("") delete external_commands; external_commands = NULL; } - forcerefresh = false; } mgMainMenu::~mgMainMenu() { delete m_Status; + if (moveselection) + delete moveselection; } void @@ -306,9 +334,9 @@ mgMainMenu::InitMapFromSetup (mgValmap& nv) } void -mgMenu::AddAction (const mgActions action, const bool hotkey) +mgMenu::AddAction (const mgActions action, mgActions on,const bool hotkey) { - mgOsdItem *a = GenerateAction(action); + mgAction *a = GenerateAction(action,on); if (!a) return; const char *mn = a->MenuName(); if (strlen(mn)==0) @@ -318,81 +346,88 @@ mgMenu::AddAction (const mgActions action, const bool hotkey) else a->SetText(mn); free(const_cast<char*>(mn)); - osd()->Add(a); + osd()->AddItem(a); } void mgMenu::AddExternalAction(const mgActions action, const char *title) { - mgOsdItem *a = GenerateAction(action); + mgAction *a = GenerateAction(action,mgActions(0)); if (!a) return; a->SetText(osd()->hk(title)); - osd()->Add(a); + osd()->AddItem(a); } void -mgMenu::AddSelectionItems () +mgMenu::AddSelectionItems (mgSelection *sel,mgActions act) { - for (unsigned int i = 0; i < selection()->values.size (); i++) + for (unsigned int i = 0; i < sel->values.size (); i++) { - mgOsdItem *a = GenerateAction(actEntry); + mgAction *a = GenerateAction(act, actEntry); if (!a) continue; - a->SetText(a->MenuName(i+1,selection()->values[i]),false); - osd()->Add(a); + a->SetText(a->MenuName(i+1,sel->values[i]),false); + osd()->AddItem(a); } if (osd()->ShowingCollections ()) { - mgCreateCollection *a = new mgCreateCollection; - if (!a) return; - a->SetMenu(this); - if (!a->Enabled()) + mgAction *a = GenerateAction(actCreateCollection,mgActions(0)); + if (a) { - delete a; - a=NULL; + a->SetText(a->MenuName(),false); + osd()->AddItem(a); } - if (!a) return; - a->SetText(a->MenuName(),false); - osd()->Add(a); } } const char * -mgMenu::BlueName () +mgMenu::BlueName (mgActions on) { - if (typeid (*this) == typeid (mgTree)) - return tr("Commands"); + // do not use typeid because we want to catch descendants too + if (dynamic_cast<mgTreeCollSelector*>(this)) + return tr ("List"); + else if (osd()->Current()<0) + return tr("Commands"); + else if (dynamic_cast<mgTree*>(this)) + return tr("Commands"); else return tr ("List"); } +const char* +mgMenu::HKey(const mgActions act,mgActions on) +{ + const char* result = NULL; + mgAction *a = GenerateAction(act,on); + if (a) + { + result = a->ButtonName(); + delete a; + } + return result; +} + void -mgMenu::SetHelpKeys() +mgMenu::SetHelpKeys(mgActions on) { - const char *Red = NULL; - const char *Green = NULL; - const char *Yellow = NULL; - mgOsdItem *a; + mgActions r,g,y; if (osd()->UsingCollection) { - if ((a = GenerateAction (CollRedAction))) - Red = a->ButtonName (); - if ((a = GenerateAction (CollGreenAction))) - Green = a->ButtonName (); - if ((a = GenerateAction (CollYellowAction))) - Yellow = a->ButtonName (); + r = CollRedAction; + g = CollGreenAction; + y = CollYellowAction; } - else + else { - if ((a = GenerateAction (TreeRedAction))) - Red = a->ButtonName (); - if ((a = GenerateAction (TreeGreenAction))) - Green = a->ButtonName (); - if ((a = GenerateAction (TreeYellowAction))) - Yellow = a->ButtonName (); + r = TreeRedAction; + g = TreeGreenAction; + y = TreeYellowAction; } - osd()->SetHelpKeys(Red,Green,Yellow,BlueName()); + osd()->SetHelpKeys(HKey(r,on), + HKey(g,on), + HKey(y,on), + BlueName(on)); } @@ -400,6 +435,7 @@ void mgMenu::InitOsd (const char *title,const bool hashotkeys) { osd ()->InitOsd (title,hashotkeys); + SetHelpKeys(); // Default, will be overridden by the single items } @@ -411,24 +447,35 @@ mgMainMenu::InitOsd (const char *title,const bool hashotkeys) if (hashotkeys) SetHasHotkeys (); } +void +mgMainMenu::AddItem(mgAction *a) +{ + cOsdItem *c = dynamic_cast<cOsdItem*>(a); + if (!c) + mgError("AddItem with non cOsdItem"); + Add(c); +} void mgSubmenu::BuildOsd () { static char b[100]; snprintf(b,99,tr("Commands:%s"),trim(osd()->selection()->getCurrentValue()).c_str()); + mgActions on = osd()->CurrentType(); InitOsd (b); mgMenu *p = osd ()->Parent (); if (!p) return; - AddAction(actInstantPlay); - AddAction(actAddThisToCollection); - AddAction(actRemoveThisFromCollection); - AddAction(actToggleSelection); - AddAction(actSetDefault); - AddAction(actDeleteCollection); - AddAction(actChooseSearch); - AddAction(actExportTracklist); + AddAction(actInstantPlay,on); + AddAction(actAddThisToCollection,on); + AddAction(actAddThisToDefaultCollection,on); + AddAction(actSetDefaultCollection,on); + AddAction(actRemoveThisFromCollection,on); + AddAction(actToggleSelection,on); + AddAction(actDeleteCollection,on); + AddAction(actClearCollection,on); + AddAction(actChooseOrder,on); + AddAction(actExportTracklist,on); cCommand *command; if (osd()->external_commands) { @@ -452,43 +499,65 @@ mgSubmenu::BuildOsd () CollYellowAction = actSetButton; } +mgActions +mgMainMenu::CurrentType() +{ + mgActions result = mgActions(0); + cOsdItem* c = Get(Current()); + if (c) + { + mgAction *a = dynamic_cast<mgAction*>(c); + if (!a) + mgError("Found an OSD item which is not mgAction:%s",c->Text()); + result = a->Type(); + } + return result; +} eOSState -mgTree::Process (eKeys key) +mgMenu::ExecuteButton(eKeys key) { - eOSState result = osUnknown; + mgActions on = osd()->CurrentType(); switch (key) { case kRed: if (osd()->UsingCollection) - ExecuteAction (CollRedAction); + ExecuteAction (CollRedAction,on); else - ExecuteAction (TreeRedAction); + ExecuteAction (TreeRedAction,on); return osContinue; case kGreen: if (osd()->UsingCollection) - ExecuteAction (CollGreenAction); + ExecuteAction (CollGreenAction,on); else - ExecuteAction (TreeGreenAction); + ExecuteAction (TreeGreenAction,on); return osContinue; case kYellow: if (osd()->UsingCollection) - ExecuteAction (CollYellowAction); + ExecuteAction (CollYellowAction,on); else - ExecuteAction (TreeYellowAction); + ExecuteAction (TreeYellowAction,on); + return osContinue; + case kBlue: + osd()->newmenu = new mgSubmenu; return osContinue; default: - result = osUnknown; break; } - return result; + return osUnknown; +} + +eOSState +mgTree::Process (eKeys key) +{ + return ExecuteButton(key); } void mgTree::BuildOsd () { InitOsd (selection ()->getListname ().c_str (), false); - AddSelectionItems (); + AddSelectionItems (selection()); } void @@ -502,8 +571,6 @@ mgMainMenu::Message1(const char *msg, const char *arg1) eOSState mgMainMenu::ProcessKey (eKeys key) { eOSState result = osContinue; - if (key!=kNone) - mgDebug (3, "MainMenu::ProcessKey(%d)", (int) key); if (Menus.size()<1) mgError("mgMainMenu::ProcessKey: Menus is empty"); @@ -562,21 +629,21 @@ otherkeys: newposition = -1; { - mgMenu * oldmenu = newmenu; + mgMenu * oldmenu = newmenu; // item specific key logic: - result = cOsdMenu::ProcessKey (key); + result = cOsdMenu::ProcessKey (key); // mgMenu specific key logic: - if (result == osUnknown) - result = oldmenu->Process (key); + if (result == osUnknown) + result = oldmenu->Process (key); } -// catch osBack for empty OSD lists -// (because if the list was empty, no mgOsdItem::ProcessKey was ever called) +// catch osBack for empty OSD lists . This should only happen for playlistitems +// (because if the list was empty, no mgActions::ProcessKey was ever called) if (result == osBack) { // do as if there was an entry - mgOsdItem *a = Menus.back()->GenerateAction(actEntry); + mgAction *a = Menus.back()->GenerateAction(actEntry,actEntry); if (a) { result = a->Back(); @@ -593,7 +660,7 @@ otherkeys: { if (Menus.size () > 1) { - Menus.pop_back (); + CloseMenu(); forcerefresh = true; } else @@ -603,9 +670,7 @@ otherkeys: } } else if (newmenu != Menus.back ()) - { - AddMenu (newmenu); - } + AddMenu (newmenu,newposition); if (UsingCollection) forcerefresh |= m_collectionsel.cacheIsEmpty(); @@ -616,7 +681,6 @@ otherkeys: if (forcerefresh) { - mgDebug(2,"forced refresh"); forcerefresh = false; if (newposition<0) newposition = selection()->gotoPosition(); @@ -628,12 +692,20 @@ pr_exit: } void +mgMainMenu::CloseMenu() +{ + mgMenu* m = Menus.back(); + Menus.pop_back (); + delete m; +} + +void mgMainMenu::showMessage() { if (m_message) { #if VDRVERSNUM >= 10307 - Skins.Message (mtInfo, m_message); + Skins.Message (mtInfo, m_message,2); Skins.Flush (); #else Interface->Status (m_message); @@ -646,13 +718,19 @@ mgMainMenu::showMessage() void -mgMainMenu::AddMenu (mgMenu * m) +mgMainMenu::AddMenu (mgMenu * m,unsigned int position) { Menus.push_back (m); m->setosd (this); - m->Display (0); + m->Display (position); } +void +mgMenu::setosd(mgMainMenu *osd) +{ + m_osd = osd; + m_prevUsingCollection = osd->UsingCollection; +} eOSState mgSubmenu::Process (eKeys key) @@ -662,15 +740,46 @@ mgSubmenu::Process (eKeys key) void -mgTreeViewSelector::BuildOsd () -{ - InitOsd (tr ("Tree View Selection")); - AddAction(actSearchCollItem); - AddAction(actSearchArtistAlbumTitle); - AddAction(actSearchArtistTitle); - AddAction(actSearchAlbumTitle); - AddAction(actSearchGenreYearTitle); - AddAction(actSearchGenreArtistAlbumTitle); +mgMenuOrders::BuildOsd () +{ + InitOsd (tr ("Select an order")); + AddAction(ActOrderCollItem); + AddAction(ActOrderArtistAlbumTitle); + AddAction(ActOrderArtistTitle); + AddAction(ActOrderAlbumTitle); + AddAction(ActOrderGenreYearTitle); + AddAction(ActOrderGenreArtistAlbumTitle); +} + + +mgTreeCollSelector::~mgTreeCollSelector() +{ + osd()->UsingCollection = m_prevUsingCollection; +} + +void +mgTreeCollSelector::BuildOsd () +{ + osd()->UsingCollection = true; + mgSelection *coll = osd()->collselection(); + InitOsd (m_title.c_str()); + coll->leave(0); + coll->setPosition(osd()->default_collection); + AddSelectionItems (coll,coll_action()); + osd()->newposition = coll->gotoPosition(0); + cOsdItem *c = osd()->Get(osd()->newposition); + mgAction *a = dynamic_cast<mgAction *>(c); + a->IgnoreNextEvent = true; +} + +mgTreeAddToCollSelector::mgTreeAddToCollSelector(string title) +{ + m_title = title; +} + +mgTreeRemoveFromCollSelector::mgTreeRemoveFromCollSelector(string title) +{ + m_title = title; } void @@ -48,14 +48,7 @@ class mgStatus : public cStatus mgMainMenu *main; public: //! \brief default constructor - mgStatus(mgMainMenu* m) { main = m;IgnoreNextEventOn=NULL;} - /*! \brief vdr calls OsdCurrentItem more often than we - * want. This tells mgStatus to ignore the next call - * for a specific item. - * \todo is this behaviour intended or a bug in vdr - * or in muggle ? - */ - cOsdItem* IgnoreNextEventOn; + mgStatus(mgMainMenu* m) { main = m;} protected: //! \brief the event we want to know about virtual void OsdCurrentItem(const char *Text); @@ -74,7 +67,11 @@ class mgMainMenu:public cOsdMenu mgSelection m_collectionsel; char *m_message; void showMessage(); + void LoadExternalCommands(); public: + mgSelection *moveselection; + mgActions CurrentType(); + //! \brief syntactic sugar: expose the protected cOsdMenu::SetHelp void SetHelpKeys(const char *Red,const char *Green, const char *Yellow, const char *Blue) { SetHelp(Red,Green,Yellow,Blue); } @@ -112,7 +109,7 @@ class mgMainMenu:public cOsdMenu void SaveState(); //! \brief adds a new mgMenu to the stack - void AddMenu (mgMenu * m); + void AddMenu (mgMenu * m,unsigned int position=0); //! \brief initializes using values from nv void InitMapFromSetup (mgValmap& nv); @@ -171,7 +168,7 @@ class mgMainMenu:public cOsdMenu //! \brief Actions can request a new position. -1 means none wanted int newposition; - //! \brief clears the screen, sets a title and buttons with text on them + //! \brief clears the screen, sets a title and the hotkey flag void InitOsd (const char *title,const bool hashotkeys); #if VDRVERSNUM >= 10307 @@ -216,8 +213,13 @@ class mgMainMenu:public cOsdMenu bool DefaultCollectionSelected(); //! \brief true if the cursor is placed in the default collection - bool DefaultCollectionEntered(); + bool CollectionEntered(string name); + + void AddItem(mgAction *a); + + void CollectionChanged(string name); + void CloseMenu(); }; //! \brief a generic muggle menu @@ -225,10 +227,13 @@ class mgMenu { private: mgMainMenu* m_osd; + const char *HKey(const mgActions act,mgActions on); protected: + bool m_prevUsingCollection; + eOSState ExecuteButton(eKeys key); //! \brief adds the wanted action to the OSD menu // \param hotkey if true, add this as a hotkey - void AddAction(const mgActions action, const bool hotkey=true); + void AddAction(const mgActions action, mgActions on = mgActions(0),const bool hotkey=true); //! \brief add an external action, always with hotkey void AddExternalAction(const mgActions action, const char *title); @@ -236,26 +241,23 @@ class mgMenu //! \brief adds entries for all selected data base items to the OSD menu. // If this is the list of collections, appends a command for collection // creation. - void AddSelectionItems (); + void AddSelectionItems (mgSelection *sel,mgActions act = actEntry); //! \brief the name of the blue button depends of where we are - const char *mgMenu::BlueName (); + const char *mgMenu::BlueName (mgActions on); public: /*! sets the correct help keys. * \todo without data from mysql, no key is shown, * not even yellow or blue */ - void SetHelpKeys(); + void SetHelpKeys(mgActions on = mgActions(0)); //! \brief generates an object for the wanted action - mgOsdItem* GenerateAction(const mgActions action); + mgAction* GenerateAction(const mgActions action,mgActions on); //! \brief executes the wanted action - void ExecuteAction (const mgActions action); + void ExecuteAction (const mgActions action, const mgActions on); //! \brief sets the pointer to the owning mgMainMenu - void setosd (mgMainMenu* osd) - { - m_osd = osd; - } + void setosd (mgMainMenu* osd); //! \brief the pointer to the owning mgMainMenu mgMainMenu* osd () @@ -280,7 +282,7 @@ class mgMenu { } -//! \brief clears the screen, sets a title and buttons with text on them +//! \brief clears the screen, sets a title and the hotkey flag void InitOsd (const char *title,const bool hashotkeys=true); //! \brief display OSD and go to position @@ -333,11 +335,39 @@ class mgSubmenu:public mgMenu void BuildOsd (); }; -//! \brief an mgMenu class for selecting a search view -class mgTreeViewSelector:public mgMenu +//! \brief an mgMenu class for selecting an order +class mgMenuOrders:public mgMenu { protected: void BuildOsd (); }; +//! \brief an mgMenu class for selecting a collection +class mgTreeCollSelector:public mgMenu +{ + public: + ~mgTreeCollSelector(); + protected: + void BuildOsd (); + virtual mgActions coll_action() = 0; + string m_title; +}; + +class mgTreeAddToCollSelector:public mgTreeCollSelector +{ + public: + mgTreeAddToCollSelector(string title); + protected: + virtual mgActions coll_action() { return actAddCollEntry; } +}; + +//! \brief an mgMenu class for selecting a collection +class mgTreeRemoveFromCollSelector:public mgTreeCollSelector +{ + public: + mgTreeRemoveFromCollSelector(string title); + protected: + virtual mgActions coll_action() { return actRemoveCollEntry; } +}; + #endif diff --git a/vdr_player.c b/vdr_player.c index a62f9b5..56828ca 100644 --- a/vdr_player.c +++ b/vdr_player.c @@ -168,13 +168,15 @@ class mgPCMPlayer:public cPlayer, cThread int m_index; void Empty (); - bool SkipFile (int step=1); + bool SkipFile (int step=0); void PlayTrack(); void StopPlay (); void SetPlayMode (ePlayMode mode); void WaitPlayMode (ePlayMode mode, bool inv); + int skip_direction; + protected: virtual void Activate (bool On); virtual void Action (void); @@ -234,6 +236,7 @@ pmAudioOnlyBlack) m_index = 0; m_playing = 0; m_current = 0; + skip_direction = 1; } @@ -320,10 +323,10 @@ mgPCMPlayer::SetPlayMode (ePlayMode mode) { m_playmode_mutex.Lock (); if (mode != m_playmode) - { + { m_playmode = mode; m_playmode_cond.Broadcast (); - } + } m_playmode_mutex.Unlock (); } @@ -333,10 +336,10 @@ mgPCMPlayer::WaitPlayMode (ePlayMode mode, bool inv) { // must be called with m_playmode_mutex LOCKED !!! - while (m_active - && ((!inv && mode != m_playmode) || (inv && mode == m_playmode))) + while( m_active + && ((!inv && mode != m_playmode) || (inv && mode == m_playmode)) ) { - m_playmode_cond.Wait (m_playmode_mutex); + m_playmode_cond.Wait (m_playmode_mutex); } } @@ -411,7 +414,6 @@ mgPCMPlayer::Action (void) { std::string filename = the_setup.getFilename( m_playing->getSourceFile () ); mgDebug( 1, "mgPCMPlayer::Action: music file is %s", filename.c_str() ); - if ((m_decoder = mgDecoders::findDecoder (m_playing)) && m_decoder->start ()) { @@ -422,6 +424,7 @@ mgPCMPlayer::Action (void) level.Init (); m_state = msDecode; + mgDebug(1,"found a decoder for %s",filename.c_str()); break; } @@ -761,8 +764,11 @@ mgPCMPlayer::StopPlay () bool mgPCMPlayer::SkipFile (int step) { MGLOG("mgPCMPlayer::SkipFile"); + mgDebug(1,"SkipFile:step=%d, skip_direction=%d",step,skip_direction); mgContentItem * newcurr = NULL; - if (m_playlist->skipTracks (step)) + if (step!=0) + skip_direction=step; + if (m_playlist->skipTracks (skip_direction)) { newcurr = m_playlist->getCurrentTrack (); if (newcurr) { @@ -831,7 +837,7 @@ mgPCMPlayer::Forward () MGLOG ("mgPCMPlayer::Forward"); Lock (); - if (SkipFile ()) + if (SkipFile (1)) { StopPlay (); Play (); @@ -857,7 +863,7 @@ mgPCMPlayer::Backward (void) void mgPCMPlayer::Goto (int index, bool still) { - m_playlist->setTrack (index - 1); + m_playlist->setTrackPosition (index - 1); mgContentItem *next = m_playlist->getCurrentTrack (); if (next) @@ -1409,7 +1415,6 @@ mgPlayerControl::ShowProgress () mgSelection *list = player->getPlaylist (); if (list) { - list->clearCache(); // playlist can dynamically grow, force reload total_frames = SecondsToFrames (list->getLength ()); current_frame += SecondsToFrames (list->getCompletedLength ()); asprintf (&buf, "%s (%d/%d)", list->getListname ().c_str (), @@ -1530,7 +1535,8 @@ mgPlayerControl::InternalHide () eOSState mgPlayerControl::ProcessKey (eKeys key) { - if (key!=kNone) MGLOG ("mgPlayerControl::ProcessKey(eKeys key)"); + if (key!=kNone) + mgDebug (1,"mgPlayerControl::ProcessKey(%u)",key); if (!Active ()) { return osEnd; |