summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLarsAC <LarsAC@e10066b5-e1e2-0310-b819-94efdf66514b>2005-01-23 13:03:34 +0000
committerLarsAC <LarsAC@e10066b5-e1e2-0310-b819-94efdf66514b>2005-01-23 13:03:34 +0000
commit05801055e91bef231bb6aa48a96034e69bd7f250 (patch)
tree98e1272faedb999e501eb7b158041992e247f369
parent0c5c1deeb5ca6e2c572ceb9192437ee3d9178238 (diff)
downloadvdr-plugin-muggle-05801055e91bef231bb6aa48a96034e69bd7f250.tar.gz
vdr-plugin-muggle-05801055e91bef231bb6aa48a96034e69bd7f250.tar.bz2
Merged branches osd_extensions and flac_player for new release, yet untested
git-svn-id: https://vdr-muggle.svn.sourceforge.net/svnroot/vdr-muggle/trunk/muggle-plugin@386 e10066b5-e1e2-0310-b819-94efdf66514b
-rw-r--r--Makefile10
-rw-r--r--README4
-rw-r--r--i18n.c187
-rw-r--r--mg_actions.c690
-rw-r--r--mg_actions.h81
-rw-r--r--mg_database.h1
-rw-r--r--mg_db.c1137
-rw-r--r--mg_db.h642
-rw-r--r--mg_filters.c297
-rw-r--r--mg_filters.h171
-rw-r--r--mg_order.c775
-rw-r--r--mg_order.h154
-rw-r--r--mg_valmap.c70
-rw-r--r--mg_valmap.h50
-rw-r--r--muggle.c1
-rw-r--r--vdr_decoder.c49
-rw-r--r--vdr_decoder.h150
-rw-r--r--vdr_decoder_flac.c380
-rw-r--r--vdr_decoder_flac.h80
-rw-r--r--vdr_menu.c325
-rw-r--r--vdr_menu.h78
-rw-r--r--vdr_player.c30
22 files changed, 2997 insertions, 2365 deletions
diff --git a/Makefile b/Makefile
index 931c6ef..9213ab8 100644
--- a/Makefile
+++ b/Makefile
@@ -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:
diff --git a/README b/README
index 06e0064..5a00e64 100644
--- a/README
+++ b/README
@@ -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
diff --git a/i18n.c b/i18n.c
index db5e673..13b7fea 100644
--- a/i18n.c
+++ b/i18n.c
@@ -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
{
diff --git a/mg_db.c b/mg_db.c
index a310d51..9fa656d 100644
--- a/mg_db.c
+++ b/mg_db.c
@@ -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;
+}
diff --git a/mg_db.h b/mg_db.h
index 40e3308..2ba02df 100644
--- a/mg_db.h
+++ b/mg_db.h
@@ -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
diff --git a/muggle.c b/muggle.c
index 62bfe75..c4e1831 100644
--- a/muggle.c
+++ b/muggle.c
@@ -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
diff --git a/vdr_menu.c b/vdr_menu.c
index 6871eb7..e3db994 100644
--- a/vdr_menu.c
+++ b/vdr_menu.c
@@ -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
diff --git a/vdr_menu.h b/vdr_menu.h
index 834a36c..faadb87 100644
--- a/vdr_menu.h
+++ b/vdr_menu.h
@@ -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;