diff options
-rw-r--r-- | HISTORY | 156 | ||||
-rw-r--r-- | Makefile | 33 | ||||
-rw-r--r-- | i18n.c | 40 | ||||
-rw-r--r-- | mg_content.c | 245 | ||||
-rw-r--r-- | mg_content.h | 111 | ||||
-rw-r--r-- | mg_database.c | 130 | ||||
-rw-r--r-- | mg_database.h | 83 | ||||
-rw-r--r-- | mg_mysql.c | 583 | ||||
-rw-r--r-- | mg_mysql.h | 77 | ||||
-rw-r--r-- | mg_order.c | 291 | ||||
-rw-r--r-- | mg_order.h | 37 | ||||
-rw-r--r-- | mg_selection.c (renamed from mg_db.c) | 483 | ||||
-rw-r--r-- | mg_selection.h (renamed from mg_db.h) | 124 | ||||
-rw-r--r-- | mg_setup.c | 2 | ||||
-rw-r--r-- | mg_sync.c | 335 | ||||
-rw-r--r-- | mg_sync.h | 71 | ||||
-rw-r--r-- | mg_tools.c | 2 | ||||
-rw-r--r-- | muggle.c | 43 | ||||
-rw-r--r-- | muggle.doxygen | 2 | ||||
-rw-r--r-- | muggle.h | 4 | ||||
-rwxr-xr-x | mugglei.c | 793 | ||||
-rw-r--r-- | scripts/COPYRIGHT | 29 | ||||
-rwxr-xr-x | scripts/createtables.mysql | 5 | ||||
-rwxr-xr-x | scripts/genres.txt | 116 | ||||
-rwxr-xr-x | scripts/gentables | 47 | ||||
-rwxr-xr-x | scripts/iso639tab.py | 81 | ||||
-rw-r--r-- | scripts/iso_639.xml | 2072 | ||||
-rw-r--r--[-rwxr-xr-x] | scripts/languages.txt | 27 | ||||
-rw-r--r-- | vdr_actions.c (renamed from mg_actions.c) | 79 | ||||
-rw-r--r-- | vdr_actions.h (renamed from mg_actions.h) | 20 | ||||
-rw-r--r-- | vdr_decoder.c | 4 | ||||
-rw-r--r-- | vdr_decoder_flac.c | 2 | ||||
-rw-r--r-- | vdr_decoder_mp3.c | 2 | ||||
-rw-r--r-- | vdr_decoder_ogg.c | 4 | ||||
-rw-r--r-- | vdr_menu.c | 103 | ||||
-rw-r--r-- | vdr_menu.h | 28 | ||||
-rw-r--r-- | vdr_player.c | 1 | ||||
-rw-r--r-- | vdr_player.h | 2 |
38 files changed, 4443 insertions, 1824 deletions
@@ -6,3 +6,159 @@ VDR Plugin 'muggle' Revision History 2004-09-05: Version 0.0.2-ALPHA - Added an Ogg Vorbis decoder + +XXXXXXXXXX: Version 0.0.5-ALPHA +- Support für g++ 2.95.4 +- Support für Sockets (statt TCP) +- Kleinere Fehlerchen beseitigt + +XXXXXXXXXX: Version 0.0.7-ALPHA +- Doppelter Import von Files bei erneutem Aufruf von mugglei + beseitigt, bei erneutem Aufruf wird stattdessen die DB + aus den Tags upgedated. +- Compilerwarnings beseitigt +- Anzeige auf dem gLCD +- Menüanzeige bei aktiviertem Progressdisplay funktioniert +- Verbessertes Progress Display beim Abspielen (Track/Playlist, Progress/Detail), nur für 1.3.12 +- Import von genre-Tags +- Instant play funktioniert +- Starten einer Playlist von irgendwo mittels Ok + +XXXXXXXXXX: Version 0.0.8-ALPHA +- Beim import werden bei bereits vorhandene Files nur die DB-Einträge erneuert, + keine Duplikate mehr. +- mugglei mit der Option -z löscht Datenbankeinträge, bei denen die verwiesene + Datei nicht existiert. +- Bug in mugglei entfernt, der verhinderte, dass neue Dateien korrekt + eingetragen werden. +- Ein Bug beim Skippen der Tracks am Ende der Playliste entfernt, der letzte + Track wurde außerdem für immer wiederholt. +- Französische Übersetzung. Merci a Patrice! + +2005-01-07: Version 0.1.0-BETA +- der Begriff Playlist ist weggefallen. Neu gibt es Sammlungen. + Wichtig ist vor allem die Sammlung "spielen". Was man an diese + Sammlung anhängt, wird eines nach dem anderen gespielt. +- Wenn man etwas an eine Sammlung anhängt, muss man vorher sagen, + welche das sein soll. Beim ersten Start von muggle ist das "spielen". + Man kann die "Zielsammlung" ändern, indem man in der Sammlungsliste + die richtige auswahlt, dann den blauen Knopf "Befehle" nimmt und + dann "Ziel auf Sammlung .... setzen" macht. +- man kann direkt in der Sammlungsliste auch neue anlegen. +- die gelbe Taste schaltet zwischen Sammlungen und Suche um. Die + Befehle sind an beiden Stellen etwa dieselben, man muss also + z.B. nicht extra eine Sammlung anlegen, um eine Playlist (*.mru) zu exportieren. +- Taste OK auf einem Track spielt ihn sofort. Wenn er zu Ende ist oder + man mit Stop abbricht, und wenn vorher etwasaus "spielen" lief, + wird dort weitergemacht. Ein zweiter Druck auf Taste Stop beendet + auch das Abspielen von "spielen". +- Der Befehl "sofort spielen" macht dasselbe wie OK auf einem Track, + aber für alle Tracks, die hinter dem gewähltenListeneintrag stecken, + z.B. alles von Abba. +- nachdem man Musik gestartet hat, bleibt das muggle - Menu stehen. + Damit und mit dem OK - Anspielen kann man sehr schnell alle Tracks + kurz anspielen. +- auch während das muggle - Menu sichtbar ist, funktionieren nun die + Tasten Stop, Play, Pause. +- wenn man muggle verlässt (am besten mit der Menu - Taste), wird der + Status gespeichert. Wenn man muggle wieder aufruft, ist man am gleichen Ort. +- beim Start von muggle kommt man direkt in das aktuelle Suchschema. + Dieses kann man neu ändern, indem man Taste "Befehle" nimmt, dann + Menu "Suchschema". +- Wenn man irgendwo mitten im Suchbaum ist und das Suchschema wechselt, + verwendet muggle die schon bekannten Schlüsselfelder, um wieder + möglichst weit in den Suchbaum hineinzugehen. +- Filter gibt es erstmal nicht mehr, da wird aber sicher wieder etwas kommen. +- Man kann die Tasten rot, grün, gelb frei belegen, indem man unter + "Befehle" auf einen Befehl geht und dann die gewünschte Farbtaste drückt. + Das funktioniert auch für extern definierte Befehle. +- Datenbankabfragen sind z.T. deutlich schneller + +2005-01-23: Version 0.1.1-BETA +- FLAC decoder added +- Compiles with VDR 1.3.18 (mileage may vary) +- Works with VDR 1.2.6 +- Selections can now be chosen when executing add/remove +- GD compatibility added +- Many bugfixes and usability improvements + +2005-02-07: Version 0.1.2-BETA +- Die Organisation der Dateien kann nun vom Benutzer verändert werden. + Zudem können neue Bäume erstellt werden (zB mag ich + Decade > Genre > Track sehr gern). +- m3u - Dateien werden nun immer in /tmp mit relativen Dateinamen erstellt. + Externe Befehle werden im top level directory der tracks aufgerufen + (vorangehendes chdir). +- m3u - Dateien enthalten zusätzlich eine Kennung #MUGGLE:XXX + wobei XXX die tracks.id des Stücks ist. Somit können Kommandos auch + Befehle auf der Datenbank durchführen (zB Löschen eines Tracks). + Muggle stellt die OSD-Ansicht danach neu dar, um Änderungen anzuzeigen. +- Blättern in Genre-Hierarchien ist neu. Das Feld Genre nutzt wie + bisher eine flache Genre-Liste. Die neuen Felder Genre1, Genre2, Genre3 + definieren Ebenen im Suchbaum aus der Genre-Hierarchie. +- Die Sprache wird aus id3v2-Tags importiert (für mp3 und flac) +- Musikstücke können nach Sprache gebrowsed werden. +- Hat ein Track 2 Genres (in den Feldern genre1 und genre2), so + erscheint es in Kategorien für beide Genres. Allerdings wird das zweite + Genre derzeit beim Import nicht berücksichtigt. +- Wichtige Meldungen erscheinen nun auch im OSD (nicht mehr nur im Syslog) +- Läuft mit allen Versionen inkl. 1.3.20 +- Player schaltet nach Ende der Playlist stumm (kein TV-Gedröhne mehr) +- Decoder für Ogg und FLAC können nach Defines in make.config gebaut werden +- Bugfixes und sonstige Verbesserungen + +2005-02-20: Version 0.1.3-BETA +- das deutsche VDR - Wiki enthält zu muggle einen Abschnitt "Bedienung". + Vielleicht findet sich ja jemand, der da etwas zu schreibt? + Ich stehe gerne bei Fragen zur Verfügung. +- Man kann nun nach Ordnern/Verzeichnissen sortieren. Bis zu 4 Stufen + sind möglich. Man muss alle Tracks mit mugglei neu importieren, damit + das geht. Wenn mugglei nicht die Berechtigung hat, neue Felder in der + Tabelle tracks anzulegen, bleibt alles wie bisher. In diesem Fall + müsste man entweder für die nötigen Rechte sorgen oder mit den Scripts + die ganze Datenbank neu anlegen. +- Hinter den Listeneinträgen steht nun, wieviele Tracks das jeweilen sind. + Dank geht an jarny für seine Hilfe zu SQL. +- Die Sprachcodes werden nun vom Standard ISO 639-2/B (bibliographic) + genommen, wie in den id3v2 Tags. Das betrifft nur den Import, die + Kompatibilität zu GiantDisc bleibt. +- Die Setup - Einstellungen loop mode und shuffle mode werden nun + berücksichtigt. +- mugglei erklärt jetzt besser, warum er etwas nicht importieren kann. +- wenn die Datei muggle.state nicht schreibbar ist, warnt muggle einmal. + (Die Datei muggle.state speichert den Status (z.B. Sortierungen, + Position, Farbknopfbelegung) +- Einige Fehler korrigiert, vor allem Memory leaks (die meisten mit + valgrind gefunden). Sollte nun auch (wieder) mit g++ 2.95 compilieren. +- Wer eine ältere Version von mysql benutzt, z.B. 3.23, wird mugglei nicht + kompilieren können. Die Fixes sollten einfach sein, evtl reicht es, + die Aufrufe mysql_server_init/end zu entfernen. Das README hat schon + immer mindestens 4.0.18 empfohlen. + +2005-03-XX: Version 0.1.4-BETA +- embedded mysql server as default. If you want to use an external server + as before, read the README file +- If the embedded version find the database missing, it offers to create + it. It will then also import all from the top level music directory + (as indicated with option -t) +- new option -v for the plugin and mugglei sets the output debug level. +- rewrote mugglei. Mainly it now recursively imports directories making + it much faster. This example will create a new database and import all + files from /Musik : mugglei -t /Musik -v 4 -c . +- removed the mugglei -f option. All arguments after the options will be + imported. +- removed the mugglei -a option. It should now automatically do the right + thing +- orders can now be displayed in the default order or descending by their + counts +- orders can now be a combination of order by collection and other key + fields. Known bug: If the collection is not the first key field like + in Genre:Collection:Album:Track, the counts are wrong in the top list +- make additions to playlists multi user safe +- rename Search to Browse/Navigieren +- when creating the data base, all ISO 639-2/B codes will be imported, + updated the list. +- the language names can appear in the local language if the translations + exist (debian: Package iso-codes) +- add all genres listed by id3 -L. Fix spellings. +- lots of bug fixes, as usual @@ -11,9 +11,13 @@ PLUGIN = muggle #if you want ogg / flac support, define HAVE_VORBISFILE and/or HAVE_FLAC #in $VDRDIR/Make.config like this: -# HAVE_VORBISFILE=1 -# HAVE_FLAC=1 +# HAVE_VORBISFILE=1 +# HAVE_FLAC=1 +#if you want to use a dedicated Mysql server instead of the embedded code, +#define this in $VDRDIR/Make.config: +# HAVE_SERVER +# ### The version number of this plugin (taken from the main source file): VERSION = $(shell grep 'static const char \*VERSION *=' $(PLUGIN).c | awk '{ print $$6 }' | sed -e 's/[";]//g') @@ -51,17 +55,21 @@ INCLUDES += -I$(VDRDIR) -I$(VDRDIR)/include -I$(DVBDIR)/include \ DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' -MIFLAGS += -I/usr/include/taglib -lmysqlclient - ### The object files (add further files here): -OBJS = $(PLUGIN).o i18n.o mg_valmap.o mg_order.o mg_db.o mg_actions.o vdr_menu.o mg_tools.o \ +OBJS = $(PLUGIN).o i18n.o mg_valmap.o mg_mysql.o mg_sync.o mg_order.o mg_content.o mg_selection.o vdr_actions.o vdr_menu.o mg_tools.o \ vdr_decoder_mp3.o vdr_stream.o vdr_decoder.o vdr_player.o \ vdr_setup.o mg_setup.o -LIBS = -lmad -lmysqlclient -MILIBS = -lmysqlclient -ltag -# MILIBS = -lmysqld -lpthread -lz -lcrypt -lnsl -lm -lpthread -lrt -lwrap -ltag +LIBS = -lmad -ltag +MILIBS = -ltag + +ifdef HAVE_SERVER +SQLLIBS = `mysql_config --libs` +DEFINES += -DHAVE_SERVER +else +SQLLIBS = `mysql_config --libmysqld-libs` -L/lib +endif ifdef HAVE_VORBISFILE DEFINES += -DHAVE_VORBISFILE @@ -92,12 +100,15 @@ $(DEPFILE): Makefile %.o: %.c %.h $(CXX) $(CXXFLAGS) $(DEFINES) $(INCLUDES) -c $< +mg_tables.h: scripts/genres.txt scripts/iso_639.xml scripts/musictypes.txt scripts/sources.txt + scripts/gentables + libvdr-$(PLUGIN).so: $(OBJS) - $(CXX) $(CXXFLAGS) -shared $(OBJS) $(LIBS) -o $@ + $(CXX) $(CXXFLAGS) -shared $(OBJS) $(LIBS) $(SQLLIBS) -o $@ @cp $@ $(LIBDIR)/$@.$(VDRVERSION) -mugglei: mg_tools.o mugglei.o - $(CXX) $(CXXFLAGS) $^ $(MILIBS) -o $@ +mugglei: mg_tools.o mugglei.o mg_sync.o mg_mysql.o mg_setup.o + $(CXX) $(CXXFLAGS) $^ $(MILIBS) $(SQLLIBS) -o $@ install: @cp ../../lib/libvdr-muggle*.so.* /usr/lib/vdr/ @@ -13,6 +13,23 @@ const tI18nPhrase Phrases[] = { { + "Sort by count", + "Nach Häufigkeit sortieren", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Sort by count", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { "Key %d", "Schlüsselfeld %d", "", // TODO @@ -47,13 +64,13 @@ const tI18nPhrase Phrases[] = "", // TODO }, { - "Search", - "Suchen", + "Browse", + "Navigieren", "", // TODO "", // TODO "", // TODO "", // TODO - "Rechercher", // TODO + "Naviguer", // TODO "", // TODO "", // TODO "", // TODO @@ -251,23 +268,6 @@ const tI18nPhrase Phrases[] = "", // TODO }, { - "Search Result", - "Suchergebnis", - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "Résultat de recherche", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - "", // TODO - }, - { "Collection", "Sammlung", "", // TODO diff --git a/mg_content.c b/mg_content.c new file mode 100644 index 0000000..5e8ecf6 --- /dev/null +++ b/mg_content.c @@ -0,0 +1,245 @@ +/*! + * \file mg_selection.c + * \brief A general interface to data items, currently only GiantDisc + * + * \version $Revision: 1.0 $ + * \date $Date: 2004-12-07 10:10:35 +0200 (Tue, 07 Dec 2004) $ + * \author Wolfgang Rohdewald + * \author Responsible author: $Author: wr $ + * + */ + +#include <stdio.h> +#include "i18n.h" +#include "mg_selection.h" +#include "vdr_setup.h" +#include "mg_tools.h" + + +string +mgContentItem::getKeyId(mgKeyTypes kt) +{ + if (m_id<0) + return ""; + switch (kt) { + case keyGenres: + case keyGenre1: + case keyGenre2: + case keyGenre3: return m_genre1_id; + default: return getKeyValue(kt); + } +} + +string +mgContentItem::getKeyValue(mgKeyTypes kt) +{ + if (m_id<0) + return ""; + switch (kt) { + case keyGenres: + case keyGenre1: + case keyGenre2: + case keyGenre3: return getGenre(); + 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 mgContentItem::getGenre () const +{ + string result=""; + if (m_genre1!="NULL") + result = m_genre1; + if (m_genre2!="NULL") + { + if (!result.empty()) + result += "/"; + result += m_genre2; + } + return result; +} + + +string mgContentItem::getBitrate () const +{ + return m_bitrate; +} + + +string mgContentItem::getImageFile () const +{ + return "Name of Imagefile"; +} + + +string mgContentItem::getAlbum () const +{ + return m_albumtitle; +} + + +int mgContentItem::getYear () const +{ + return m_year; +} + + +int mgContentItem::getRating () const +{ + return m_rating; +} + + +int mgContentItem::getDuration () const +{ + return m_duration; +} + + +int mgContentItem::getSampleRate () const +{ + return m_samplerate; +} + + +int mgContentItem::getChannels () const +{ + return m_channels; +} + +mgContentItem::mgContentItem () +{ + m_id = -1; +} + +mgContentItem::mgContentItem (const mgContentItem* c) +{ + m_id = c->m_id; + m_title = c->m_title; + m_mp3file = c->m_mp3file; + m_artist = c->m_artist; + m_albumtitle = c->m_albumtitle; + m_genre1_id = c->m_genre1_id; + m_genre2_id = c->m_genre2_id; + m_genre1 = c->m_genre1; + m_genre2 = c->m_genre2; + m_bitrate = c->m_bitrate; + m_year = c->m_year; + m_rating = c->m_rating; + m_duration = c->m_duration; + m_samplerate = c->m_samplerate; + m_channels = c->m_channels; +} + +static char *mg_readline(FILE *f) +{ + static char buffer[MAXPARSEBUFFER]; + if (fgets(buffer, sizeof(buffer), f) > 0) { + int l = strlen(buffer) - 1; + if (l >= 0 && buffer[l] == '\n') + buffer[l] = 0; + return buffer; + } + return 0; +} + +static const char *FINDCMD = "cd '%s' 2>/dev/null && find -follow -name '%s' -print 2>/dev/null"; + +static string +GdFindFile( const char* tld, string mp3file ) +{ + string result = ""; + char *cmd = 0; + asprintf( &cmd, FINDCMD, tld, mp3file.c_str() ); + FILE *p = popen( cmd, "r" ); + if (p) + { + char *s; + if( (s = mg_readline(p) ) != 0) + result = string(s); + pclose(p); + } + + free( cmd ); + + return result; +} + +string +mgContentItem::getSourceFile(bool AbsolutePath) const +{ + const char* tld = the_setup.ToplevelDir; + string result=""; + if (AbsolutePath) result = tld; + if (the_setup.GdCompatibility) + result += GdFindFile(tld,m_mp3file); + else + result += m_mp3file; + return result; +} + +mgContentItem::mgContentItem (const mgSelection* sel,const MYSQL_ROW row) +{ + m_id = atol (row[0]); + if (row[1]) + m_title = row[1]; + else + m_title = "NULL"; + if (row[2]) + m_mp3file = row[2]; + else + m_mp3file = "NULL"; + if (row[3]) + m_artist = row[3]; + else + m_artist = "NULL"; + if (row[4]) + m_albumtitle = row[4]; + else + m_albumtitle = "NULL"; + if (row[5]) + { + m_genre1_id = row[5]; + m_genre1 = sel->value(keyGenres,row[5]); + } + else + m_genre1 = "NULL"; + if (row[6]) + { + m_genre2_id = row[6]; + m_genre2 = sel->value(keyGenres,row[6]); + } + else + m_genre2 = "NULL"; + if (row[7]) + m_bitrate = row[7]; + else + m_bitrate = "NULL"; + if (row[8]) + m_year = atol (row[8]); + else + m_year = 0; + if (row[9]) + m_rating = atol (row[9]); + else + m_rating = 0; + if (row[10]) + m_duration = atol (row[10]); + else + m_duration = 0; + if (row[11]) + m_samplerate = atol (row[11]); + else + m_samplerate = 0; + if (row[12]) + m_channels = atol (row[12]); + else + m_channels = 0; +}; + diff --git a/mg_content.h b/mg_content.h new file mode 100644 index 0000000..313b54b --- /dev/null +++ b/mg_content.h @@ -0,0 +1,111 @@ +/*! + * \file mg_selection.h + * \brief A general interface to data items, currently only GiantDisc + * + * \version $Revision: 1.0 $ + * \date $Date: 2004-12-07 10:10:35 +0200 (Tue, 07 Dec 2004) $ + * \author Wolfgang Rohdewald + * \author Responsible author: $Author: wr $ + * + */ + +#ifndef _MG_CONTENT_H +#define _MG_CONTENT_H +#include <stdlib.h> +#include <mysql/mysql.h> +#include <string> +#include <list> +#include <vector> +#include <map> +using namespace std; + +#include "mg_tools.h" +#include "mg_valmap.h" +#include "mg_order.h" + +typedef vector<string> strvector; + + +class mgSelection; + +//! \brief represents a content item like an mp3 file. +class mgContentItem +{ + public: + mgContentItem (); + + string getKeyValue(mgKeyTypes kt); + string getKeyId(mgKeyTypes kt); + + //! \brief copy constructor + mgContentItem(const mgContentItem* c); + + //! \brief construct an item from an SQL row + mgContentItem (const mgSelection* sel, const MYSQL_ROW row); +//! \brief returns track id + long getId () const + { + return m_id; + } + +//! \brief returns title + string getTitle () const + { + return m_title; + } + +//! \brief returns filename + string getSourceFile (bool AbsolutePath=true) const; + +//! \brief returns artist + string getArtist () const + { + return m_artist; + } + +//! \brief returns the name of the album + string getAlbum () const; + +//! \brief returns the name of genre + string getGenre () const; + +//! \brief returns the bitrate + string getBitrate () const; + +//! \brief returns the file name of the album image + string getImageFile () const; + +//! \brief returns year + int getYear () const; + +//! \brief returns rating + int getRating () const; + +//! \brief returns duration + int getDuration () const; + +//! \brief returns samplerate + int getSampleRate () const; + +//! \brief returns # of channels + int getChannels () const; + + private: + long m_id; + string m_title; + string m_mp3file; + string m_artist; + string m_albumtitle; + string m_genre1_id; + string m_genre2_id; + string m_genre1; + string m_genre2; + string m_bitrate; + int m_year; + int m_rating; + int m_duration; + int m_samplerate; + int m_channels; +}; + +#endif diff --git a/mg_database.c b/mg_database.c deleted file mode 100644 index dff7b0f..0000000 --- a/mg_database.c +++ /dev/null @@ -1,130 +0,0 @@ -/*! \file mg_database.c - * \brief A capsule around MySql database access - * - * \version $Revision: 1.2 $ - * \date $Date$ - * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author$ - */ - -#include "mg_database.h" -#include "mg_tools.h" - -#include <stdarg.h> - -static const int MAX_QUERY_BUFLEN = 2048; - -static char *db_cmds[] = { - "use GiantDisc;" - "DROP DATABASE IF EXISTS GiantDisc; CREATE DATABASE GiantDisc;", - "grant all privileges on GiantDisc.* to vdr@localhost;", - "drop table if exists album; CREATE TABLE album ( artist varchar(255) default NULL, title varchar(255) default NULL, cddbid varchar(20) NOT NULL default '', coverimg varchar(255) default NULL, covertxt mediumtext, modified date default NULL, genre varchar(10) default NULL, PRIMARY KEY (cddbid), KEY artist (artist(10)), KEY title (title(10)), KEY genre (genre), KEY modified (modified)) TYPE=MyISAM;", - "drop table if exists genre; CREATE TABLE genre (id varchar(10) NOT NULL default '', id3genre smallint(6) default NULL, genre varchar(255) default NULL, freq int(11) default NULL, PRIMARY KEY (id)) TYPE=MyISAM;", - "drop table if exists language; CREATE TABLE language (id varchar(4) NOT NULL default '', language varchar(40) default NULL, freq int(11) default NULL, PRIMARY KEY (id)) TYPE=MyISAM;", - "drop table if exists musictype; CREATE TABLE musictype (musictype varchar(40) default NULL, id tinyint(3) unsigned NOT NULL auto_increment, PRIMARY KEY (id)) TYPE=MyISAM;", - "drop table if exists player;CREATE TABLE player ( ipaddr varchar(255) NOT NULL default '', uichannel varchar(255) NOT NULL default '', logtarget int(11) default NULL, cdripper varchar(255) default NULL, mp3encoder varchar(255) default NULL, cdromdev varchar(255) default NULL, cdrwdev varchar(255) default NULL, id int(11) NOT NULL default '0', PRIMARY KEY (id)) TYPE=MyISAM;", - "drop table if exists playerstate;CREATE TABLE playerstate ( playerid int(11) NOT NULL default '0', playertype int(11) NOT NULL default '0', snddevice varchar(255) default NULL, playerapp varchar(255) default NULL, playerparams varchar(255) default NULL, ptlogger varchar(255) default NULL, currtracknb int(11) default NULL, state varchar(4) default NULL, shufflepar varchar(255) default NULL, shufflestat varchar(255) default NULL, pauseframe int(11) default NULL, framesplayed int(11) default NULL, framestotal int(11) default NULL, anchortime bigint(20) default NULL, PRIMARY KEY (playerid,playertype)) TYPE=HEAP;", - "drop table if exists playlist;CREATE TABLE playlist ( title varchar(255) default NULL, author varchar(255) default NULL, note varchar(255) default NULL, created timestamp(8) NOT NULL, id int(10) unsigned NOT NULL auto_increment, PRIMARY KEY (id)) TYPE=MyISAM;", - "drop table if exists playlistitem;CREATE TABLE playlistitem ( playlist int(11) NOT NULL default '0', tracknumber mediumint(9) NOT NULL default '0', trackid int(11) default NULL, PRIMARY KEY (playlist,tracknumber)) TYPE=MyISAM;", - "drop table if exists playlog;CREATE TABLE playlog ( trackid int(11) default NULL, played date default NULL, id tinyint(3) unsigned NOT NULL auto_increment, PRIMARY KEY (id)) TYPE=MyISAM;", - "drop table if exists recordingitem;CREATE TABLE recordingitem ( trackid int(11) default NULL, recdate date default NULL, rectime time default NULL, reclength int(11) default NULL, enddate date default NULL, endtime time default NULL, repeat varchar(10) default NULL, initcmd varchar(255) default NULL, parameters varchar(255) default NULL, atqjob int(11) default NULL, id int(11) NOT NULL default '0', PRIMARY KEY (id)) TYPE=MyISAM;", - "drop table if exists source; CREATE TABLE source ( source varchar(40) default NULL, id tinyint(3) unsigned NOT NULL auto_increment, PRIMARY KEY (id)) TYPE=MyISAM;", - "drop table if exists tracklistitem;CREATE TABLE tracklistitem ( playerid int(11) NOT NULL default '0', listtype smallint(6) NOT NULL default '0', tracknb int(11) NOT NULL default '0', trackid int(11) NOT NULL default '0', PRIMARY KEY (playerid,listtype,tracknb)) TYPE=MyISAM;", - "drop table if exists tracks;CREATE TABLE tracks ( artist varchar(255) default NULL, title varchar(255) default NULL, genre1 varchar(10) default NULL, genre2 varchar(10) default NULL, year smallint(5) unsigned default NULL, lang varchar(4) default NULL, type tinyint(3) unsigned default NULL, rating tinyint(3) unsigned default NULL, length smallint(5) unsigned default NULL, source tinyint(3) unsigned default NULL, sourceid varchar(20) default NULL, tracknb tinyint(3) unsigned default NULL, mp3file varchar(255) default NULL, condition tinyint(3) unsigned default NULL, voladjust smallint(6) default '0', lengthfrm mediumint(9) default '0', startfrm mediumint(9) default '0', bpm smallint(6) default '0', lyrics mediumtext, bitrate varchar(10) default NULL, created date default NULL, modified date default NULL, backup tinyint(3) unsigned default NULL, samplerate int(7) unsigned default NULL, channels tinyint(3) unsigned default NULL, id int(11) NOT NULL auto_increment, PRIMARY KEY (id), KEY title (title(10)), KEY mp3file (mp3file(10)), KEY genre1 (genre1), KEY genre2 (genre2), KEY year (year), KEY lang (lang), KEY type (type), KEY rating (rating), KEY sourceid (sourceid), KEY artist (artist(10))) TYPE=MyISAM;" -}; - - -mgDB::mgDB() -{ -} - -mgDB::mgDB(std::string host, std::string name, - std::string user, std::string pass, - int port) -{ -} - -mgDB::~mgDB() -{ -} - -MYSQL_RES * -mgDB::exec_sql(std::string query) -{ - if (query.empty()) - return 0; - mgDebug( 3, "exec_sql(%X,%s)", &m_dbase, query.c_str() ); - if (mysql_query (&m_dbase, (query + ';').c_str ())) - { - mgError("SQL Error in %s: %s",query.c_str(),mysql_error (&m_dbase)); - std::cout << "ERROR in " << query << ":" << mysql_error(&m_dbase) << std::endl; - return 0; - } - return mysql_store_result(&m_dbase); -} - -void mgDB::initialize() -{ - // create database - exec_sql( std::string(db_cmds[1]) ); - exec_sql( std::string(db_cmds[0]) ); - exec_sql( std::string(db_cmds[2]) ); - - // create tables - int len = sizeof( db_cmds ) / sizeof( char* ); - for( int i=3; i < len; i ++ ) - { - exec_sql( std::string( db_cmds[i] ) ); - } -} - -MYSQL mgDB::getDBHandle() -{ - - return m_dbase; -} - -std::string mgDB::escape_string( MYSQL *db, std::string s ) -{ - char *escbuf = (char *) malloc( 2*s.size() + 1 ); - - mysql_real_escape_string( db, escbuf, s.c_str(), s.size() ); - - std::string r = std::string( escbuf ); - free( escbuf ); - - return r; -} - -MYSQL_RES* mgDB::read_query( const char *fmt, ...) -{ - char querybuf[MAX_QUERY_BUFLEN]; - va_list ap; - va_start( ap, fmt ); - vsnprintf( querybuf, MAX_QUERY_BUFLEN-1, fmt, ap ); - - if( mysql_query( &m_dbase, querybuf) ) - { - mgError( "SQL error in MUGGLE:\n%s\n", querybuf ); - } - - MYSQL_RES *result = mysql_store_result( &m_dbase ); - - va_end(ap); - return result; -} - -void mgDB::write_query( const char *fmt, ... ) -{ - char querybuf[MAX_QUERY_BUFLEN]; - va_list ap; - va_start( ap, fmt ); - vsnprintf( querybuf, MAX_QUERY_BUFLEN-1, fmt, ap ); - - if( mysql_query( &m_dbase, querybuf ) ) - { - mgError( "SQL error in MUGGLE:\n%s\n", querybuf ); - } - - va_end(ap); -} diff --git a/mg_database.h b/mg_database.h deleted file mode 100644 index de159aa..0000000 --- a/mg_database.h +++ /dev/null @@ -1,83 +0,0 @@ -/*! - * \file mg_database.h - * \brief A capsule around MySql database access - * - * \version $Revision: 1.2 $ - * \date $Date$ - * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author Responsible author: $Author$ - */ - -#ifndef __MG_DATABASE_H -#define __MG_DATABASE_H - -#include <string> -#include <mysql/mysql.h> - -/*! - * \brief an abstract database class - * - */ -class mgDB -{ - public: - - /*! \brief default constructor - */ - mgDB( ); - - /*! \brief constructor - * - * \param host - * \param name - * \param user - * \param pass - * \param port - */ - mgDB( std::string host, std::string name, - std::string user="", std::string pass="", - int port = 0 ); - - // add constructor for sockets - - /*! \brief destructor */ - ~mgDB(); - - /*! - * \brief obtain database handle - */ - MYSQL getDBHandle(); - - MYSQL_RES *exec_sql (std::string query); - - /*! - * \brief database initialization - */ - void initialize(); - - /*! - * \brief helper function to execute read queries - * - * \todo Could be a member of mgDatabase? - */ - MYSQL_RES* read_query( const char *fmt, ... ); - - /*! - * \brief helper function to execute write queries - * - * \todo Could be a member of mgDatabase? - */ - void write_query( const char *fmt, ... ); - - /*! - * \brief escape arguments to be contained in a query - * - * \todo use m_dbase member of this class - */ - static std::string escape_string( MYSQL *db, std::string s ); - - private: - MYSQL m_dbase; -}; - -#endif diff --git a/mg_mysql.c b/mg_mysql.c new file mode 100644 index 0000000..d7b6476 --- /dev/null +++ b/mg_mysql.c @@ -0,0 +1,583 @@ +/*! \file mg_mysql.c + * \brief A capsule around MySql database access + * + * \version $Revision: 1.2 $ + * \date $Date: 2005-02-10 17:42:54 +0100 (Thu, 10 Feb 2005) $ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner, Wolfgang Rohdewald + * \author file owner: $Author: LarsAC $ + */ + +#include "mg_mysql.h" +#include "mg_tools.h" + +#include <assert.h> +#include <stdarg.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <time.h> + +#include "vdr_setup.h" + +bool needGenre2; +static bool needGenre2_set; + +class mysqlhandle_t { + public: + mysqlhandle_t(); + ~mysqlhandle_t(); +}; + +#ifndef HAVE_SERVER +static char *datadir; + +static char *server_args[] = +{ + "muggle", + "--datadir=/tmp", // stupid default + "--key_buffer_size=32M" +}; +static char *server_groups[] = +{ + "embedded", + "server", + "muggle_SERVER", + 0 +}; + +void +set_datadir(char *dir) +{ + mgDebug(1,"setting datadir to %s",dir); + struct stat stbuf; + datadir=strdup(dir); + asprintf(&server_args[1],"--datadir=%s",datadir); + if (stat(datadir,&stbuf)) + mkdir(datadir,0755); + if (stat(datadir,&stbuf)) + { + mgError("Cannot access datadir %s: errno=%d",datadir,errno); + } +} +#endif + +mysqlhandle_t::mysqlhandle_t() +{ +#ifndef HAVE_SERVER + mgDebug(1,"calling mysql_server_init"); + if (mysql_server_init(sizeof(server_args) / sizeof(char *), + server_args, server_groups)) + mgDebug(3,"mysql_server_init failed"); +#endif +} + +mysqlhandle_t::~mysqlhandle_t() +{ +#ifndef HAVE_SERVER + mgDebug(3,"calling mysql_server_end"); + mysql_server_end(); +#endif +} + +static mysqlhandle_t* mysqlhandle; + +mgmySql::mgmySql() +{ + m_database_found=false; + m_hasfolderfields=false; + if (!mysqlhandle) + mysqlhandle = new mysqlhandle_t; + m_db = 0; + Connect(); +} + +mgmySql::~mgmySql() +{ + if (m_db) + { + mgDebug(3,"%X: closing DB connection",this); + mysql_close (m_db); + m_db = 0; + } +} + +static char *db_cmds[] = +{ + "drop table if exists album;", + "CREATE TABLE album ( " + "artist varchar(255) default NULL, " + "title varchar(255) default NULL, " + "cddbid varchar(20) NOT NULL default '', " + "coverimg varchar(255) default NULL, " + "covertxt mediumtext, " + "modified date default NULL, " + "genre varchar(10) default NULL, " + "PRIMARY KEY (cddbid), " + "KEY artist (artist(10)), " + "KEY title (title(10)), " + "KEY genre (genre), " + "KEY modified (modified)) " + "TYPE=MyISAM;", + "drop table if exists genre;", + "CREATE TABLE genre (" + "id varchar(10) NOT NULL default '', " + "id3genre smallint(6) default NULL, " + "genre varchar(255) default NULL, " + "freq int(11) default NULL, " + "PRIMARY KEY (id)) " + "TYPE=MyISAM;", + "drop table if exists language;", + "CREATE TABLE language (" + "id varchar(4) NOT NULL default '', " + "language varchar(40) default NULL, " + "freq int(11) default NULL, " + "PRIMARY KEY (id)) " + "TYPE=MyISAM;", + "drop table if exists musictype;", + "CREATE TABLE musictype (" + "musictype varchar(40) default NULL, " + "id tinyint(3) unsigned NOT NULL auto_increment, " + "PRIMARY KEY (id)) " + "TYPE=MyISAM;", + "drop table if exists player;", + "CREATE TABLE player ( " + "ipaddr varchar(255) NOT NULL default '', " + "uichannel varchar(255) NOT NULL default '', " + "logtarget int(11) default NULL, " + "cdripper varchar(255) default NULL, " + "mp3encoder varchar(255) default NULL, " + "cdromdev varchar(255) default NULL, " + "cdrwdev varchar(255) default NULL, " + "id int(11) NOT NULL default '0', " + "PRIMARY KEY (id)) " + "TYPE=MyISAM;", + "drop table if exists playerstate;", + "CREATE TABLE playerstate ( " + "playerid int(11) NOT NULL default '0', " + "playertype int(11) NOT NULL default '0', " + "snddevice varchar(255) default NULL, " + "playerapp varchar(255) default NULL, " + "playerparams varchar(255) default NULL, " + "ptlogger varchar(255) default NULL, " + "currtracknb int(11) default NULL, " + "state varchar(4) default NULL, " + "shufflepar varchar(255) default NULL, " + "shufflestat varchar(255) default NULL, " + "pauseframe int(11) default NULL, " + "framesplayed int(11) default NULL, " + "framestotal int(11) default NULL, " + "anchortime bigint(20) default NULL, " + "PRIMARY KEY (playerid,playertype)) " + "TYPE=HEAP;", + "drop table if exists playlist;", + "CREATE TABLE playlist ( " + "title varchar(255) default NULL, " + "author varchar(255) default NULL, " + "note varchar(255) default NULL, " + "created timestamp(8) NOT NULL, " + "id int(10) unsigned NOT NULL auto_increment, " + "PRIMARY KEY (id)) " + "TYPE=MyISAM;", + "drop table if exists playlistitem;", + "CREATE TABLE playlistitem ( " + "playlist int(11) NOT NULL default '0', " + "tracknumber mediumint(9) NOT NULL default '0', " + "trackid int(11) default NULL, " + "PRIMARY KEY (playlist,tracknumber)) " + "TYPE=MyISAM;", + "drop table if exists playlog;", + "CREATE TABLE playlog ( " + "trackid int(11) default NULL, " + "played date default NULL, " + "id tinyint(3) unsigned NOT NULL auto_increment, " + "PRIMARY KEY (id)) " + "TYPE=MyISAM;", + "drop table if exists recordingitem;", + "CREATE TABLE recordingitem ( " + "trackid int(11) default NULL, " + "recdate date default NULL, " + "rectime time default NULL, " + "reclength int(11) default NULL, " + "enddate date default NULL, " + "endtime time default NULL, " + "repeat varchar(10) default NULL, " + "initcmd varchar(255) default NULL, " + "parameters varchar(255) default NULL, " + "atqjob int(11) default NULL, " + "id int(11) NOT NULL default '0', " + "PRIMARY KEY (id)) " + "TYPE=MyISAM;", + "drop table if exists source", + "CREATE TABLE source ( " + "source varchar(40) default NULL, " + "id tinyint(3) unsigned NOT NULL auto_increment, " + "PRIMARY KEY (id)) " + "TYPE=MyISAM;", + "drop table if exists tracklistitem;", + "CREATE TABLE tracklistitem ( " + "playerid int(11) NOT NULL default '0', " + "listtype smallint(6) NOT NULL default '0', " + "tracknb int(11) NOT NULL default '0', " + "trackid int(11) NOT NULL default '0', " + "PRIMARY KEY (playerid,listtype,tracknb)) " + "TYPE=MyISAM;", + "drop table if exists tracks;", + "CREATE TABLE tracks ( " + "artist varchar(255) default NULL, " + "title varchar(255) default NULL, " + "genre1 varchar(10) default NULL, " + "genre2 varchar(10) default NULL, " + "year smallint(5) unsigned default NULL, " + "lang varchar(4) default NULL, " + "type tinyint(3) unsigned default NULL, " + "rating tinyint(3) unsigned default NULL, " + "length smallint(5) unsigned default NULL, " + "source tinyint(3) unsigned default NULL, " + "sourceid varchar(20) default NULL, " + "tracknb tinyint(3) unsigned default NULL, " + "mp3file varchar(255) default NULL, " + "condition tinyint(3) unsigned default NULL, " + "voladjust smallint(6) default '0', " + "lengthfrm mediumint(9) default '0', " + "startfrm mediumint(9) default '0', " + "bpm smallint(6) default '0', " + "lyrics mediumtext, " + "bitrate varchar(10) default NULL, " + "created date default NULL, " + "modified date default NULL, " + "backup tinyint(3) unsigned default NULL, " + "samplerate int(7) unsigned default NULL, " + "channels tinyint(3) unsigned default NULL, " + "id int(11) NOT NULL auto_increment, " + "folder1 varchar(255), " + "folder2 varchar(255), " + "folder3 varchar(255), " + "folder4 varchar(255), " + "PRIMARY KEY (id), " + "KEY title (title(10)), " + "KEY mp3file (mp3file(10)), " + "KEY genre1 (genre1), " + "KEY genre2 (genre2), " + "KEY year (year), " + "KEY lang (lang), " + "KEY type (type), " + "KEY rating (rating), " + "KEY sourceid (sourceid), " + "KEY artist (artist(10))) " + "TYPE=MyISAM;" +}; + + +MYSQL_RES* +mgmySql::exec_sql( string query) +{ + if (!m_db || query.empty()) + return 0; + mgDebug(4,"exec_sql(%X,%s)",m_db,query.c_str()); + if (mysql_query (m_db, (query + ';').c_str ())) + { + mgError("SQL Error in %s: %s",query.c_str(),mysql_error (m_db)); + std::cout<<"ERROR in " << query << ":" << mysql_error(m_db)<<std::endl; + return 0; + } + return mysql_store_result (m_db); +} + +string +mgmySql::get_col0( const string query) +{ + MYSQL_RES * sql_result = exec_sql ( query); + if (!sql_result) + return "NULL"; + MYSQL_ROW row = mysql_fetch_row (sql_result); + string result; + if (row == NULL) + result = "NULL"; + else if (row[0] == NULL) + result = "NULL"; + else + result = row[0]; + mysql_free_result (sql_result); + return result; +} + +unsigned long +mgmySql::exec_count( const string query) +{ + return atol (get_col0 ( query).c_str ()); +} + +struct genres_t { + char *id; + int id3genre; + char *name; +}; + +struct lang_t { + char *id; + char *name; +}; + +struct musictypes_t { + char *name; +}; + +struct sources_t { + char *name; +}; + +#include "mg_tables.h" +void mgmySql::FillTables() +{ + int len = sizeof( genres ) / sizeof( genres_t ); + for( int i=0; i < len; i ++ ) + { + char b[600]; + char id3genre[5]; + if (genres[i].id3genre>=0) + sprintf(id3genre,"%d",genres[i].id3genre); + else + strcpy(id3genre,"NULL"); + string genre = sql_string(genres[i].name); + sprintf(b,"INSERT INTO genre SET id='%s', id3genre=%s, genre=%s", + genres[i].id,id3genre,genre.c_str()); + exec_sql(b); + } + len = sizeof( languages ) / sizeof( lang_t ); + for( int i=0; i < len; i ++ ) + { + char b[600]; + sprintf(b,"INSERT INTO language SET id='%s', language=", + languages[i].id); + sql_Cstring(languages[i].name,strchr(b,0)); + exec_sql(b); + } + len = sizeof( musictypes ) / sizeof( musictypes_t ); + for( int i=0; i < len; i ++ ) + { + char b[600]; + sprintf(b,"INSERT INTO musictype SET musictype='%s'", + musictypes[i].name); + exec_sql(b); + } + len = sizeof( sources ) / sizeof( sources_t ); + for( int i=0; i < len; i ++ ) + { + char b[600]; + sprintf(b,"INSERT INTO source SET source='%s'", + sources[i].name); + exec_sql(b); + } +} + +time_t createtime; + +void mgmySql::Create() +{ + createtime=time(0); + // create database and tables + mgDebug(1,"Dropping and recreating database %s",the_setup.DbName); + if (mysql_query(m_db,"DROP DATABASE IF EXISTS GiantDisc;")) + { + mgWarning("Cannot drop existing database:%s",mysql_error (m_db)); + return; + } + if (mysql_query(m_db,"CREATE DATABASE GiantDisc;")) + { + mgWarning("Cannot create database:%s",mysql_error (m_db)); + return; + } +#ifdef HAVE_SERVER + mysql_query(m_db,"grant all privileges on GiantDisc.* to vdr@localhost;"); + // ignore error. If we can create the data base, we can do everything + // with it anyway. +#endif + mysql_query(m_db,"use GiantDisc;"); + int len = sizeof( db_cmds ) / sizeof( char* ); + for( int i=0; i < len; i ++ ) + { + if (mysql_query (m_db, db_cmds[i])) + { + mgWarning("%20s: %s",db_cmds[i],mysql_error (m_db)); + mysql_query(m_db, "DROP DATABASE IF EXISTS GiantDisc;"); // clean up + return; + } + } + m_database_found=true; + Use(); + FillTables(); +} + +string +mgmySql::sql_string( const string s ) +{ + char *b = sql_Cstring(s); + string result = string( b); + free( b); + return result; +} + +char* +mgmySql::sql_Cstring( const string s, char *buf ) +{ + return sql_Cstring(s.c_str(),buf); +} + +char* +mgmySql::sql_Cstring( const char *s, char *buf) +{ + char *b; + if (buf) + b=buf; + else + { + int buflen; + if (!this) + buflen=strlen(s)+2; + else + buflen=2*strlen(s)+3; + b = (char *) malloc( buflen); + } + b[0]='\''; + if (!this) + strcpy(b+1,s); + else + mysql_real_escape_string( m_db, b+1, s, strlen(s) ); + *(strchr(b,0)+1)=0; + *(strchr(b,0))='\''; + return b; +} + +bool +mgmySql::ServerConnected () const +{ + return m_db; +} + +bool +mgmySql::Connected () const +{ + return m_database_found; +} + +void +mgmySql::Connect () +{ + assert(!m_db); +#ifdef HAVE_SERVER + if (the_setup.DbHost == "") return; +#endif + m_db = mysql_init (0); + if (!m_db) + return; +#ifdef HAVE_SERVER + bool success; + if (the_setup.DbSocket != NULL) + { + mgDebug(1,"Using socket %s for connecting to server as user %s.", + the_setup.DbSocket, + the_setup.DbUser); + mgDebug(3,"DbPassword is: '%s'",the_setup.DbPass); + success = (mysql_real_connect( m_db, + "", + the_setup.DbUser, + the_setup.DbPass, + 0, + 0, + the_setup.DbSocket, 0 ) != 0 ); + } + else + { + mgDebug(1,"Using TCP for connecting to server %s as user %s.", + the_setup.DbHost, + the_setup.DbUser); + mgDebug(3,"DbPassword is: '%s'",the_setup.DbPass); + success = ( mysql_real_connect( m_db, + the_setup.DbHost, + the_setup.DbUser, + the_setup.DbPass, + 0, + the_setup.DbPort, + 0, 0 ) != 0 ); + } + if (!success) + { + mgWarning("Failed to connect to server '%s' as User '%s', Password '%s': %s", + the_setup.DbHost,the_setup.DbUser,the_setup.DbPass,mysql_error(m_db)); + mysql_close (m_db); + m_db = 0; + } +#else + if (!mysql_real_connect(m_db, 0, 0, 0, 0, 0, 0, 0)) + mgWarning("Failed to connect to embedded mysql in %s:%s ",datadir,mysql_error(m_db)); + else + mgDebug(1,"Connected to embedded mysql in %s",datadir); +#endif + if (m_db) + { + mysql_query(m_db,"SHOW DATABASES"); + MYSQL_RES * rows = mysql_store_result(m_db); + if (rows) + { + MYSQL_ROW row; + while ((row = mysql_fetch_row (rows)) != 0) + if (!strcmp(row[0],the_setup.DbName)) + { + m_database_found=true; + break; + } + mysql_free_result(rows); + } + if (m_database_found) + Use(); + else + if (!createtime) + mgWarning("Database %s does not exist",the_setup.DbName); + } + if (!needGenre2_set && Connected()) + { + needGenre2_set=true; + needGenre2=exec_count("SELECT COUNT(DISTINCT genre2) from tracks")>1; + } + return; +} + +void +mgmySql::Use() +{ + char b[100]; + sprintf(b,"USE %s;",the_setup.DbName); + mysql_query(m_db,b); + mgDebug(1,"found database %s",the_setup.DbName); +} + +void +mgmySql::CreateFolderFields() +{ + if (!Connected()) + return; + if (HasFolderFields()) + return; + mysql_query(m_db,"DESCRIBE tracks folder1"); + MYSQL_RES *rows = mysql_store_result(m_db); + if (rows) + { + m_hasfolderfields = mysql_num_rows(rows)>0; + mysql_free_result(rows); + if (!m_hasfolderfields) + { + m_hasfolderfields = !mysql_query(m_db, + "alter table tracks add column folder1 varchar(255)," + "add column folder2 varchar(255)," + "add column folder3 varchar(255)," + "add column folder4 varchar(255)"); + + } + } +} + +void +database_end() +{ + delete mysqlhandle; + mysqlhandle=0; +} diff --git a/mg_mysql.h b/mg_mysql.h new file mode 100644 index 0000000..eddc34e --- /dev/null +++ b/mg_mysql.h @@ -0,0 +1,77 @@ +/*! + * \file mg_mysql.h + * \brief A capsule around MySql database access + * + * \version $Revision: 1.2 $ + * \date $Date: 2005-02-10 17:42:54 +0100 (Thu, 10 Feb 2005) $ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner, Wolfgang Rohdewald + * \author Responsible author: $Author: LarsAC $ + */ + +#ifndef __MG_MYSQL_H +#define __MG_MYSQL_H + +#include <string> +#include <mysql/mysql.h> + +using namespace std; + +void database_end(); // must be done explicitly +void set_datadir(char *datadir); + +/*! + * \brief an abstract database class + * + */ +class mgmySql +{ + public: + + /*! \brief default constructor + */ + mgmySql( ); + + ~mgmySql(); + + /*! + * \brief helper function to execute queries + * + */ + MYSQL_RES* exec_sql( const string query); + + /*! + * \brief escape arguments to be contained in a query + */ + string sql_string( string s ); + + char* sql_Cstring( const string s,char *buf=0); + char* sql_Cstring( const char *s,char *buf=0); + + string get_col0( const string query); + +/*! \brief executes a query and returns the integer value from + * the first column in the first row. The query shold be a COUNT query + * returning only one row. + * \param query the SQL query to be executed + */ + unsigned long exec_count (string query); + + long thread_id() { return mysql_thread_id(m_db);} + long affected_rows() { return mysql_affected_rows(m_db);} + bool ServerConnected() const; + bool Connected() const; + bool HasFolderFields() const { return m_hasfolderfields;} + void Connect(); + void Use(); + //! \brief create database and tables + void Create(); + void FillTables(); + void CreateFolderFields(); + + private: + MYSQL *m_db; + bool m_database_found; + bool m_hasfolderfields; +}; + +#endif @@ -2,7 +2,7 @@ #include "mg_tools.h" #include "i18n.h" #include <stdio.h> - +#include <assert.h> const char * EMPTY = "XNICHTGESETZTX"; @@ -24,18 +24,6 @@ strlist& operator+=(strlist&a, strlist b) } -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 = "'" + string (buf) + "'"; - free (buf); - 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 @@ -65,48 +53,6 @@ optimize (string & spar) 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); -} - -string -get_col0(MYSQL *db, string query) -{ - MYSQL_RES * sql_result = exec_sql (db, query); - if (!sql_result) - return "NULL"; - MYSQL_ROW row = mysql_fetch_row (sql_result); - string result; - if (row == NULL) - result = "NULL"; - else if (row[0] == NULL) - result = "NULL"; - else - result = row[0]; - mysql_free_result (sql_result); - return result; -} - -int -exec_count(MYSQL *db, string query) -{ - return atol (get_col0 (db, query).c_str ()); -} - - string& addsep (string & s, string sep, string n) { @@ -158,7 +104,7 @@ class mgKeyNormal : public mgKey { public: mgKeyNormal(const mgKeyNormal& k); mgKeyNormal(const mgKeyTypes kt, string table, string field); - virtual mgParts Parts(bool orderby=false) const; + virtual mgParts Parts(mgmySql &db,bool orderby=false) const; string value() const; string id() const; void set(string value,string id); @@ -166,8 +112,8 @@ class mgKeyNormal : public mgKey { virtual string expr() const { return m_table + "." + m_field; } virtual string table() const { return m_table; } protected: - string IdClause(string what,string::size_type start=0,string::size_type len=string::npos) const; - void AddIdClause(mgParts &result,string what) const; + string IdClause(mgmySql &db,string what,string::size_type start=0,string::size_type len=string::npos) const; + void AddIdClause(mgmySql &db,mgParts &result,string what) const; string m_id; string m_field; private: @@ -184,19 +130,19 @@ class mgKeyDate : public mgKeyNormal { class mgKeyTrack : public mgKeyNormal { public: mgKeyTrack() : mgKeyNormal(keyTrack,"tracks","tracknb") {}; - mgParts Parts(bool orderby=false) const; + mgParts Parts(mgmySql &db,bool orderby=false) const; }; class mgKeyAlbum : public mgKeyNormal { public: mgKeyAlbum() : mgKeyNormal(keyAlbum,"album","title") {}; - mgParts Parts(bool orderby=false) const; + mgParts Parts(mgmySql &db,bool orderby=false) const; }; mgParts -mgKeyAlbum::Parts(bool orderby) const +mgKeyAlbum::Parts(mgmySql &db,bool orderby) const { - mgParts result = mgKeyNormal::Parts(orderby); + mgParts result = mgKeyNormal::Parts(db,orderby); result.tables.push_back("tracks"); return result; } @@ -205,7 +151,7 @@ class mgKeyFolder : public mgKeyNormal { public: mgKeyFolder(mgKeyTypes kt,const char *fname) : mgKeyNormal(kt,"tracks",fname) { m_enabled=-1;}; - bool Enabled(); + bool Enabled(mgmySql &db); private: int m_enabled; }; @@ -228,15 +174,15 @@ class mgKeyFolder4 : public mgKeyFolder { }; bool -mgKeyFolder::Enabled() +mgKeyFolder::Enabled(mgmySql &db) { if (m_enabled<0) { - if (!m_db) + if (!db.Connected()) return false; char *b; asprintf(&b,"DESCRIBE tracks %s",m_field.c_str()); - MYSQL_RES * rows = exec_sql (m_db, b); + MYSQL_RES * rows = db.exec_sql (b); free(b); if (rows) { @@ -251,12 +197,14 @@ class mgKeyGenres : public mgKeyNormal { public: mgKeyGenres() : mgKeyNormal(keyGenres,"tracks","genre1") {}; mgKeyGenres(mgKeyTypes kt) : mgKeyNormal(kt,"tracks","genre1") {}; - mgParts Parts(bool orderby=false) const; + mgParts Parts(mgmySql &db,bool orderby=false) const; string map_idfield() const { return "id"; } string map_valuefield() const { return "genre"; } string map_valuetable() const { return "genre"; } - string GenreClauses(bool orderby) const; + protected: virtual unsigned int genrelevel() const { return 4; } + private: + string GenreClauses(mgmySql &db,bool orderby) const; }; class mgKeyGenre1 : public mgKeyGenres @@ -281,7 +229,7 @@ class mgKeyGenre3 : public mgKeyGenres }; string -mgKeyGenres::GenreClauses(bool orderby) const +mgKeyGenres::GenreClauses(mgmySql &db,bool orderby) const { strlist g1; strlist g2; @@ -302,8 +250,8 @@ mgKeyGenres::GenreClauses(bool orderby) const { unsigned int len=genrelevel(); if (len==4) len=0; - g1.push_back(IdClause("tracks.genre1",0,genrelevel())); - g2.push_back(IdClause("tracks.genre2",0,genrelevel())); + g1.push_back(IdClause(db,"tracks.genre1",0,genrelevel())); + g2.push_back(IdClause(db,"tracks.genre2",0,genrelevel())); } extern bool needGenre2; @@ -321,10 +269,10 @@ mgKeyGenres::GenreClauses(bool orderby) const mgParts -mgKeyGenres::Parts(bool orderby) const +mgKeyGenres::Parts(mgmySql &db,bool orderby) const { mgParts result; - result.clauses.push_back(GenreClauses(orderby)); + result.clauses.push_back(GenreClauses(db,orderby)); result.tables.push_back("tracks"); if (orderby) { @@ -340,7 +288,7 @@ mgKeyGenres::Parts(bool orderby) const class mgKeyLanguage : public mgKeyNormal { public: mgKeyLanguage() : mgKeyNormal(keyLanguage,"tracks","lang") {}; - mgParts Parts(bool orderby=false) const; + mgParts Parts(mgmySql &db,bool orderby=false) const; string map_idfield() const { return "id"; } string map_valuefield() const { return "language"; } string map_valuetable() const { return "language"; } @@ -349,7 +297,7 @@ class mgKeyLanguage : public mgKeyNormal { class mgKeyCollection: public mgKeyNormal { public: mgKeyCollection() : mgKeyNormal(keyCollection,"playlist","id") {}; - mgParts Parts(bool orderby=false) const; + mgParts Parts(mgmySql &db,bool orderby=false) const; string map_idfield() const { return "id"; } string map_valuefield() const { return "title"; } string map_valuetable() const { return "playlist"; } @@ -357,7 +305,7 @@ class mgKeyCollection: public mgKeyNormal { class mgKeyCollectionItem : public mgKeyNormal { public: mgKeyCollectionItem() : mgKeyNormal(keyCollectionItem,"playlistitem","tracknumber") {}; - mgParts Parts(bool orderby=false) const; + mgParts Parts(mgmySql &db,bool orderby=false) const; }; class mgKeyDecade : public mgKeyNormal { @@ -379,28 +327,12 @@ mgKeyNormal::value() const } -mgKey::mgKey() -{ - m_db = 0; -} - -mgKey::~mgKey() -{ -} - -void -mgKey::setdb(MYSQL *db) -{ - m_db = db; -} - 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) @@ -409,7 +341,6 @@ mgKeyNormal::mgKeyNormal(const mgKeyTypes kt, string table, string field) m_table = table; m_field = field; m_id = EMPTY; - m_db = 0; } void @@ -422,6 +353,7 @@ mgKeyNormal::set(string value, string id) mgParts::mgParts() { m_sql_select=""; + orderByCount = false; } mgParts::~mgParts() @@ -429,12 +361,11 @@ mgParts::~mgParts() } mgParts -mgKeyNormal::Parts(bool orderby) const +mgKeyNormal::Parts(mgmySql &db, bool orderby) const { - assert(strlen(m_db->host)); mgParts result; result.tables.push_back(table()); - AddIdClause(result,expr()); + AddIdClause(db,result,expr()); if (orderby) { result.fields.push_back(expr()); @@ -444,35 +375,34 @@ mgKeyNormal::Parts(bool orderby) const } string -mgKeyNormal::IdClause(string what,string::size_type start,string::size_type len) const +mgKeyNormal::IdClause(mgmySql &db,string what,string::size_type start,string::size_type len) const { - assert(strlen(m_db->host)); if (len==0) len=string::npos; if (id() == "'NULL'") return what + " is NULL"; else if (len==string::npos) - return what + "=" + sql_string(m_db,id()); + return what + "=" + db.sql_string(id()); else { return "substring("+what + ","+ltos(start+1)+","+ltos(len)+")=" - + sql_string(m_db,id().substr(start,len)); + + db.sql_string(id().substr(start,len)); } } void -mgKeyNormal::AddIdClause(mgParts &result,string what) const +mgKeyNormal::AddIdClause(mgmySql &db,mgParts &result,string what) const { if (id() != EMPTY) - result.clauses.push_back(IdClause(what)); + result.clauses.push_back(IdClause(db,what)); } mgParts -mgKeyTrack::Parts(bool orderby) const +mgKeyTrack::Parts(mgmySql &db,bool orderby) const { mgParts result; result.tables.push_back("tracks"); - AddIdClause(result,"tracks.title"); + AddIdClause(db,result,"tracks.title"); if (orderby) { // if you change tracks.title, please also @@ -484,10 +414,10 @@ mgKeyTrack::Parts(bool orderby) const } mgParts -mgKeyLanguage::Parts(bool orderby) const +mgKeyLanguage::Parts(mgmySql &db,bool orderby) const { mgParts result; - AddIdClause(result,"tracks.lang"); + AddIdClause(db,result,"tracks.lang"); result.tables.push_back("tracks"); if (orderby) { @@ -500,13 +430,13 @@ mgKeyLanguage::Parts(bool orderby) const } mgParts -mgKeyCollection::Parts(bool orderby) const +mgKeyCollection::Parts(mgmySql &db,bool orderby) const { mgParts result; if (orderby) { result.tables.push_back("playlist"); - AddIdClause(result,"playlist.id"); + AddIdClause(db,result,"playlist.id"); result.fields.push_back("playlist.title"); result.fields.push_back("playlist.id"); result.orders.push_back("playlist.title"); @@ -514,18 +444,17 @@ mgKeyCollection::Parts(bool orderby) const else { result.tables.push_back("playlistitem"); - AddIdClause(result,"playlistitem.playlist"); + AddIdClause(db,result,"playlistitem.playlist"); } return result; } mgParts -mgKeyCollectionItem::Parts(bool orderby) const +mgKeyCollectionItem::Parts(mgmySql &db,bool orderby) const { - assert(strlen(m_db->host)); mgParts result; result.tables.push_back("playlistitem"); - AddIdClause(result,"playlistitem.tracknumber"); + AddIdClause(db,result,"playlistitem.tracknumber"); if (orderby) { // tracks nur hier, fuer sql_delete_from_coll wollen wir es nicht @@ -583,7 +512,7 @@ mgParts::sql_select(bool distinct) string result; if (distinct) { - fields.push_back("COUNT(*)"); + fields.push_back("COUNT(*) AS mgcount"); result = sql_list("SELECT",fields); fields.pop_back(); } @@ -594,7 +523,11 @@ mgParts::sql_select(bool distinct) result += sql_list("FROM",tables); result += sql_list("WHERE",clauses," AND "); if (distinct) + { result += sql_list("GROUP BY",fields); + if (orderByCount) + orders.insert(orders.begin(),"mgcount desc"); + } result += sql_list("ORDER BY",orders); optimize(result); return result; @@ -666,10 +599,11 @@ mgReference::mgReference(string t1,string f1,string t2,string f2) mgOrder::mgOrder() { - setDB(0); - setKey (0,keyArtist); - setKey (1,keyAlbum); - setKey (2,keyTrack); + clear(); + setKey (keyArtist); + setKey (keyAlbum); + setKey (keyTrack); + m_orderByCount = false; } mgOrder::~mgOrder() @@ -686,7 +620,7 @@ mgOrder::Key(unsigned int idx) const mgKey*& mgOrder::operator[](unsigned int idx) { - assert(idx<Keys.size()); + assert(idx<size()); return Keys[idx]; } @@ -721,11 +655,11 @@ mgOrder::InitFrom(const mgOrder &from) { for (unsigned int i = 0; i < from.size();i++) { - mgKey *k = ktGenerate(from.getKeyType(i),m_db); + mgKey *k = ktGenerate(from.getKeyType(i)); k->set(from.getKeyValue(i),from.getKeyId(i)); Keys.push_back(k); } - if (from.m_db) setDB(from.m_db); + m_orderByCount=from.m_orderByCount; } string @@ -741,51 +675,48 @@ mgOrder::Name() } void -mgOrder::setKey (const unsigned int level, const mgKeyTypes kt) +mgOrder::setKey (const mgKeyTypes kt) { - mgKey *newkey = ktGenerate(kt,m_db); - if (level == 0 && kt == keyCollection) - { - clear (); - Keys.push_back(newkey); - Keys.push_back(ktGenerate(keyCollectionItem,m_db)); - return; - } - if (level == size ()) - { - Keys.push_back(newkey); - } - else - { - if (level >= Keys.size()) - mgError("mgOrder::setKey(%u,%s): level greater than size() %u", - level,ktName(kt),Keys.size()); - delete Keys[level]; - Keys[level] = newkey; - } - -// clear values for this and following levels (needed for copy constructor) - for (unsigned int i = level; i < Keys.size (); i++) - Keys[i]->set ("",EMPTY); + mgKey *newkey = ktGenerate(kt); + if (newkey) + Keys.push_back(newkey); } mgOrder::mgOrder(mgValmap& nv,char *prefix) { - setDB(0); + char *idx; + asprintf(&idx,"%s.OrderByCount",prefix); + m_orderByCount = nv.getbool(idx); + free(idx); + clear(); for (unsigned int i = 0; i < 999 ; i++) { - char *idx; asprintf(&idx,"%s.Keys.%u.Type",prefix,i); unsigned int v = nv.getuint(idx); free(idx); if (v==0) break; - setKey (i,mgKeyTypes(v) ); + setKey (mgKeyTypes(v) ); + } + if (size()>0) + clean(); +} + +void +mgOrder::DumpState(mgValmap& nv, char *prefix) const +{ + char n[100]; + sprintf(n,"%s.OrderByCount",prefix); + nv.put(n,m_orderByCount); + for (unsigned int i=0;i<size();i++) + { + sprintf(n,"%s.Keys.%d.Type",prefix,i); + nv.put(n,int(Key(i)->Type())); } } mgOrder::mgOrder(vector<mgKeyTypes> kt) { - setDB(0); + m_orderByCount = false; setKeys(kt); } @@ -794,7 +725,7 @@ mgOrder::setKeys(vector<mgKeyTypes> kt) { clear(); for (unsigned int i=0;i<kt.size();i++) - setKey(size(),kt[i]); + setKey(kt[i]); clean(); } @@ -821,29 +752,6 @@ mgOrder::getKeyId(unsigned int idx) const } 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) @@ -865,6 +773,8 @@ mgOrder::clean() // remove double entries: keyvector::iterator i; keyvector::iterator j; + bool collection_found = false; + bool collitem_found = false; bool album_found = false; bool tracknb_found = false; bool title_found = false; @@ -872,10 +782,13 @@ mgOrder::clean() for (i = Keys.begin () ; i != Keys.end (); ++i) { mgKeyNormal* k = dynamic_cast<mgKeyNormal*>(*i); + collection_found |= (k->Type()==keyCollection); + collitem_found |= (k->Type()==keyCollectionItem); album_found |= (k->Type()==keyAlbum); tracknb_found |= (k->Type()==keyTrack); title_found |= (k->Type()==keyTitle); - is_unique = tracknb_found || (album_found && title_found); + is_unique = tracknb_found || (album_found && title_found) + || (collection_found && collitem_found); if (is_unique) { for (j = i+1 ; j !=Keys.end(); ++j) @@ -902,25 +815,29 @@ cleanagain: goto cleanagain; } } - bool IsCollection = size()==0 ? false : Keys[0]->Type()==keyCollection; - if (!IsCollection && !is_unique) + if (!is_unique) { if (!album_found) - Keys.push_back(ktGenerate(keyAlbum,m_db)); + Keys.push_back(ktGenerate(keyAlbum)); if (!title_found) - Keys.push_back(ktGenerate(keyTitle,m_db)); + Keys.push_back(ktGenerate(keyTitle)); } } +bool +mgOrder::isCollectionOrder() const +{ + return (size()==2 + && (Keys[0]->Type()==keyCollection) + && (Keys[1]->Type()==keyCollectionItem)); +} mgParts -mgOrder::Parts(unsigned int level,bool orderby) const +mgOrder::Parts(mgmySql &db,unsigned int level,bool orderby) const { - assert(strlen(m_db->host)); mgParts result; - mgKeyNormal *k0 = dynamic_cast<mgKeyNormal*>(Keys[0]); - mgKeyTypes kt0 = k0->Type(); - if (level==0 && kt0==keyCollection) + result.orderByCount = m_orderByCount; + if (level==0 && isCollectionOrder()) { // sql command contributed by jarny result.m_sql_select = string("select playlist.title,playlist.id, " @@ -933,7 +850,6 @@ mgOrder::Parts(unsigned int level,bool orderby) const { if (i==Keys.size()) break; mgKeyNormal *k = dynamic_cast<mgKeyNormal*>(Keys[i]); - k->setdb(m_db); mgKeyTypes kt = k->Type(); if (iskeyGenre(kt)) { @@ -951,7 +867,7 @@ mgOrder::Parts(unsigned int level,bool orderby) const } } } - result += k->Parts(orderby && (i==level)); + result += k->Parts(db,orderby && (i==level)); next: continue; } @@ -1032,7 +948,7 @@ mgReferences::Connect(string table1, string table2) const mgKey* -ktGenerate(const mgKeyTypes kt,MYSQL* db) +ktGenerate(const mgKeyTypes kt) { mgKey* result = 0; switch (kt) @@ -1058,7 +974,6 @@ ktGenerate(const mgKeyTypes kt,MYSQL* db) 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; } @@ -1095,7 +1010,7 @@ ktName(const mgKeyTypes kt) mgKeyTypes ktValue(const char * name) { - for (int kt=int(mgKeyTypesLow);kt<int(mgKeyTypesHigh);kt++) + for (int kt=int(mgKeyTypesLow);kt<=int(mgKeyTypesHigh);kt++) if (!strcmp(name,ktName(mgKeyTypes(kt)))) return mgKeyTypes(kt); mgError("ktValue(%s): unknown name",name); @@ -1,14 +1,13 @@ #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 <sstream> #include "mg_valmap.h" +#include "mg_mysql.h" using namespace std; @@ -78,9 +77,8 @@ private: class mgKey { public: - mgKey(); - virtual ~mgKey(); - virtual mgParts Parts(bool orderby=false) const = 0; + virtual ~mgKey() {}; + virtual mgParts Parts(mgmySql &db,bool orderby=false) const = 0; virtual string id() const = 0; virtual string value () const = 0; //!\brief translate field into user friendly string @@ -89,15 +87,12 @@ class mgKey { virtual string map_idfield() const { return ""; } virtual string map_valuefield() const { return ""; } virtual string map_valuetable() const { return ""; } - void setdb(MYSQL *db); - virtual bool Enabled() { return true; } - protected: - MYSQL *m_db; + virtual bool Enabled(mgmySql &db) { return true; } }; mgKey* -ktGenerate(const mgKeyTypes kt,MYSQL *db); +ktGenerate(const mgKeyTypes kt); const char * const ktName(const mgKeyTypes kt); mgKeyTypes ktValue(const char * name); @@ -122,18 +117,12 @@ public: string sql_update(strlist new_values); bool empty() const { return tables.size()==0;} string m_sql_select; + bool orderByCount; private: bool UsesTracks(); mgReferences ref; }; -string -sql_string (MYSQL *db, const string s); - -MYSQL_RES * exec_sql (MYSQL *db,string query); -string get_col0 (MYSQL *db,string query); -int exec_count (MYSQL *db,string query); - //! \brief converts long to string string itos (int i); @@ -151,26 +140,28 @@ public: mgOrder(vector<mgKeyTypes> kt); ~mgOrder(); void InitFrom(const mgOrder &from); - void setDB(MYSQL *db); - mgParts Parts(unsigned int level,bool orderby=true) const; + void DumpState(mgValmap& nv, char *prefix) const; + mgParts Parts(mgmySql &db,const unsigned int level,bool orderby=true) const; const mgOrder& operator=(const mgOrder& from); 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(); - 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; void setKeys(vector<mgKeyTypes> kt); string Name(); + void setOrderByCount(bool orderbycount) { m_orderByCount = orderbycount;} + bool getOrderByCount() { return m_orderByCount; } private: - MYSQL *m_db; + bool m_orderByCount; + bool isCollectionOrder() const; keyvector Keys; - void setKey (const unsigned int level, const mgKeyTypes kt); + void setKey ( const mgKeyTypes kt); + void clean(); }; bool operator==(const mgOrder& a,const mgOrder&b); //! \brief compares only the order, not the current key values @@ -1,6 +1,6 @@ /*! - * \file mg_db.c - * \brief A database interface to the GiantDisc + * \file mg_selection.c + * \brief A general interface to data items, currently only GiantDisc * * \version $Revision: 1.0 $ * \date $Date: 2004-12-07 10:10:35 +0200 (Tue, 07 Dec 2004) $ @@ -9,13 +9,28 @@ * */ +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> #include <stdio.h> -#include "mg_db.h" +#include <fts.h> +#include <assert.h> + +#include "i18n.h" +#include "mg_selection.h" #include "vdr_setup.h" #include "mg_tools.h" +#include "mg_sync.h" + +#include <mpegfile.h> +#include <flacfile.h> +#include <id3v2tag.h> +#include <fileref.h> -bool needGenre2; -bool needGenre2_set; +#if VDRVERSNUM >= 10307 +#include <vdr/interface.h> +#include <vdr/skins.h> +#endif //! \brief adds string n to string s, using a comma to separate them static string comma (string & s, string n); @@ -107,74 +122,6 @@ 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) const -{ - return ::get_col0(m_db, query); -} - - -unsigned long -mgSelection::exec_count (string query) const -{ - return ::exec_count(m_db, query); -} - - - -string -mgSelection::sql_string (const string s) const -{ - char *buf = (char *) malloc (s.size () * 2 + 1); - mysql_real_escape_string (m_db, buf, s.c_str (), s.size ()); - string result = "'" + std::string (buf) + "'"; - free (buf); - return result; -} - -string -mgContentItem::getKeyId(mgKeyTypes kt) -{ - if (m_id<0) - return ""; - switch (kt) { - case keyGenres: - case keyGenre1: - case keyGenre2: - case keyGenre3: return m_genre1_id; - default: return getKeyValue(kt); - } -} - -string -mgContentItem::getKeyValue(mgKeyTypes kt) -{ - if (m_id<0) - return ""; - switch (kt) { - case keyGenres: - case keyGenre1: - case keyGenre2: - case keyGenre3: return getGenre(); - 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 ""; - } -} - mgContentItem * mgSelection::getTrack (unsigned int position) { @@ -184,69 +131,6 @@ mgSelection::getTrack (unsigned int position) } -string mgContentItem::getGenre () const -{ - string result=""; - if (m_genre1!="NULL") - result = m_genre1; - if (m_genre2!="NULL") - { - if (!result.empty()) - result += "/"; - result += m_genre2; - } - return result; -} - - -string mgContentItem::getBitrate () const -{ - return m_bitrate; -} - - -string mgContentItem::getImageFile () const -{ - return "Name of Imagefile"; -} - - -string mgContentItem::getAlbum () const -{ - return m_albumtitle; -} - - -int mgContentItem::getYear () const -{ - return m_year; -} - - -int mgContentItem::getRating () const -{ - return m_rating; -} - - -int mgContentItem::getDuration () const -{ - return m_duration; -} - - -int mgContentItem::getSampleRate () const -{ - return m_samplerate; -} - - -int mgContentItem::getChannels () const -{ - return m_channels; -} - - mgSelection::ShuffleMode mgSelection::toggleShuffleMode () { setShuffleMode((m_shuffle_mode == SM_PARTY) ? SM_NONE : ShuffleMode (m_shuffle_mode + 1)); @@ -326,33 +210,47 @@ mgSelection::LoopMode mgSelection::toggleLoopMode () unsigned int mgSelection::AddToCollection (const string Name) { - if (!m_db) return 0; + if (!m_db.Connected()) return 0; CreateCollection(Name); - string listid = sql_string (get_col0 - ("SELECT id FROM playlist WHERE title=" + sql_string (Name))); - string tmp = - get_col0 ("SELECT MAX(tracknumber) FROM playlistitem WHERE playlist=" + - listid); - int high; - if (tmp == "NULL") - high = 0; - else - high = atol (tmp.c_str ()); + string listid = m_db.sql_string (m_db.get_col0 + ("SELECT id FROM playlist WHERE title=" + m_db.sql_string (Name))); unsigned int tracksize = getNumTracks (); + if (tracksize==0) + return 0; + + // this code is rather complicated but works in a multi user + // environment: + + // insert a unique trackid: + string trackid = ltos(m_db.thread_id()+1000000); + m_db.exec_sql("INSERT INTO playlistitem SELECT "+listid+"," + "MAX(tracknumber)+"+ltos(tracksize)+","+trackid+ + " FROM playlistitem WHERE playlist="+listid); + + // find tracknumber of the trackid we just inserted: + string sql = string("SELECT tracknumber FROM playlistitem WHERE " + "playlist=")+listid+" AND trackid="+trackid; + long first = atol(m_db.get_col0(sql).c_str()) - tracksize + 1; + + // replace the place holder trackid by the correct value: + m_db.exec_sql("UPDATE playlistitem SET trackid="+ltos(m_tracks[tracksize-1].getId())+ + " WHERE playlist="+listid+" AND trackid="+trackid); + + // insert all other tracks: const char *sql_prefix = "INSERT INTO playlistitem VALUES "; - string sql = ""; - for (unsigned int i = 0; i < tracksize; i++) + sql = ""; + for (unsigned int i = 0; i < tracksize-1; i++) { - string item = "(" + listid + "," + ltos (high + 1 + i) + "," + + string item = "(" + listid + "," + ltos (first + i) + "," + ltos (m_tracks[i].getId ()) + ")"; comma(sql, item); if ((i%100)==99) { - exec_sql (sql_prefix+sql); + m_db.exec_sql (sql_prefix+sql); sql = ""; } } - if (!sql.empty()) exec_sql (sql_prefix+sql); + if (!sql.empty()) m_db.exec_sql (sql_prefix+sql); if (inCollection(Name)) clearCache (); return tracksize; } @@ -361,11 +259,11 @@ mgSelection::AddToCollection (const string Name) unsigned int mgSelection::RemoveFromCollection (const string Name) { - if (!m_db) return 0; - mgParts p = order.Parts(m_level,false); + if (!m_db.Connected()) return 0; + mgParts p = order.Parts(m_db,m_level,false); string sql = p.sql_delete_from_collection(id(keyCollection,Name)); - exec_sql (sql); - unsigned int removed = mysql_affected_rows (m_db); + m_db.exec_sql (sql); + unsigned int removed = m_db.affected_rows (); if (inCollection(Name)) clearCache (); return removed; } @@ -373,30 +271,30 @@ mgSelection::RemoveFromCollection (const string Name) bool mgSelection::DeleteCollection (const string Name) { - if (!m_db) return false; + if (!m_db.Connected()) return false; ClearCollection(Name); - exec_sql ("DELETE FROM playlist WHERE title=" + sql_string (Name)); + m_db.exec_sql ("DELETE FROM playlist WHERE title=" + m_db.sql_string (Name)); if (isCollectionlist()) clearCache (); - return (mysql_affected_rows (m_db) == 1); + return (m_db.affected_rows () == 1); } void mgSelection::ClearCollection (const string Name) { - if (!m_db) return; + if (!m_db.Connected()) return; string listid = id(keyCollection,Name); - exec_sql ("DELETE FROM playlistitem WHERE playlist="+sql_string(listid)); + m_db.exec_sql ("DELETE FROM playlistitem WHERE playlist="+m_db.sql_string(listid)); if (inCollection(Name)) clearCache (); } bool mgSelection::CreateCollection(const string Name) { - if (!m_db) return false; - string name = sql_string(Name); - if (exec_count("SELECT count(title) FROM playlist WHERE title = " + name)>0) + if (!m_db.Connected()) return false; + string name = m_db.sql_string(Name); + if (m_db.exec_count("SELECT count(title) FROM playlist WHERE title = " + name)>0) return false; - exec_sql ("INSERT playlist VALUES(" + name + ",'VDR',NULL,NULL,NULL)"); + m_db.exec_sql ("INSERT playlist VALUES(" + name + ",'VDR',NULL,NULL,NULL)"); if (isCollectionlist()) clearCache (); return true; } @@ -587,9 +485,9 @@ string mgSelection::ListFilename () const vector < mgContentItem > & mgSelection::tracks () const { - if (!m_db) return m_tracks; + if (!m_db.Connected()) return m_tracks; if (!m_current_tracks.empty()) return m_tracks; - mgParts p = order.Parts(m_level); + mgParts p = order.Parts(m_db,m_level); p.fields.clear(); p.fields.push_back("tracks.id"); p.fields.push_back("tracks.title"); @@ -607,10 +505,10 @@ mgSelection::tracks () const p.tables.push_back("tracks"); p.tables.push_back("album"); for (unsigned int i = m_level; i<order.size(); i++) - p += order.Key(i)->Parts(true); + p += order.Key(i)->Parts(m_db,true); m_current_tracks = p.sql_select(false); m_tracks.clear (); - MYSQL_RES *rows = exec_sql (m_current_tracks); + MYSQL_RES *rows = m_db.exec_sql (m_current_tracks); if (rows) { MYSQL_ROW row; @@ -624,138 +522,7 @@ mgSelection::tracks () const } -mgContentItem::mgContentItem () -{ - m_id = -1; -} - -mgContentItem::mgContentItem (const mgContentItem* c) -{ - m_id = c->m_id; - m_title = c->m_title; - m_mp3file = c->m_mp3file; - m_artist = c->m_artist; - m_albumtitle = c->m_albumtitle; - m_genre1_id = c->m_genre1_id; - m_genre2_id = c->m_genre2_id; - m_genre1 = c->m_genre1; - m_genre2 = c->m_genre2; - m_bitrate = c->m_bitrate; - m_year = c->m_year; - m_rating = c->m_rating; - m_duration = c->m_duration; - m_samplerate = c->m_samplerate; - m_channels = c->m_channels; -} - -static char *mg_readline(FILE *f) -{ - static char buffer[MAXPARSEBUFFER]; - if (fgets(buffer, sizeof(buffer), f) > 0) { - int l = strlen(buffer) - 1; - if (l >= 0 && buffer[l] == '\n') - buffer[l] = 0; - return buffer; - } - return 0; -} - -static const char *FINDCMD = "cd '%s' 2>/dev/null && find -follow -name '%s' -print 2>/dev/null"; - -static string -GdFindFile( const char* tld, string mp3file ) -{ - string result = ""; - char *cmd = 0; - asprintf( &cmd, FINDCMD, tld, mp3file.c_str() ); - FILE *p = popen( cmd, "r" ); - if (p) - { - char *s; - if( (s = mg_readline(p) ) != 0) - result = string(s); - pclose(p); - } - - free( cmd ); - - return result; -} - -string -mgContentItem::getSourceFile(bool AbsolutePath) const -{ - const char* tld = the_setup.ToplevelDir; - string result=""; - if (AbsolutePath) result = tld; - if (the_setup.GdCompatibility) - result += GdFindFile(tld,m_mp3file); - else - result += m_mp3file; - return result; -} - -mgContentItem::mgContentItem (const mgSelection* sel,const MYSQL_ROW row) -{ - m_id = atol (row[0]); - if (row[1]) - m_title = row[1]; - else - m_title = "NULL"; - if (row[2]) - m_mp3file = row[2]; - else - m_mp3file = "NULL"; - if (row[3]) - m_artist = row[3]; - else - m_artist = "NULL"; - if (row[4]) - m_albumtitle = row[4]; - else - m_albumtitle = "NULL"; - if (row[5]) - { - m_genre1_id = row[5]; - m_genre1 = sel->value(keyGenres,row[5]); - } - else - m_genre1 = "NULL"; - if (row[6]) - { - m_genre2_id = row[6]; - m_genre2 = sel->value(keyGenres,row[6]); - } - else - m_genre2 = "NULL"; - if (row[7]) - m_bitrate = row[7]; - else - m_bitrate = "NULL"; - if (row[8]) - m_year = atol (row[8]); - else - m_year = 0; - if (row[9]) - m_rating = atol (row[9]); - else - m_rating = 0; - if (row[10]) - m_duration = atol (row[10]); - else - m_duration = 0; - if (row[11]) - m_samplerate = atol (row[11]); - else - m_samplerate = 0; - if (row[12]) - m_channels = atol (row[12]); - else - m_channels = 0; -}; - void mgSelection::InitSelection() { - setDB(0); m_Directory="."; m_level = 0; m_position = 0; @@ -777,7 +544,6 @@ void mgSelection::InitSelection() { mgSelection::mgSelection (const bool fall_through) { InitSelection (); - Connect(); m_fall_through = fall_through; } @@ -797,29 +563,42 @@ mgSelection::mgSelection (mgValmap& nv) } void -mgSelection::setDB(MYSQL *db) -{ - m_db = db; - order.setDB(db); -} - -void mgSelection::setOrder(mgOrder* o) { if (o) { order = *o; - order.setDB(m_db); } else mgWarning("mgSelection::setOrder(0)"); } + void mgSelection::InitFrom(mgValmap& nv) { + extern time_t createtime; + if (m_db.ServerConnected() && !m_db.Connected() + && (time(0)>createtime+10)) + { + char *b; + asprintf(&b,tr("Create database %s?"),the_setup.DbName); + if (Interface->Confirm(b)) + { + char *argv[2]; + argv[0]="."; + argv[1]=0; + m_db.Create(); + if (Interface->Confirm(tr("Import tracks?"))) + { + mgSync *s = new mgSync; + s->Sync(argv); + delete s; + } + } + free(b); + } InitSelection(); - Connect(); m_fall_through = nv.getbool("FallThrough"); m_Directory = nv.getstr("Directory"); while (m_level < nv.getuint("Level")) @@ -840,8 +619,6 @@ mgSelection::InitFrom(mgValmap& nv) mgSelection::~mgSelection () { - if (m_db) - mysql_close (m_db); } void mgSelection::InitFrom(const mgSelection* s) @@ -852,12 +629,10 @@ void mgSelection::InitFrom(const mgSelection* s) map_values = s->map_values; map_ids = s->map_ids; order = s->order; - order.setDB(m_db); m_level = s->m_level; m_position = s->m_position; m_trackid = s->m_trackid; m_tracks_position = s->m_tracks_position; - Connect(); setShuffleMode (s->getShuffleMode ()); setLoopMode (s->getLoopMode ()); } @@ -916,17 +691,16 @@ void mgSelection::refreshValues () const { assert(this); - if (!m_db) + if (!m_db.Connected()) return; if (m_current_values.empty()) { - mgOrder o1 = order; - mgParts p = order.Parts(m_level); + mgParts p = order.Parts(m_db,m_level); m_current_values = p.sql_select(); values.strings.clear (); m_ids.clear (); m_counts.clear(); - MYSQL_RES *rows = exec_sql (m_current_values); + MYSQL_RES *rows = m_db.exec_sql (m_current_values); if (rows) { unsigned int num_fields = mysql_num_fields(rows); @@ -962,63 +736,6 @@ mgSelection::count () const { return values.size (); } -void -mgSelection::Connect () -{ - if (m_db) - { - mysql_close (m_db); - setDB(0); - } - if (the_setup.DbHost == "") return; - setDB(mysql_init (0)); - if (!m_db) - return; - bool success; - if (the_setup.DbSocket != NULL) - { - mgDebug(1,"Using socket %s for connecting to Database %s as user %s.", - the_setup.DbSocket, - the_setup.DbName, - the_setup.DbUser); - mgDebug(3,"DbPassword is: '%s'",the_setup.DbPass); - success = (mysql_real_connect( m_db, - "", - the_setup.DbUser, - the_setup.DbPass, - the_setup.DbName, - 0, - the_setup.DbSocket, 0 ) != 0 ); - } - else - { - mgDebug(1,"Using TCP-%s for connecting to Database %s as user %s.", - the_setup.DbHost, - the_setup.DbName, - the_setup.DbUser); - mgDebug(3,"DbPassword is: '%s'",the_setup.DbPass); - success = ( mysql_real_connect( m_db, - the_setup.DbHost, - the_setup.DbUser, - the_setup.DbPass, - the_setup.DbName, - the_setup.DbPort, - 0, 0 ) != 0 ); - } - if (!success) - { - mgWarning("Failed to connect to host '%s' as User '%s', Password '%s': Error: %s", - the_setup.DbHost,the_setup.DbUser,the_setup.DbPass,mysql_error(m_db)); - mysql_close (m_db); - setDB(0); - } - if (!needGenre2_set && m_db) - { - needGenre2_set=true; - needGenre2=exec_count("SELECT COUNT(DISTINCT genre2) from tracks")>1; - } - return; -} bool mgSelection::enter (unsigned int position) @@ -1259,6 +976,10 @@ mgSelection::UsedBefore(mgOrder *o,const mgKeyTypes kt,unsigned int level) const return false; } +bool mgSelection::isLanguagelist() const +{ + return (order.getKeyType(0) == keyLanguage); +} bool mgSelection::isCollectionlist () const { @@ -1322,7 +1043,7 @@ mgSelection::loadvalues (mgKeyTypes kt) const if (map_ids.count(kt)>0) return true; map<string,string>& idmap = map_ids[kt]; - mgKey* k = ktGenerate(kt,m_db); + mgKey* k = ktGenerate(kt); if (k->map_idfield().empty()) { delete k; @@ -1331,7 +1052,7 @@ mgSelection::loadvalues (mgKeyTypes kt) const 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)); + MYSQL_RES *rows = m_db.exec_sql (string(b)); free(b); if (rows) { @@ -1355,7 +1076,6 @@ static vector<int> keycounts; unsigned int mgSelection::keycount(mgKeyTypes kt) { - assert(strlen(m_db->host)); if (keycounts.size()==0) { for (unsigned int ki=int(mgKeyTypesLow);ki<=int(mgKeyTypesHigh);ki++) @@ -1366,9 +1086,9 @@ mgSelection::keycount(mgKeyTypes kt) int& count = keycounts[int(kt-mgKeyTypesLow)]; if (count==-1) { - mgKey* k = ktGenerate(kt,m_db); - if (k->Enabled()) - count = exec_count(k->Parts(true).sql_count()); + mgKey* k = ktGenerate(kt); + if (k->Enabled(m_db)) + count = m_db.exec_count(k->Parts(m_db,true).sql_count()); else count = 0; delete k; @@ -1436,3 +1156,4 @@ mgSelection::choices(mgOrder *o,unsigned int level, unsigned int *current) } return result; } + @@ -1,6 +1,6 @@ /*! - * \file mg_db.h - * \brief A database interface to the GiantDisc + * \file mg_selection.h + * \brief A general interface to data items, currently only GiantDisc * * \version $Revision: 1.0 $ * \date $Date: 2004-12-07 10:10:35 +0200 (Tue, 07 Dec 2004) $ @@ -9,106 +9,23 @@ * */ -#ifndef _MG_DB_H -#define _MG_DB_H +#ifndef _MG_SELECTION_H +#define _MG_SELECTION_H #include <stdlib.h> -#include <mysql/mysql.h> #include <string> #include <list> #include <vector> #include <map> -#include <i18n.h> using namespace std; #include "mg_tools.h" #include "mg_valmap.h" #include "mg_order.h" +#include "mg_content.h" typedef vector<string> strvector; -class mgSelection; - -//! \brief represents a content item like an mp3 file. -class mgContentItem -{ - public: - mgContentItem (); - - string getKeyValue(mgKeyTypes kt); - string getKeyId(mgKeyTypes kt); - - //! \brief copy constructor - mgContentItem(const mgContentItem* c); - - //! \brief construct an item from an SQL row - mgContentItem (const mgSelection* sel, const MYSQL_ROW row); -//! \brief returns track id - long getId () const - { - return m_id; - } - -//! \brief returns title - string getTitle () const - { - return m_title; - } - -//! \brief returns filename - string getSourceFile (bool AbsolutePath=true) const; - -//! \brief returns artist - string getArtist () const - { - return m_artist; - } - -//! \brief returns the name of the album - string getAlbum () const; - -//! \brief returns the name of genre - string getGenre () const; - -//! \brief returns the bitrate - string getBitrate () const; - -//! \brief returns the file name of the album image - string getImageFile () const; - -//! \brief returns year - int getYear () const; - -//! \brief returns rating - int getRating () const; - -//! \brief returns duration - int getDuration () const; - -//! \brief returns samplerate - int getSampleRate () const; - -//! \brief returns # of channels - int getChannels () const; - - private: - long m_id; - string m_title; - string m_mp3file; - string m_artist; - string m_albumtitle; - string m_genre1_id; - string m_genre2_id; - string m_genre1; - string m_genre2; - string m_bitrate; - int m_year; - int m_rating; - int m_duration; - int m_samplerate; - int m_channels; -}; - /*! * \brief the only interface to the database. * Some member functions are declared const although they can modify the inner state of mgSelection. @@ -155,9 +72,6 @@ class mgSelection LM_FULL //!< \brief loop the whole track list }; -//! \brief escapes special characters - string sql_string(const string s) const; - /*! \brief the main constructor * \param fall_through if TRUE: If enter() returns a choice * containing only one item, that item is automatically entered. @@ -388,15 +302,6 @@ class mgSelection */ string exportM3U (); - /*! import/export tags like - * \par path can be a file or a directory. If directory, - * sync all files within but by default non recursive - * \par recursive recurse into all directories beneath path - * \par assorted see mugglei -h - * \par delete_missing if the file does not exist, delete the - * data base entry. If the file is unreadable, do not delete. - */ - void Sync(string path, bool recursive=false,bool assorted=false,bool delete_missing=false); /*! \brief go to a position in the current level. If we are at the * most detailled level this also sets the track position since @@ -476,6 +381,10 @@ class mgSelection */ bool isCollectionlist () const; +/*! \brief true if this selection currently selects a list of languages + */ + bool isLanguagelist () const; + //! \brief true if we have entered a collection bool inCollection(const string Name="") const; @@ -508,6 +417,7 @@ class mgSelection unsigned int keycount(mgKeyTypes kt); vector <const char *> choices(mgOrder *o,unsigned int level, unsigned int *current); unsigned int valcount (string val); + bool Connected() { return m_db.Connected(); } private: mutable map <mgKeyTypes, map<string,string> > map_values; @@ -526,15 +436,13 @@ class mgSelection ShuffleMode m_shuffle_mode; void Shuffle() const; LoopMode m_loop_mode; - MYSQL *m_db; - void setDB(MYSQL *db); + mutable mgmySql m_db; unsigned int m_level; long m_trackid; mgOrder order; bool UsedBefore (mgOrder *o,const mgKeyTypes kt, unsigned int level) const; void InitSelection (); - void Connect (); /*! \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. @@ -548,19 +456,9 @@ class mgSelection string ListFilename (); string m_Directory; void loadgenres (); - MYSQL_RES * exec_sql(string query) const; - string get_col0 (string query) const; void InitFrom(const mgSelection* s); -/*! \brief executes a query and returns the integer value from - * the first column in the first row. The query shold be a COUNT query - * returning only one row. - * \param query the SQL query to be executed - */ - unsigned long exec_count (string query) const; - - }; @@ -31,4 +31,6 @@ mgSetup::mgSetup () LimiterLevel = DEFAULT_LIMITER_LEVEL; Only48kHz = 0; ToplevelDir = "/mnt/music/"; + DbHost = "localhost"; + DbName = "GiantDisc"; } diff --git a/mg_sync.c b/mg_sync.c new file mode 100644 index 0000000..73f0bce --- /dev/null +++ b/mg_sync.c @@ -0,0 +1,335 @@ +/*! + * \file mg_sync.c + * \brief synchronization between SQL and filesystem + * + * \version $Revision: 1.0 $ + * \date $Date: 2004-12-07 10:10:35 +0200 (Tue, 07 Dec 2004) $ + * \author Wolfgang Rohdewald + * \author Responsible author: $Author: wr $ + * + */ + +#include "mg_sync.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fts.h> +#include <sys/time.h> +#include <time.h> + +#include <mpegfile.h> +#include <flacfile.h> +#include <id3v2tag.h> +#include <fileref.h> + +#include "mg_tools.h" +#include "mg_mysql.h" +#include "mg_setup.h" + +char * +mgSync::sql_Cstring(TagLib::String s,char *buf) +{ + return m_db.sql_Cstring(s.toCString(),buf); +} + +char * +mgSync::lower(char *s) +{ + char *p=s; + while (*p) + { + int i=(int)(*p); + (*p)=(char)tolower(i); + p++; + } + return s; +} + +TagLib::String +mgSync::getlanguage(const char *filename) +{ + TagLib::String result = ""; + TagLib::ID3v2::Tag * id3v2tag=0; + if (!strcmp(c_extension,"flac")) + { + TagLib::FLAC::File f(filename); + id3v2tag = f.ID3v2Tag(); + if (id3v2tag) + { + TagLib::ID3v2::FrameList l = id3v2tag->frameListMap()["TLAN"]; + if (!l.isEmpty()) + result = l.front()->toString(); + } + } + else if (!strcmp(c_extension,"mp3")) + { + TagLib::MPEG::File f(filename); + id3v2tag = f.ID3v2Tag(); + if (id3v2tag) + { + TagLib::ID3v2::FrameList l = id3v2tag->frameListMap()["TLAN"]; + if (!l.isEmpty()) + result = l.front()->toString(); + } + } + return result; +} + +char * +mgSync::getAlbum(const char *c_album,const char *c_artist,const char *c_directory) +{ + char * result; + char *b; + asprintf(&b,"SELECT cddbid FROM album" + " WHERE title=%s AND artist=%s",c_album,c_artist); + result=m_db.sql_Cstring(m_db.get_col0(b)); + free(b); + if (!strcmp(result,"'NULL'")) + { + const char *directory="substring(tracks.mp3file,1,length(tracks.mp3file)" + "-instr(reverse(tracks.mp3file),'/'))"; + char *where; + asprintf(&where,"WHERE tracks.sourceid=album.cddbid " + "AND %s=%s " + "AND album.title=%s", + directory,c_directory, + c_album); + + // how many artists will the album have after adding this one? + asprintf(&b,"SELECT distinct album.artist FROM tracks, album %s " + " union select %s",where,c_artist); + MYSQL_RES *rows = m_db.exec_sql (b); + free(b); + long new_album_artists = m_db.affected_rows(); + mysql_free_result(rows); + if (new_album_artists>1) // is the album multi artist? + { + asprintf(&b,"SELECT album.cddbid FROM tracks, album %s",where); + free(result); + result=m_db.sql_Cstring(m_db.get_col0(b)); + free(b); + asprintf(&b,"UPDATE album SET artist='Various Artists' WHERE cddbid=%s",result); + m_db.exec_sql(b); + // here we could change all tracks.sourceid to result and delete + // the other album entries for this album, but that should only + // be needed if a pre 0.1.4 import has been done incorrectly, so we + // don't bother + } + else + { // no usable album found + free(result); + asprintf(&result,"'%ld-%9s",random(),c_artist+1); + char *p=strchr(result,0)-1; + if (*p!='\'') + *p='\''; + asprintf(&b,"INSERT INTO album SET title=%s,artist=%s,cddbid=%s", + c_album,c_artist,result); + m_db.exec_sql(b); + free(b); + } + free(where); + } + return result; +} + +mgSync::mgSync() +{ + m_genre_rows=0; + if (!m_db.Connected()) + return; + m_genre_rows = m_db.exec_sql ("SELECT id,genre from genre"); + MYSQL_ROW rx; + while ((rx = mysql_fetch_row (m_genre_rows)) != 0) + m_Genres[rx[1]]=rx[0]; + // init random number generator + struct timeval tv; + struct timezone tz; + gettimeofday( &tv, &tz ); + srandom( tv.tv_usec ); +} + +mgSync::~mgSync() +{ + if (m_genre_rows) mysql_free_result(m_genre_rows); +} + +void +mgSync::UpdateTrack(long trackid) +{ + char sql[7000]; + char *c_cddbid=getAlbum(c_album,c_artist,c_directory); + sprintf(sql,"UPDATE tracks SET artist=%s, title=%s,year=%d,sourceid=%s," + "tracknb=%d,length=%d,bitrate=%d,samplerate=%d," + "channels=%d,genre1=%s,lang=%s WHERE id=%ld", + c_artist,c_title,year,c_cddbid, + trackno,len,bitrate,sample, + channels,c_genre1,c_lang,trackid); + free(c_cddbid); + m_db.exec_sql(sql); +} + +void +mgSync::AddTrack() +{ + char sql[7000]; + char *c_cddbid=getAlbum(c_album,c_artist,c_directory); + sprintf(sql,"INSERT INTO tracks SET artist=%s,title=%s,year=%u,sourceid=%s," + "tracknb=%u,mp3file=%s,length=%d,bitrate=%d,samplerate=%d," + "channels=%d,genre1=%s,genre2='',lang=%s," + "folder1=%s,folder2=%s,folder3=%s,folder4=%s", + c_artist,c_title,year,c_cddbid, + trackno,c_mp3file,len,bitrate,sample, + channels,c_genre1,c_lang, + c_folder1,c_folder2,c_folder3,c_folder4); + free(c_cddbid); + m_db.exec_sql(sql); +} + +bool +mgSync::GetFileInfo(const char *filename) +{ + TagLib::FileRef f( filename) ; + if (f.isNull()) + return false; + TagLib::Tag *tag = f.tag(); + if (!f.tag()) + return false; + if (tag->album()=="") + strcpy(c_album,"'Unassigned'"); + else + sql_Cstring(tag->album(),c_album); + sql_Cstring(tag->artist(),c_artist); + sql_Cstring(tag->title(),c_title); + sql_Cstring(filename,c_directory); + char *slash=strrchr(c_directory,'/'); + if (slash) + { + *slash='\''; + *(slash+1)=0; + } + const char *genrename=tag->genre().toCString(); + const char *genreid=m_Genres[genrename].c_str(); + sql_Cstring(genreid,c_genre1); + sql_Cstring(getlanguage(filename),c_lang); + trackno=tag->track(); + year=tag->year(); + TagLib::AudioProperties *ap = f.audioProperties(); + len = ap->length(); // tracks.length + bitrate = ap->bitrate(); // tracks.bitrate + sample = ap->sampleRate(); //tracks.samplerate + channels = ap->channels(); //tracks.channels + if (m_db.HasFolderFields()) + { + char *path = strdup(filename); + char *folder1=""; + char *folder2=""; + char *folder3=""; + char *folder4=""; + char *p=path; + char *slash; + slash=strchr(p,'/'); + if (slash) + { + folder1=p; + *slash=0; + p=slash+1; + slash=strchr(p,'/'); + if (slash) + { + folder2=p; + *slash=0; + p=slash+1; + slash=strchr(p,'/'); + if (slash) + { + folder3=p; + *slash=0; + p=slash+1; + slash=strchr(p,'/'); + if (slash) + { + folder4=p; + *slash=0; + } + } + } + } + sql_Cstring(folder1,c_folder1); + sql_Cstring(folder2,c_folder2); + sql_Cstring(folder3,c_folder3); + sql_Cstring(folder4,c_folder4); + free(path); + } + return true; +} + +void +mgSync::SyncFile(const char *filename) +{ + if (!strncmp(filename,"./",2)) // strip leading ./ + filename += 2; + if (strlen(filename)>255) + { + mgWarning("Length of file exceeds database field capacity: %s", filename); + return; + } + mgDebug(3,"Importing %s",filename); + sql_Cstring(filename,c_mp3file); + char sql[600]; + sprintf(sql,"SELECT id from tracks WHERE mp3file=%s",c_mp3file); + string s = m_db.get_col0(sql); + if (s!="NULL") + { + if (GetFileInfo(filename)) + UpdateTrack(atol(s.c_str())); + } + else + { + if (GetFileInfo(filename)) + AddTrack(); + } +} + +void +mgSync::Sync(char * const * path_argv, bool delete_missing) +{ + if (!m_db.Connected()) + return; + unsigned int count=0; + m_db.CreateFolderFields(); + chdir(the_setup.ToplevelDir); + FTS *fts; + FTSENT *ftsent; + fts = fts_open( path_argv, FTS_LOGICAL, 0); + if (fts) + { + while ( (ftsent = fts_read(fts)) != NULL) + { + if (!((ftsent->fts_statp->st_mode)||S_IFREG)) + continue; + char *extension = strrchr(ftsent->fts_path,'.'); + if (!extension) + continue; + strcpy(c_extension,extension+1); + lower(c_extension); + if (!strcmp(c_extension,"flac") || !strcmp(c_extension,"ogg") || !strcmp(c_extension,"mp3")) + { + SyncFile(ftsent->fts_path); + count++; + if (count%1000==0) + { + extern void showimportcount(unsigned int); + showimportcount(count); + } + } + } + fts_close(fts); + } +} + +void +mgSync::Create() +{ + m_db.Create(); +} diff --git a/mg_sync.h b/mg_sync.h new file mode 100644 index 0000000..2337c69 --- /dev/null +++ b/mg_sync.h @@ -0,0 +1,71 @@ +/*! + * \file mg_sync.h + * \brief synchronization between SQL and filesystem + * + * \version $Revision: 1.0 $ + * \date $Date: 2004-12-07 10:10:35 +0200 (Tue, 07 Dec 2004) $ + * \author Wolfgang Rohdewald + * \author Responsible author: $Author: wr $ + * + */ + +#ifndef _MG_SYNC_H +#define _MG_SYNC_H + +#include <map> +#include <string.h> +#include <tag.h> + +#include "mg_mysql.h" + +class mgSync +{ + public: + mgSync(); + ~mgSync(); + //! \brief drop and create the data base GiantDisc + void Create(); + + /*! \brief import/export tags like + * \par path can be a file or a directory. If directory, + * sync all files within + * \par assorted see mugglei -h + * \par delete_missing if the file does not exist, delete the + * data base entry. If the file is unreadable, do not delete. + */ + void Sync(char * const * path_argv, bool delete_missing=false); + + private: + mgmySql m_db; + char *sql_Cstring(TagLib::String s,char *buf=0); + char *lower(char *s); + TagLib::String getlanguage(const char *filename); + char * getAlbum(const char *c_album,const char *c_artist,const char *c_directory); + bool GetFileInfo(const char *filename); + void AddTrack(); + void UpdateTrack(long trackid); + void SyncFile(const char *filename); + map<string,string> m_Genres; + MYSQL_RES* m_genre_rows; + + char c_album[520]; // at least 256 * 2 + 2 for VARCHAR(255), see sql_string() + char c_artist[520]; + char c_title[520]; + char c_directory[520]; + char c_mp3file[520]; + char c_genre1[520]; + char c_lang[520]; + char c_folder1[520]; + char c_folder2[520]; + char c_folder3[520]; + char c_folder4[520]; + char c_extension[300]; + unsigned int trackno; + unsigned int year; + int len; + int bitrate; + int sample; + int channels; +}; + +#endif @@ -84,7 +84,7 @@ mgWarning (const char *fmt, ...) isyslog ("Warning: %s\n", buffer); #endif } - extern void showmessage(const char*); + extern void showmessage(const char*,int duration=0); showmessage(buffer); va_end (ap); } @@ -15,13 +15,12 @@ #include "vdr_menu.h" #include "vdr_setup.h" #include "mg_tools.h" -#include "mg_db.h" #include "i18n.h" #include <getopt.h> #include <config.h> -static const char *VERSION = "0.1.3"; +static const char *VERSION = "0.1.4"; static const char *DESCRIPTION = "Media juggle plugin for VDR"; static const char *MAINMENUENTRY = "Muggle"; @@ -58,10 +57,18 @@ mgMuggle::mgMuggle (void) the_setup.DbPass = strdup (""); the_setup.GdCompatibility = false; the_setup.ToplevelDir = strdup ("/mnt/music/"); +#ifndef HAVE_SERVER + char *buf; + asprintf(&buf,"%s/.muggle",getenv("HOME")); + set_datadir(buf); + free(buf); +#endif } +#if VDRVERSNUM >= 0321 -mgMuggle::~mgMuggle () +void +mgMuggle::Stop (void) { if (main) main->SaveState(); free(the_setup.DbHost); @@ -71,6 +78,7 @@ mgMuggle::~mgMuggle () free(the_setup.ToplevelDir); } +#endif const char * mgMuggle::CommandLineHelp (void) @@ -84,12 +92,17 @@ mgMuggle::CommandLineHelp (void) " -u UUUU, --user=UUUU specify database user (default is )\n" " -w WWWW, --password=WWWW specify database password (default is empty)\n" " -t TTTT, --toplevel=TTTT specify toplevel directory for music (default is /mnt/music)\n" - " -g, --giantdisc enable full Giantdisc compatibility mode\n"; +#ifndef HAVE_SERVER + " -d DIRN, --datadir=DIRN specify directory for embedded sql data (default is $HOME/.muggle)\n" +#endif + " -g, --giantdisc enable full Giantdisc compatibility mode\n" + " -v, --verbose specify debug level. The higher the more. Default is 1\n"; } bool mgMuggle::ProcessArgs (int argc, char *argv[]) { + mgSetDebugLevel (1); mgDebug (1, "mgMuggle::ProcessArgs"); // Implement command line argument processing here if applicable. @@ -102,6 +115,9 @@ bool mgMuggle::ProcessArgs (int argc, char *argv[]) {"port", required_argument, NULL, 'p'}, {"user", required_argument, NULL, 'u'}, {"password", required_argument, NULL, 'w'}, +#ifndef HAVE_SERVER + {"datadir", required_argument, NULL, 'd'}, +#endif {"toplevel", required_argument, NULL, 't'}, {"giantdisc", no_argument, NULL, 'g'}, {NULL} @@ -111,7 +127,11 @@ bool mgMuggle::ProcessArgs (int argc, char *argv[]) c, option_index = 0; while ((c = - getopt_long (argc, argv, "gh:s:n:p:t:u:w:", long_options, +#ifndef HAVE_SERVER + getopt_long (argc, argv, "gh:s:n:p:t:u:w:d:v:", long_options, +#else + getopt_long (argc, argv, "gh:s:n:p:t:u:w:v:", long_options, +#endif &option_index)) != -1) { switch (c) @@ -146,6 +166,18 @@ bool mgMuggle::ProcessArgs (int argc, char *argv[]) the_setup.DbPass = strcpyrealloc (the_setup.DbPass, optarg); } break; +#ifndef HAVE_SERVER + case 'd': + { + set_datadir(optarg); + } + break; +#endif + case 'v': + { + mgSetDebugLevel (atol(optarg)); + } + break; case 't': { if (optarg[strlen (optarg) - 1] != '/') @@ -185,7 +217,6 @@ bool mgMuggle::Initialize (void) bool mgMuggle::Start (void) { // Start any background activities the plugin shall perform. - mgSetDebugLevel (99); RegisterI18n (Phrases); return true; } diff --git a/muggle.doxygen b/muggle.doxygen index 3a1ee60..8c917e2 100644 --- a/muggle.doxygen +++ b/muggle.doxygen @@ -23,7 +23,7 @@ PROJECT_NAME = Muggle media plugin # This could be handy for archiving the generated documentation or # if some version control system is used. -PROJECT_NUMBER = 0.1.3 +PROJECT_NUMBER = 0.1.4 # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. @@ -45,8 +45,6 @@ class mgMuggle:public cPlugin mgMuggle (void); - virtual ~ mgMuggle (); - virtual const char *Version (void); virtual const char *Description (void); @@ -59,6 +57,8 @@ class mgMuggle:public cPlugin virtual bool Start (void); + virtual void Stop (void); + virtual void Housekeeping (void); virtual const char *MainMenuEntry (void); @@ -31,705 +31,32 @@ #include <fileref.h> #include "mg_tools.h" +#include "mg_setup.h" +#include "mg_sync.h" -MYSQL *db; -static char *server_args[] = -{ - "this_program", /* this string is not used */ - "--datadir=.", - "--key_buffer_size=32M" -}; - -static char *server_groups[] = -{ - "embedded", - "server", - "this_program_SERVER", - (char *)NULL -}; - -char *db_cmds[] = -{ - "DROP DATABASE IF EXISTS GiantDisc; CREATE DATABASE GiantDisc;", - "grant all privileges on GiantDisc.* to vdr@localhost;", - "use GiantDisc;", - "drop table if exists album; CREATE TABLE album ( " - "artist varchar(255) default NULL, " - "title varchar(255) default NULL, " - "cddbid varchar(20) NOT NULL default '', " - "coverimg varchar(255) default NULL, " - "covertxt mediumtext, " - "modified date default NULL, " - "genre varchar(10) default NULL, " - "PRIMARY KEY (cddbid), " - "KEY artist (artist(10)), " - "KEY title (title(10)), " - "KEY genre (genre), " - "KEY modified (modified)) " - "TYPE=MyISAM;", - "drop table if exists genre; CREATE TABLE genre (" - "id varchar(10) NOT NULL default '', " - "id3genre smallint(6) default NULL, " - "genre varchar(255) default NULL, " - "freq int(11) default NULL, " - "PRIMARY KEY (id)) " - "TYPE=MyISAM;", - "drop table if exists language; CREATE TABLE language (" - "id varchar(4) NOT NULL default '', " - "language varchar(40) default NULL, " - "freq int(11) default NULL, " - "PRIMARY KEY (id)) " - "TYPE=MyISAM;", - "drop table if exists musictype; CREATE TABLE musictype (" - "musictype varchar(40) default NULL, " - "id tinyint(3) unsigned NOT NULL auto_increment, " - "PRIMARY KEY (id)) " - "TYPE=MyISAM;", - "drop table if exists player;CREATE TABLE player ( " - "ipaddr varchar(255) NOT NULL default '', " - "uichannel varchar(255) NOT NULL default '', " - "logtarget int(11) default NULL, " - "cdripper varchar(255) default NULL, " - "mp3encoder varchar(255) default NULL, " - "cdromdev varchar(255) default NULL, " - "cdrwdev varchar(255) default NULL, " - "id int(11) NOT NULL default '0', " - "PRIMARY KEY (id)) " - "TYPE=MyISAM;", - "drop table if exists playerstate;CREATE TABLE playerstate ( " - "playerid int(11) NOT NULL default '0', " - "playertype int(11) NOT NULL default '0', " - "snddevice varchar(255) default NULL, " - "playerapp varchar(255) default NULL, " - "playerparams varchar(255) default NULL, " - "ptlogger varchar(255) default NULL, " - "currtracknb int(11) default NULL, " - "state varchar(4) default NULL, " - "shufflepar varchar(255) default NULL, " - "shufflestat varchar(255) default NULL, " - "pauseframe int(11) default NULL, " - "framesplayed int(11) default NULL, " - "framestotal int(11) default NULL, " - "anchortime bigint(20) default NULL, " - "PRIMARY KEY (playerid,playertype)) " - "TYPE=HEAP;", - "drop table if exists playlist;CREATE TABLE playlist ( " - "title varchar(255) default NULL, " - "author varchar(255) default NULL, " - "note varchar(255) default NULL, " - "created timestamp(8) NOT NULL, " - "id int(10) unsigned NOT NULL auto_increment, " - "PRIMARY KEY (id)) " - "TYPE=MyISAM;", - "drop table if exists playlistitem;CREATE TABLE playlistitem ( " - "playlist int(11) NOT NULL default '0', " - "tracknumber mediumint(9) NOT NULL default '0', " - "trackid int(11) default NULL, " - "PRIMARY KEY (playlist,tracknumber)) " - "TYPE=MyISAM;", - "drop table if exists playlog;CREATE TABLE playlog ( " - "trackid int(11) default NULL, " - "played date default NULL, " - "id tinyint(3) unsigned NOT NULL auto_increment, " - "PRIMARY KEY (id)) " - "TYPE=MyISAM;", - "drop table if exists recordingitem;CREATE TABLE recordingitem ( " - "trackid int(11) default NULL, " - "recdate date default NULL, " - "rectime time default NULL, " - "reclength int(11) default NULL, " - "enddate date default NULL, " - "endtime time default NULL, " - "repeat varchar(10) default NULL, " - "initcmd varchar(255) default NULL, " - "parameters varchar(255) default NULL, " - "atqjob int(11) default NULL, " - "id int(11) NOT NULL default '0', " - "PRIMARY KEY (id)) " - "TYPE=MyISAM;", - "drop table if exists source; CREATE TABLE source ( " - "source varchar(40) default NULL, " - "id tinyint(3) unsigned NOT NULL auto_increment, " - "PRIMARY KEY (id)) " - "TYPE=MyISAM;", - "drop table if exists tracklistitem;CREATE TABLE tracklistitem ( " - "playerid int(11) NOT NULL default '0', " - "listtype smallint(6) NOT NULL default '0', " - "tracknb int(11) NOT NULL default '0', " - "trackid int(11) NOT NULL default '0', " - "PRIMARY KEY (playerid,listtype,tracknb)) " - "TYPE=MyISAM;", - "drop table if exists tracks;CREATE TABLE tracks ( " - "artist varchar(255) default NULL, " - "title varchar(255) default NULL, " - "genre1 varchar(10) default NULL, " - "genre2 varchar(10) default NULL, " - "year smallint(5) unsigned default NULL, " - "lang varchar(4) default NULL, " - "type tinyint(3) unsigned default NULL, " - "rating tinyint(3) unsigned default NULL, " - "length smallint(5) unsigned default NULL, " - "source tinyint(3) unsigned default NULL, " - "sourceid varchar(20) default NULL, " - "tracknb tinyint(3) unsigned default NULL, " - "mp3file varchar(255) default NULL, " - "condition tinyint(3) unsigned default NULL, " - "voladjust smallint(6) default '0', " - "lengthfrm mediumint(9) default '0', " - "startfrm mediumint(9) default '0', " - "bpm smallint(6) default '0', " - "lyrics mediumtext, " - "bitrate varchar(10) default NULL, " - "created date default NULL, " - "modified date default NULL, " - "backup tinyint(3) unsigned default NULL, " - "samplerate int(7) unsigned default NULL, " - "channels tinyint(3) unsigned default NULL, " - "id int(11) NOT NULL auto_increment, " - "folder1 varchar(255), " - "folder2 varchar(255), " - "folder3 varchar(255), " - "folder4 varchar(255), " - "PRIMARY KEY (id), " - "KEY title (title(10)), " - "KEY mp3file (mp3file(10)), " - "KEY genre1 (genre1), " - "KEY genre2 (genre2), " - "KEY year (year), " - "KEY lang (lang), " - "KEY type (type), " - "KEY rating (rating), " - "KEY sourceid (sourceid), " - "KEY artist (artist(10))) " - "TYPE=MyISAM;" -}; +using namespace std; -bool folderfields; +int SysLogLevel = 1; -std::string host, user, pass, dbname, sck; bool import_assorted, delete_mode, create_mode; -#define MAX_QUERY_BUFLEN 2048 -static char querybuf[MAX_QUERY_BUFLEN]; - -void showmessage(const char *msg) +void showmessage(const char *msg,int duration) { } -void -init_folderfields() -{ - folderfields=false; - mysql_query(db,"DESCRIBE tracks folder1"); - MYSQL_RES *rows = mysql_store_result(db); - if (rows) - { - folderfields = mysql_num_rows(rows)>0; - mysql_free_result(rows); - if (!folderfields) - { - folderfields = !mysql_query(db, - "alter table tracks add column folder1 varchar(255)," - "add column folder2 varchar(255)," - "add column folder3 varchar(255)," - "add column folder4 varchar(255)"); - - } - } -} - -MYSQL_RES* mgSqlReadQuery(MYSQL *db, const char *fmt, ...) +void showimportcount(unsigned int count) { - va_list ap; - va_start( ap, fmt ); - vsnprintf( querybuf, MAX_QUERY_BUFLEN-1, fmt, ap ); - - if( mysql_query(db, querybuf) ) - { - mgError( "SQL error in MUGGLE:\n%s: %s\n", querybuf,mysql_error(db) ); - } - - MYSQL_RES *result = mysql_store_result(db); - - va_end(ap); - return result; } -void mgSqlWriteQuery(MYSQL *db, const char *fmt, ...) +const char *I18nTranslate(const char *s,const char *Plugin) { - va_list ap; - va_start(ap, fmt); - vsnprintf(querybuf, MAX_QUERY_BUFLEN-1, fmt, ap); - - if( mysql_query(db, querybuf) ) - { - mgError( "SQL error in MUGGLE:\n%s %s\n", querybuf,mysql_error(db) ); - } - - va_end(ap); -} - -int init_database() -{ - db = mysql_init(0); // NULL? - - if( db == NULL ) - { - std::cout << "mysql_init failed." << std::endl; - return -1; - } - - // check for use of sockets - if( sck != "" ) - { - if( mysql_real_connect( db, NULL, user.c_str(), pass.c_str(), dbname.c_str(), - 0, sck.c_str(), 0 ) == NULL ) - - { - std::cout << "mysql_real_connect using sockets failed." << std::endl; - return -2; - } - } - else - { - if( mysql_real_connect( db, host.c_str(), user.c_str(), pass.c_str(), dbname.c_str(), - 0, NULL, 0 ) == NULL ) - { - std::cout << "mysql_real_connect via TCP failed." << std::endl; - return -2; - } - } - - return 0; -} - -MYSQL_RES * -exec_sql( std::string query ) -{ - 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); -} - -int create_database() -{ - // create database and tables - int len = sizeof( db_cmds ) / sizeof( char* ); - for( int i=0; i < len; i ++ ) - { - exec_sql( std::string( db_cmds[i] ) ); - } - return 0; -} - -time_t get_fs_modification_time( std::string filename ) -{ - struct stat *buf = (struct stat*) malloc( sizeof( struct stat ) ); - time_t mod = 0; - - // yes: obtain modification date for file and db entry - if( !stat( filename.c_str(), buf ) ) - { - mod = buf->st_mtime; - free( buf ); - } - - return mod; -} - -time_t get_db_modification_time( long uid ) -{ - time_t mt = 0; - - MYSQL_RES *result = mgSqlReadQuery( db, "SELECT UNIX_TIMESTAMP(modification_time) " - "FROM tracks WHERE id=\"%d\"", uid ); - if( mysql_num_rows(result) ) - { - MYSQL_ROW row = mysql_fetch_row( result ); - - std::string mod_time = row[0]; - mt = (time_t) atol( mod_time.c_str() ); - } - - return mt; -} - -TagLib::String escape_string( MYSQL *db, TagLib::String s ) -{ - char *buf = strdup( s.toCString() ); - char *escbuf = (char *) malloc( 2*strlen( buf ) + 1 ); - - mysql_real_escape_string( db, escbuf, s.toCString(), s.size() ); - TagLib::String r = TagLib::String( escbuf ); - - free( escbuf ); - free( buf); - - return r; -} - -long find_file_in_database( MYSQL *db, std::string filename ) -{ - long uid = -1; - - TagLib::String file = TagLib::String( filename.c_str() ); - file = escape_string( db, file ); - - MYSQL_RES *result = mgSqlReadQuery( db, "SELECT id FROM tracks WHERE mp3file=\"%s\"", file.toCString() ); - if( mysql_num_rows(result) ) - { - MYSQL_ROW row = mysql_fetch_row( result ); - uid = atol( row[0] ); - } - - // obtain ID and return - return uid; -} - -TagLib::String find_genre_id( TagLib::String genre ) -{ - TagLib::String id = ""; - - if( genre.size() ) - { - MYSQL_RES *result = mgSqlReadQuery( db, "SELECT id FROM genre WHERE " - "genre=\"%s\"", genre.toCString() ); - - if( mysql_num_rows(result) ) - { - MYSQL_ROW row = mysql_fetch_row( result ); - - id = row[0]; - } - } - - return id; -} - -// read tags from the mp3 file and store them into the corresponding database entry -void update_db( long uid, std::string filename ) -{ - TagLib::String title, album, artist, genre, cddbid, language; - uint trackno, year; - - // ID3_Tag filetag( filename.c_str() ); - TagLib::FileRef f( filename.c_str() ); - - if( !f.isNull() && f.tag() ) - { - // std::cout << "Evaluating " << filename << std::endl; - TagLib::Tag *tag = f.tag(); - - // obtain tag information - title = tag->title(); - album = tag->album(); - year = tag->year(); - artist = tag->artist(); - trackno = tag->track(); - genre = tag->genre(); - language = ""; - TagLib::ID3v2::Tag * id3v2tag=0; - if (filename.substr(filename.size()-5)==".flac") - { - TagLib::FLAC::File f(filename.c_str()); - id3v2tag = f.ID3v2Tag(); - if (id3v2tag) - { - TagLib::ID3v2::FrameList l = id3v2tag->frameListMap()["TLAN"]; - if (!l.isEmpty()) - language = l.front()->toString(); - } - } - else if (filename.substr(filename.size()-4)==".mp3") - { - TagLib::MPEG::File f(filename.c_str()); - id3v2tag = f.ID3v2Tag(); - if (id3v2tag) - { - TagLib::ID3v2::FrameList l = id3v2tag->frameListMap()["TLAN"]; - if (!l.isEmpty()) - language = l.front()->toString(); - } - } - - TagLib::String gid = find_genre_id( genre ); - - TagLib::AudioProperties *ap = f.audioProperties(); - int len = ap->length(); // tracks.length - int bitrate = ap->bitrate(); // tracks.bitrate - int sample = ap->sampleRate(); //tracks.samplerate - int channels = ap->channels(); //tracks.channels - - title = escape_string( db, title ); - album = escape_string( db, album ); - artist = escape_string( db, artist ); - - // TODO: CD identifier (if it exists), playcounter, popularimeter (rating?), volume adjustment, lyrics, cover - - // finally update the database - - // obtain associated album or create - if( album == "" ) - { // no album found, create default album for artist - MYSQL_RES *result = mgSqlReadQuery( db, "SELECT cddbid FROM album WHERE title=\"Unassigned\" AND artist=\"%s\"", artist.toCString() ); - MYSQL_ROW row = mysql_fetch_row( result ); - - // Default album does not yet exist (num rows == 0) - int nrows = mysql_num_rows(result); - if( nrows == 0 ) - { - // create new album entry "Unassigned" for this artist - long id = random(); - char *buf; - asprintf( &buf, "%ld-%s", id, tag->artist().toCString() ); - cddbid = TagLib::String( buf ).substr( 0, 20 ); - cddbid = escape_string( db, cddbid ); - free( buf ); - - mgSqlWriteQuery( db, - "INSERT INTO album (artist, title, cddbid) " - "VALUES (\"%s\", \"Unassigned\", \"%s\")", - artist.toCString(), cddbid.toCString() ); - } - else - { // use first album found as source id for the track - cddbid = escape_string(db,row[0]); - } - } - else - { // album tag found, associate or create - MYSQL_RES *result; - if( import_assorted ) - { // lookup an existing album by title only (artist should be "Various Artists" - result = mgSqlReadQuery( db, "SELECT cddbid FROM album WHERE title=\"%s\" AND artist=\"Various Artists\"", - album.toCString(), artist.toCString() ); - } - else - { - result = mgSqlReadQuery( db, "SELECT cddbid FROM album WHERE title=\"%s\" AND artist=\"%s\"", - album.toCString(), artist.toCString() ); - } - MYSQL_ROW row = mysql_fetch_row( result ); - - // num rows == 0 ? - int nrows = mysql_num_rows(result); - if( nrows == 0 ) - { - // create new album entry - long id = random(); - char *buf; - asprintf( &buf, "%ld-%s", id, tag->album().toCString() ); - cddbid = TagLib::String( buf ).substr( 0, 20 ); - cddbid = escape_string( db, cddbid ); - free( buf ); - - if( import_assorted ) - { // in this case, the album author is "Various Artists" - mgSqlWriteQuery( db, - "INSERT INTO album (artist,title,cddbid) " - "VALUES (\"Various Artists\", \"%s\", \"%s\")", - album.toCString(), cddbid.toCString() ); - } - else - { - mgSqlWriteQuery( db, - "INSERT INTO album (artist,title,cddbid) " - "VALUES (\"%s\", \"%s\", \"%s\")", - artist.toCString(), album.toCString(), cddbid.toCString() ); - } - } - else - { // use first album found as source id for the track - cddbid = escape_string(db,row[0]); - } - } - - // update tracks table - if( uid > 0 ) - { // the entry is known to exist already, hence update it - - mgSqlWriteQuery( db, "UPDATE tracks SET artist=\"%s\", title=\"%s\", year=\"%d\"," - "sourceid=\"%s\", mp3file=\"%s\", length=%d, bitrate=\"%d\"," - "samplerate=%d, channels=%d, genre1=\"%s\", lang=\"%s\" WHERE id=%d", - artist.toCString(), title.toCString(), year, - cddbid.toCString(), filename.c_str(), len, bitrate, - sample, channels, gid.toCString(), language.toCString(), uid ); - } - else - { // the entry does not exist, create it - if (folderfields) - { - char *path = strdup(filename.c_str()); - char *folder1=""; - char *folder2=""; - char *folder3=""; - char *folder4=""; - char *p=path; - char *slash; - slash=strchr(p,'/'); - if (slash) - { - folder1=p; - *slash=0; - p=slash+1; - slash=strchr(p,'/'); - if (slash) - { - folder2=p; - *slash=0; - p=slash+1; - slash=strchr(p,'/'); - if (slash) - { - folder3=p; - *slash=0; - p=slash+1; - slash=strchr(p,'/'); - if (slash) - { - folder4=p; - *slash=0; - } - } - } - } - TagLib::String f1 = escape_string( db, folder1 ); - TagLib::String f2 = escape_string( db, folder2 ); - TagLib::String f3 = escape_string( db, folder3 ); - TagLib::String f4 = escape_string( db, folder4 ); - mgSqlWriteQuery( db, - "INSERT INTO tracks " - "(artist, title, year,sourceid,tracknb,mp3file,length,bitrate,samplerate,channels,genre1,genre2,lang,folder1,folder2,folder3,folder4) VALUES" - "(\"%s\", \"%s\", %d, \"%s\", %d, \"%s\", %d, \"%d\", %d, %d, \"%s\",\"\",\"%s\"," - "\"%s\",\"%s\",\"%s\",\"%s\")", - artist.toCString(), title.toCString(), year, cddbid.toCString(), - trackno, filename.c_str(), len, bitrate, sample, channels, gid.toCString(), - language.toCString(),f1.toCString(),f2.toCString(),f3.toCString(),f4.toCString()); - free(path); - } - else - mgSqlWriteQuery( db, - "INSERT INTO tracks " - "(artist, title, year,sourceid,tracknb,mp3file,length,bitrate,samplerate,channels,genre1,genre2,lang) VALUES" - "(\"%s\", \"%s\", %d, \"%s\", %d, \"%s\", %d, \"%d\", %d, %d, \"%s\",\"\",\"%s\")", - artist.toCString(), title.toCString(), year, cddbid.toCString(), - trackno, filename.c_str(), len, bitrate, sample, channels, gid.toCString(), - language.toCString()); - -#ifdef VERBOSE - std::cout << "-- TAG --" << std::endl; - std::cout << "title - '" << tag->title() << "'" << std::endl; - std::cout << "artist - '" << tag->artist() << "'" << std::endl; - std::cout << "album - '" << tag->album() << "'" << std::endl; - std::cout << "year - '" << tag->year() << "'" << std::endl; - std::cout << "comment - '" << tag->comment() << "'" << std::endl; - std::cout << "track - '" << tag->track() << "'" << std::endl; - std::cout << "genre - '" << tag->genre() << "'" << std::endl; - std::cout << "language- '" << language << "'" << std::endl; -#endif - } - } -} - -void update_tags( long uid ) -{ - MYSQL_RES *result; - MYSQL_ROW row; - - if( uid >= 0 ) - { - result = mgSqlReadQuery( db, "SELECT artist,title,year,tracknb,mp3file,genre1,id FROM tracks where id=%d", uid ); - } - else - { - result = mgSqlReadQuery( db, "SELECT artist,title,year,tracknb,mp3file,genre1,id FROM tracks" ); - } - - // loop all results - char* cwd = getcwd( NULL, 0 ); - std::string wdir = std::string( cwd ); - free( cwd ); - - struct stat *buf = (struct stat*) malloc( sizeof( struct stat ) ); - while(( row = mysql_fetch_row(result) ) != NULL ) - { - - std::string file = wdir + "/" + std::string( row[4] ); - - if( !stat( file.c_str(), buf ) ) - { - // set tags? - /* - std::string artist = row[0]; - std::string title = row[1]; - int year = atoi( row[2] ); - int track = atoi( row[3] ); - std::string genre = row[5]; - */ - } - else - { - if( delete_mode ) - { -#ifdef VERBOSE - std::cout << "Deleting entry " << row[6] << " from database because the file no longer exists." << std::endl; -#endif - mgSqlWriteQuery( db, "DELETE FROM tracks where id=%s", row[6] ); - } - } - } - free( buf ); -} - -void evaluate_file( std::string filename ) -{ - // is filename stored in database? - long uid = find_file_in_database( db, filename ); - if( uid >= 0 ) - { - // currently only update database, do not consider writing changes from the db back to tags - /* - // determine modification times in database and on filesystem - time_t db_time = get_db_modification_time( uid ); - time_t fs_time = get_fs_modification_time( filename ); - - if( db_time > fs_time ) - { - // db is newer: update id3 tags from db - update_tags( uid, filename ); - } - else - { - // file is newer: update db from id3 tags - update_db( uid, filename ); - } - */ - - update_db( uid, filename ); - } - else - { - // not in db yet: import file - update_db( -1, filename ); - } + return s; } int main( int argc, char *argv[] ) { - if( mysql_server_init(sizeof(server_args) / sizeof(char *), - server_args, server_groups) ) - { - exit(1); - } - std::string filename; + mgSetDebugLevel(1); if( argc < 2 ) { // we need at least a filename! @@ -737,35 +64,49 @@ int main( int argc, char *argv[] ) std::cout << "(C) Lars von Wedel" << std::endl; std::cout << "This is free software; see the source for copying conditions." << std::endl; std::cout << "" << std::endl; + std::cout << "Usage: mugglei [OPTION]... [FILE]..." << std::endl; + std::cout << "" << std::endl; + std::cout << " all FILE arguments will be imported. If they are directories, their content is imported"<< std::endl; + std::cout << "" << std::endl; + std::cout << "Only files ending in .flac, .mp3, .ogg (ignoring case) will be imported" << std::endl; + std::cout << "" << std::endl; std::cout << "Options:" << std::endl; std::cout << " -h <hostname> - specify host of mySql database server (default is 'localhost')" << std::endl; std::cout << " -s <socket> - specify a socket for mySQL communication (default is TCP)" << std::endl; std::cout << " -n <database> - specify database name (default is 'GiantDisc')" << std::endl; std::cout << " -u <username> - specify user of mySql database (default is empty)" << std::endl; std::cout << " -p <password> - specify password of user (default is empty password)" << std::endl; - std::cout << " -f <filename> - name of music file to import or update" << std::endl; - std::cout << " -a - import track as if it was on an assorted album" << std::endl; + std::cout << " -t <topleveldir> - name of music top level directory" << std::endl; std::cout << " -z - scan all database entries and delete entries for files not found" << std::endl; - std::cout << " -c - create a new database entry deleting existing entries" << std::endl; + std::cout << " -z is not yet implemented" << std::endl; + std::cout << " -c - delete the entire database and recreate a new empty one" << std::endl; +#ifndef HAVE_SERVER + std::cout << " -d <datadir> - the data directory for the embedded mysql server. Defaults to ./.muggle" << std::endl; +#endif + std::cout << " -v - the wanted log level, the higher the more. Default is 1" << std::endl; exit( 1 ); } // option defaults - host = "localhost"; - dbname = "GiantDisc"; - user = ""; - pass = ""; - sck = ""; import_assorted = false; delete_mode = false; create_mode = false; - filename = ""; +#ifndef HAVE_SERVER + char *buf; + asprintf(&buf,"%s/.muggle",getenv("HOME")); + set_datadir(buf); + free(buf); +#endif // parse command line options while( 1 ) { - int c = getopt(argc, argv, "h:u:p:n:af:s:z"); +#ifndef HAVE_SERVER + int c = getopt(argc, argv, "h:s:n:u:p:t:zcv:d:"); +#else + int c = getopt(argc, argv, "h:s:n:u:p:t:zcv:"); +#endif if (c == -1) break; @@ -778,32 +119,28 @@ int main( int argc, char *argv[] ) } break; case 'h': { - host = optarg; + the_setup.DbHost = optarg; } break; case 'n': { - dbname = optarg; + the_setup.DbName = optarg; } break; case 'u': { - user = optarg; + the_setup.DbUser = optarg; } break; case 'p': { - pass = optarg; + the_setup.DbPass = optarg; } break; - case 'a': + case 's': { - import_assorted = true; + the_setup.DbSocket = optarg; } break; - case 'f': + case 't': { - filename = optarg; + the_setup.ToplevelDir = optarg; } break; - case 's': - { - sck = optarg; - } break; case 'z': { delete_mode = true; @@ -812,39 +149,25 @@ int main( int argc, char *argv[] ) { create_mode = true; } break; + case 'v': + { + mgSetDebugLevel(atol(optarg)); + } break; +#ifndef HAVE_SERVER + case 'd': + { + set_datadir(optarg); + } break; +#endif } } - - if( filename.length() > 255 ) - { - std::cerr << "Warning: length of file exceeds database field capacity: " << filename << std::endl; - } - - // init random number generator - struct timeval tv; - struct timezone tz; - gettimeofday( &tv, &tz ); - srandom( tv.tv_usec ); - - if( 0 == init_database() ) - { - init_folderfields(); - if( delete_mode ) - { - update_tags( -1 ); - } - else if( create_mode ) - { - create_database(); - } - if( filename.size() ) - { - evaluate_file( filename ); - } - } - - mysql_server_end(); - + mgSync *sync = new mgSync; // because we want to delete it before database_end + if (create_mode) + sync->Create(); + if (optind<argc) + sync->Sync(argv+optind,delete_mode); + delete sync; + database_end(); return 0; } diff --git a/scripts/COPYRIGHT b/scripts/COPYRIGHT index 9b0e8cb..05339ef 100644 --- a/scripts/COPYRIGHT +++ b/scripts/COPYRIGHT @@ -1,16 +1,17 @@ -# the content of languages.txt is taken from the file -# iso_639.tab which contains this copyright: +the content of languages.txt is generated from the file +iso_639.xml which contains this copyright: +<?xml version="1.0" encoding="UTF-8" ?> +<!-- iso-639.tab --> +<!-- --> +<!-- Copyright (C) 2004 Alastair McKinstry <mckinstry@computer.org> --> +<!-- Released under the GNU License; see file COPYING for details --> +<!-- --> +<!-- Last update: 2004-10-27 --> +<!-- --> +<!-- This file gives a list of all languages in the ISO-639 --> +<!-- standard, and is used to provide translations (via gettext) --> +<!-- --> +<!-- Status: ISO 639-2:1998 + additions and changes until 2004-03-05 --> +<!-- Source: http://lcweb.loc.gov/standards/iso639-2/englangn.html --> -## iso-639.tab -## -## Copyright (C) 2004 Alastair McKinstry <mckinstry@computer.org> -## Released under the GNU License; see file COPYING for details -## -## Last update: 2004-03-29 -## -## This file gives a list of all languages in the ISO-639 -## standard, and is used to provide translations (via gettext) -## -## Status: ISO 639-2:1998 + additions and changes until 2003-03-05 -## Source: http://lcweb.loc.gov/standards/iso639-2/englangn.html diff --git a/scripts/createtables.mysql b/scripts/createtables.mysql index 079b3bd..708bdd2 100755 --- a/scripts/createtables.mysql +++ b/scripts/createtables.mysql @@ -224,6 +224,11 @@ CREATE TABLE tracks ( channels tinyint(3) unsigned default NULL, id int(11) NOT NULL auto_increment, + folder1 varchar(255), + folder2 varchar(255), + folder3 varchar(255), + folder4 varchar(255), + PRIMARY KEY (id), KEY title (title(10)), KEY mp3file (mp3file(10)), diff --git a/scripts/genres.txt b/scripts/genres.txt index 43124e9..25655e4 100755 --- a/scripts/genres.txt +++ b/scripts/genres.txt @@ -1,20 +1,20 @@ b 20 Alternative -ba 40 Alternative General +ba 40 Alt. Rock bb \N Art Rock -bc 90 Avant Rock +bc 90 Avantgarde be \N Experimental bh 6 Grunge -bi \N Indie -bm 12 Unclassifiable -bn \N Crossover +bi 131 Indie +bm 12 Other +bn 139 Crossover c \N Books & Spoken ca \N Short Stories cb 57 Comedy -cc 77 Musicals/Broadway +cc 77 Musical cd \N Poetry -ce \N Cabaret / Satire +ce 65 Cabaret cf \N Religion -cg 101 Spoken Word +cg 101 Speech ch \N Stories/Fairytales ci \N Radio Play cia \N Literary Radio Play @@ -28,7 +28,7 @@ eca \N Contemp. Crossover ecb \N Electronic Classical ecc \N Experimental Classical ecd \N Minimal Music -ed \N Film Music +ed 24 Soundtrack ee 33 Instrumental ef \N Period Music efa \N Baroque @@ -40,12 +40,11 @@ eg \N Solo Instruments ega \N Guitar egb \N Percussion egc \N Piano -eh 106 Symphonic -ei 28 Classic Vocal -eia 97 Choral +eh 106 Symphony +ei 28 Vocal +eia 97 Chorus eib \N Ensembles eic 103 Opera -ej \N Baroque f 2 Country fa \N Alternative Country fb 89 Bluegrass @@ -58,7 +57,7 @@ fi \N Rockabilly g 98 Easy Listening gb \N Lounge gc \N Love Songs -gca 116 Ballads +gca 116 Ballad gd \N Mood Music ge 10 New Age gf \N Soft Rock @@ -69,7 +68,7 @@ gi 45 Meditative gj \N Pair Dance gja \N Walz gjb \N Tango -h 102 Songs/Chansons +h 102 Chanson ha \N Singer-Songwriter hb 65 Children's Music i 52 Electronic @@ -80,33 +79,35 @@ ica \N Breakbeat icb \N Darkside icc 63 Jungle icd \N Ragga -ice 27 Trip Hop +ice 27 Trip-Hop id 3 Dance -ie \N Drum n' Bass +ie 127 Drum & Bass if \N Electronica ig \N Envir. Soundscapes ih \N Experimental Elect. iha \N Minimal Experimental ihb 39 Noise -ii 37 Game Soundtracks +ii 37 Sound Clip ij 35 House ija \N Acid House ijb \N Funk House ijc \N Hard House ijd \N Progressive House +ije 124 Euro-House +ijf 128 Club-House ik 19 Industrial il 18 Techno ilb \N Dub -ild \N Goa +ild 126 Goa ile \N Hardcore Techno ilf \N Illbient ilg \N Minimal -ilh 25 Old Skool Techno +ilh 25 Euro-Techno ili 68 Rave ilj 31 Trance im 44 Space Music j \N Hip Hop/Rap -ja 7 Hip Hop +ja 7 Hip-Hop jb 15 Rap jbb 61 Christian Rap jbd \N Hardcore Rap @@ -124,8 +125,8 @@ kd 14 R&B ke 42 Soul kea \N Sweet Soul l 8 Jazz -la 73 Acid Jazz -lb 85 Bebop +la 73 Acid Punk +lb 85 Bebob lc \N Dancefloor Jazz lf 30 Jazz Fusion lh \N Jazz Vocals @@ -152,14 +153,14 @@ mcc 22 Death Metal mcd \N Doom Metal mcf \N Hard Core Metal mcg \N Heavy Metal -mck \N Thrash/Speed Metal +mck \N Thrash Metal md 13 Pop mda 99 Acoustic mdb \N Synthesizer Pop mdd \N Latin Pop -mdg 123 A capella/Pop Vocals +mdg 123 A Capella mdh \N Neue Deutsche Welle -mdi \N Disco +mdi 4 Disco mdj \N Dance Pop mdja \N Twist mdk \N Doo-Wop @@ -170,15 +171,15 @@ mec \N Old School Punk med 21 Ska mf 17 Rock mfb \N Acid Rock -mfe 81 Folk Rock +mfe 81 Folk/Rock mfg \N Groove Rock mfh \N Guitar Rock mfha 1 Classic Rock mfhb \N Improv Rock -mfhc 47 Instrumental Rock +mfhc 47 Instrum. Rock mfhf \N Surf Rock mfj 66 New Wave -mfk 67 Psychedelic +mfk 67 Psychadelic mfl 78 Rock & Roll mfm 94 Symphonic Rock mg \N Rock En Espanol @@ -194,7 +195,7 @@ nce \N Arabian Pop ncf \N European Ethnopop ncg \N Latin Pop nch \N Caribbean Pop -nd 82 World Traditions +nd 82 National Folk nda \N African ndaa \N Mali Blues ndb \N Arabic @@ -202,7 +203,7 @@ ndc \N Asian ndd \N Bossa Nova nde \N Caribbean ndf 88 Celtic -ndg 53 European Folk/Pop +ndg 53 Pop-Folk ndga \N Jodel ndh \N France ndi \N Germany @@ -212,8 +213,8 @@ ndl 86 Latin ndlb \N Flamenco ndlc \N Mambo ndld \N Mariachi -ndle \N Meringue -ndlg \N Salsa +ndle 142 Merengue +ndlg 143 Salsa ndlh 114 Samba ndm 64 Native American ndn \N Quebecois @@ -221,4 +222,53 @@ ndo \N Russian ndp \N South/Cent. American ndq \N Spain ndr 113 Tango +y 11 Oldies +y2 49 Gothic +y6 23 Pranks +y9 29 Jazz+Funk +y12 46 Instrum. Pop +y14 48 Ethnic +y15 50 Darkwave +y16 51 Techno-Indust. +y18 54 Eurodance +y19 55 Dream +y20 56 Southern Rock +y21 58 Cult +y22 60 Top 40 +y23 62 Pop/Funk +y25 69 Showtunes +y26 70 Trailer +y27 72 Tribal +y29 75 Polka +y30 79 Hard Rock +y34 87 Revival +y36 91 Gothic Rock +y37 92 Progress. Rock +y38 93 Psychadel. Rock +y39 95 Slow Rock +y41 100 Humour +y42 107 Booty Bass +y43 108 Primus +y44 109 Porn Groove +y45 111 Slow Jam +y46 112 Club +y47 115 Folklore +y48 117 Power Ballad +y49 118 Rhythmic Soul +y50 119 Freestyle +y51 120 Duet +y52 122 Drum Solo +y55 125 Dance Hall +y58 130 Terror +y59 132 BritPop +y60 133 Negerpunk +y61 134 Polsk Punk +y62 135 Beat +y63 136 Christian Gangsta Rap +y64 138 Black Metal +y65 140 Contemporary Christian +y66 141 Christian Rock +y69 145 Anime +y70 146 Jpop +y71 147 Synthpop NULL diff --git a/scripts/gentables b/scripts/gentables new file mode 100755 index 0000000..f7fcc89 --- /dev/null +++ b/scripts/gentables @@ -0,0 +1,47 @@ + +( + echo " +//autogenerated by `pwd -P`/$0 + +genres_t genres[] = {" + +cat scripts/genres.txt | while read gdid id3 name +do + test "$id3" = N && id3=-1 + test "$gdid" = NULL && break + echo ' { "'$gdid'", '$id3', "'$name'" },' +done +echo '}; +' + +echo "lang_t languages[] = {" + +scripts/iso639tab.py scripts/iso_639.xml | + grep -v '^#' | + grep -v '^$' | + while read iso1 iso2 iso3 name +do + echo ' { "'$iso2'", "'$name'" },' +done +echo '}; +' + +echo "musictypes_t musictypes[] = {" + +cat scripts/musictypes.txt | while read mtype +do + echo ' { "'$mtype'"},' +done +echo '}; +' + +echo "sources_t sources[] = {" + +cat scripts/sources.txt | while read stype +do + echo ' { "'$stype'"},' +done +echo '};' +) >mg_tables.h + + diff --git a/scripts/iso639tab.py b/scripts/iso639tab.py new file mode 100755 index 0000000..b2739c9 --- /dev/null +++ b/scripts/iso639tab.py @@ -0,0 +1,81 @@ +#!/usr/bin/python +# +# Read iso-codes iso_639.xml data file and output a .tab file +# +# Copyright (C) 2005 Alastair McKinstry <mckinstry@debian.org> +# Released under the GPL. +# $Id: iso639tab.py,v 1.1 2005/03/02 07:24:51 mckinstry Exp $ + +from xml.sax import saxutils, make_parser, saxlib, saxexts, ContentHandler +from xml.sax.handler import feature_namespaces +import sys, os, getopt, urllib2 + +class printLines(saxutils.DefaultHandler): + def __init__(self, ofile): + self.ofile = ofile + + def startElement(self, name, attrs): + if name != 'iso_639_entry': + return + t_code = attrs.get('iso_639_2T_code', None) + if t_code == None: + raise RunTimeError, "Bad file" + if type(t_code) == unicode: + t_code = t_code.encode('UTF-8') + b_code = attrs.get('iso_639_2B_code', None) + if b_code == None: + raise RunTimeError, "Bad file" + if type(b_code) == unicode: + b_code = b_code.encode('UTF-8') + name = attrs.get('name', None) + if name == None: + raise RunTimeError, " BadFile" + short_code=attrs.get('iso_639_1_code','XX') + short_code=short_code.encode('UTF-8') + if type(name) == unicode: + name = name.encode('UTF-8') + self.ofile.write (t_code + '\t' + b_code + '\t' + short_code + '\t' + name + '\n') + + +## +## MAIN +## + + +ofile = sys.stdout +ofile.write(""" +## iso-639.tab +## +## Copyright (C) 2005 Alastair McKinstry <mckinstry@computer.org> +## Released under the GNU License; see file COPYING for details +## +## PLEASE NOTE: THIS FILE IS DEPRECATED AND SCHEDULED TO BE REMOVED. +## IT IS FOR BACKWARD-COMPATIBILITY ONLY: PLEASE USE THE ISO-639.XML +## FILE INSTEAD. +## +## This file gives a list of all languages in the ISO-639 +## standard, and is used to provide translations (via gettext) +## +## Status: ISO 639-2:1998 + additions and changes until 2003-03-05 +## Source: http://lcweb.loc.gov/standards/iso639-2/englangn.html +## +## Columns: +## iso-639-2 terminology code +## iso-639-2 bibliography code +## iso-639-1 code (XX if none exists) +## Name (English) +## +## +""") +p = make_parser() +p.setErrorHandler(saxutils.ErrorPrinter()) +try: + dh = printLines(ofile) + p.setContentHandler(dh) + p.parse(sys.argv[1]) +except IOError,e: + print in_sysID+": "+str(e) +except saxlib.SAXException,e: + print str(e) + +ofile.close() diff --git a/scripts/iso_639.xml b/scripts/iso_639.xml new file mode 100644 index 0000000..ed6fc73 --- /dev/null +++ b/scripts/iso_639.xml @@ -0,0 +1,2072 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- iso-639.tab --> +<!-- --> +<!-- Copyright (C) 2004 Alastair McKinstry <mckinstry@computer.org> --> +<!-- Released under the GNU License; see file COPYING for details --> +<!-- --> +<!-- Last update: 2004-10-27 --> +<!-- --> +<!-- This file gives a list of all languages in the ISO-639 --> +<!-- standard, and is used to provide translations (via gettext) --> +<!-- --> +<!-- Status: ISO 639-2:1998 + additions and changes until 2004-03-05 --> +<!-- Source: http://lcweb.loc.gov/standards/iso639-2/englangn.html --> + +<iso_639_entries> + <iso_639_entry + iso_639_2B_code="aar" + iso_639_2T_code="aar" + iso_639_1_code="aa" + name="Afar" /> + <iso_639_entry + iso_639_2B_code="abk" + iso_639_2T_code="abk" + iso_639_1_code="ab" + name="Abkhazian" /> + <iso_639_entry + iso_639_2B_code="ace" + iso_639_2T_code="ace" + name="Achinese" /> + <iso_639_entry + iso_639_2B_code="ach" + iso_639_2T_code="ach" + name="Acoli" /> + <iso_639_entry + iso_639_2B_code="ada" + iso_639_2T_code="ada" + name="Adangme" /> + <iso_639_entry + iso_639_2B_code="ady" + iso_639_2T_code="ady" + name="Adyghe; Adygei" /> + <iso_639_entry + iso_639_2B_code="afa" + iso_639_2T_code="afa" + name="Afro-Asiatic (Other)" /> + <iso_639_entry + iso_639_2B_code="afh" + iso_639_2T_code="afh" + name="Afrihili" /> + <iso_639_entry + iso_639_2B_code="afr" + iso_639_2T_code="afr" + iso_639_1_code="af" + name="Afrikaans" /> + <iso_639_entry + iso_639_2B_code="aka" + iso_639_2T_code="aka" + iso_639_1_code="ak" + name="Akan" /> + <iso_639_entry + iso_639_2B_code="akk" + iso_639_2T_code="akk" + name="Akkadian" /> + <iso_639_entry + iso_639_2B_code="alb" + iso_639_2T_code="sqi" + iso_639_1_code="sq" + name="Albanian" /> + <iso_639_entr + iso_639_2B_code="alg" + iso_639_2T_code="alg" + name="Algonquian languages" /> + <iso_639_entry + iso_639_2B_code="amh" + iso_639_2T_code="amh" + iso_639_1_code="am" + name="Amharic" /> + <iso_639_entry + iso_639_2B_code="ang" + iso_639_2T_code="ang" + name="English, Old (ca.450-1100)" /> + <iso_639_entry + iso_639_2B_code="apa" + iso_639_2T_code="apa" + name="Apache languages" /> + <iso_639_entry + iso_639_2B_code="ara" + iso_639_2T_code="ara" + iso_639_1_code="ar" + name="Arabic" /> + <iso_639_entry + iso_639_2B_code="arc" + iso_639_2T_code="arc" + name="Aramaic" /> + <iso_639_entry + iso_639_2B_code="arg" + iso_639_2T_code="arg" + iso_639_1_code="an" + name="Aragonese" /> + <iso_639_entry + iso_639_2B_code="arm" + iso_639_2T_code="hye" + iso_639_1_code="hy" + name="Armenian" /> + <iso_639_entry + iso_639_2B_code="arn" + iso_639_2T_code="arn" + name="Araucanian" /> + <iso_639_entry + iso_639_2B_code="arp" + iso_639_2T_code="arp" + name="Arapaho" /> + <iso_639_entry + iso_639_2B_code="art" + iso_639_2T_code="art" + name="Artificial (Other)" /> + <iso_639_entry + iso_639_2B_code="arw" + iso_639_2T_code="arw" + name="Arawak" /> + <iso_639_entry + iso_639_2B_code="asm" + iso_639_2T_code="asm" + iso_639_1_code="as" + name="Assamese" /> + <iso_639_entry + iso_639_2B_code="ast" + iso_639_2T_code="ast" + name="Asturian; Bable" /> + <iso_639_entry + iso_639_2B_code="ath" + iso_639_2T_code="ath" + name="Athapascan language" /> + <iso_639_entry + iso_639_2B_code="aus" + iso_639_2T_code="aus" + name="Australian languages" /> + <iso_639_entry + iso_639_2B_code="ava" + iso_639_2T_code="ava" + iso_639_1_code="av" + name="Avaric" /> + <iso_639_entry + iso_639_2B_code="ave" + iso_639_2T_code="ave" + iso_639_1_code="ae" + name="Avestan" /> + <iso_639_entry + iso_639_2B_code="awa" + iso_639_2T_code="awa" + name="Awadhi" /> + <iso_639_entry + iso_639_2B_code="aym" + iso_639_2T_code="aym" + iso_639_1_code="ay" + name="Aymara" /> + <iso_639_entry + iso_639_2B_code="aze" + iso_639_2T_code="aze" + iso_639_1_code="az" + name="Azerbaijani" /> + <iso_639_entry + iso_639_2B_code="bad" + iso_639_2T_code="bad" + name="Banda" /> + <iso_639_entry + iso_639_2B_code="bai" + iso_639_2T_code="bai" + name="Bamileke languages" /> + <iso_639_entry + iso_639_2B_code="bak" + iso_639_2T_code="bak" + iso_639_1_code="ba" + name="Bashkir" /> + <iso_639_entry + iso_639_2B_code="bal" + iso_639_2T_code="bal" + name="Baluchi" /> + <iso_639_entry + iso_639_2B_code="bam" + iso_639_2T_code="bam" + iso_639_1_code="bm" + name="Bambara" /> + <iso_639_entry + iso_639_2B_code="ban" + iso_639_2T_code="ban" + name="Balinese" /> + <iso_639_entry + iso_639_2B_code="baq" + iso_639_2T_code="eus" + iso_639_1_code="eu" + name="Basque" /> + <iso_639_entry + iso_639_2B_code="bas" + iso_639_2T_code="bas" + name="Basa" /> + <iso_639_entry + iso_639_2B_code="bat" + iso_639_2T_code="bat" + name="Baltic (Other)" /> + <iso_639_entry + iso_639_2B_code="bej" + iso_639_2T_code="bej" + name="Beja" /> + <iso_639_entry + iso_639_2B_code="bel" + iso_639_2T_code="bel" + iso_639_1_code="be" + name="Belarusian" /> + <iso_639_entry + iso_639_2B_code="bem" + iso_639_2T_code="bem" + name="Bemba" /> + <iso_639_entry + iso_639_2B_code="ben" + iso_639_2T_code="ben" + iso_639_1_code="bn" + name="Bengali" /> + <iso_639_entry + iso_639_2B_code="ber" + iso_639_2T_code="ber" + name="Berber (Other)" /> + <iso_639_entry + iso_639_2B_code="bho" + iso_639_2T_code="bho" + name="Bhojpuri" /> + <iso_639_entry + iso_639_2B_code="bih" + iso_639_2T_code="bih" + iso_639_1_code="bh" + name="Bihari" /> + <iso_639_entry + iso_639_2B_code="bik" + iso_639_2T_code="bik" + name="Bikol" /> + <iso_639_entry + iso_639_2B_code="bin" + iso_639_2T_code="bin" + name="Bini" /> + <iso_639_entry + iso_639_2B_code="bis" + iso_639_2T_code="bis" + iso_639_1_code="bi" + name="Bislama" /> + <iso_639_entry + iso_639_2B_code="bla" + iso_639_2T_code="bla" + name="Siksika" /> + <iso_639_entry + iso_639_2B_code="bnt" + iso_639_2T_code="bnt" + name="Bantu (Other)" /> + <iso_639_entry + iso_639_2B_code="bos" + iso_639_2T_code="bos" + iso_639_1_code="bs" + name="Bosnian" /> + <iso_639_entry + iso_639_2B_code="bra" + iso_639_2T_code="bra" + name="Braj" /> + <iso_639_entry + iso_639_2B_code="bre" + iso_639_2T_code="bre" + iso_639_1_code="br" + name="Breton" /> + <iso_639_entry + iso_639_2B_code="btk" + iso_639_2T_code="btk" + name="Batak (Indonesia)" /> + <iso_639_entry + iso_639_2B_code="bua" + iso_639_2T_code="bua" + name="Buriat" /> + <iso_639_entry + iso_639_2B_code="bug" + iso_639_2T_code="bug" + name="Buginese" /> + <iso_639_entry + iso_639_2B_code="bul" + iso_639_2T_code="bul" + iso_639_1_code="bg" + name="Bulgarian" /> + <iso_639_entry + iso_639_2B_code="bur" + iso_639_2T_code="mya" + iso_639_1_code="my" + name="Burmese" /> + <iso_639_entry + iso_639_2B_code="byn" + iso_639_2T_code="byn" + name="Blin; Bilin" /> + <iso_639_entry + iso_639_2B_code="cad" + iso_639_2T_code="cad" + name="Caddo" /> + <iso_639_entry + iso_639_2B_code="cai" + iso_639_2T_code="cai" + name="Central American Indian (Other)" /> + <iso_639_entry + iso_639_2B_code="car" + iso_639_2T_code="car" + name="Carib" /> + <iso_639_entry + iso_639_2B_code="cat" + iso_639_2T_code="cat" + iso_639_1_code="ca" + name="Catalan" /> + <iso_639_entry + iso_639_2B_code="cau" + iso_639_2T_code="cau" + name="Caucasian (Other)" /> + <iso_639_entry + iso_639_2B_code="ceb" + iso_639_2T_code="ceb" + name="Cebuano" /> + <iso_639_entry + iso_639_2B_code="cel" + iso_639_2T_code="cel" + name="Celtic (Other)" /> + <iso_639_entry + iso_639_2B_code="cha" + iso_639_2T_code="cha" + iso_639_1_code="ch" + name="Chamorro" /> + <iso_639_entry + iso_639_2B_code="chb" + iso_639_2T_code="chb" + name="Chibcha" /> + <iso_639_entry + iso_639_2B_code="che" + iso_639_2T_code="che" + iso_639_1_code="ce" + name="Chechen" /> + <iso_639_entry + iso_639_2B_code="chg" + iso_639_2T_code="chg" + name="Chagatai" /> + <iso_639_entry + iso_639_2B_code="chi" + iso_639_2T_code="zho" + iso_639_1_code="zh" + name="Chinese" /> + <iso_639_entry + iso_639_2B_code="chk" + iso_639_2T_code="chk" + name="Chukese" /> + <iso_639_entry + iso_639_2B_code="chm" + iso_639_2T_code="chm" + name="Mari" /> + <iso_639_entry + iso_639_2B_code="chn" + iso_639_2T_code="chn" + name="Chinook jargon" /> + <iso_639_entry + iso_639_2B_code="cho" + iso_639_2T_code="cho" + name="Choctaw" /> + <iso_639_entry + iso_639_2B_code="chp" + iso_639_2T_code="chp" + name="Chipewyan" /> + <iso_639_entry + iso_639_2B_code="chr" + iso_639_2T_code="chr" + name="Cherokee" /> + <iso_639_entry + iso_639_2B_code="chu" + iso_639_2T_code="chu" + name="Church Slavic" /> + <iso_639_entry + iso_639_2B_code="chv" + iso_639_2T_code="chv" + iso_639_1_code="cv" + name="Chuvash" /> + <iso_639_entry + iso_639_2B_code="chy" + iso_639_2T_code="chy" + name="Cheyenne" /> + <iso_639_entry + iso_639_2B_code="cmc" + iso_639_2T_code="cmc" + name="Chamic languages" /> + <iso_639_entry + iso_639_2B_code="cop" + iso_639_2T_code="cop" + name="Coptic" /> + <iso_639_entry + iso_639_2B_code="cor" + iso_639_2T_code="cor" + iso_639_1_code="kw" + name="Cornish" /> + <iso_639_entry + iso_639_2B_code="cos" + iso_639_2T_code="cos" + iso_639_1_code="co" + name="Corsican" /> + <iso_639_entry + iso_639_2B_code="cpe" + iso_639_2T_code="cpe" + name="English-based (Other)" /> + <iso_639_entry + iso_639_2B_code="cpf" + iso_639_2T_code="cpf" + name="French-based (Other)" /> + <iso_639_entry + iso_639_2B_code="cpp" + iso_639_2T_code="cpp" + name="Portuguese-based (Other)" /> + <iso_639_entry + iso_639_2B_code="cre" + iso_639_2T_code="cre" + iso_639_1_code="cr" + name="Cree" /> + <iso_639_entry + iso_639_2B_code="crh" + iso_639_2T_code="crh" + name="Crimean Turkish; Crimean Tatar" /> + <iso_639_entry + iso_639_2B_code="crp" + iso_639_2T_code="crp" + name="Creoles and pidgins (Other)" /> + <iso_639_entry + iso_639_2B_code="csb" + iso_639_2T_code="csb" + name="Kashubian" /> + <iso_639_entry + iso_639_2B_code="cus" + iso_639_2T_code="cus" + name="Cushitic (Other)" /> + <iso_639_entry + iso_639_2B_code="cze" + iso_639_2T_code="ces" + iso_639_1_code="cs" + name="Czech" /> + <iso_639_entry + iso_639_2B_code="dak" + iso_639_2T_code="dak" + name="Dakota" /> + <iso_639_entry + iso_639_2B_code="dan" + iso_639_2T_code="dan" + iso_639_1_code="da" + name="Danish" /> + <iso_639_entry + iso_639_2B_code="dar" + iso_639_2T_code="dar" + name="Dargwa" /> + <iso_639_entry + iso_639_2B_code="del" + iso_639_2T_code="del" + name="Delaware" /> + <iso_639_entry + iso_639_2B_code="den" + iso_639_2T_code="den" + name="Slave (Athapascan)" /> + <iso_639_entry + iso_639_2B_code="dgr" + iso_639_2T_code="dgr" + name="Dogrib" /> + <iso_639_entry + iso_639_2B_code="din" + iso_639_2T_code="din" + name="Dinka" /> + <iso_639_entry + iso_639_2B_code="div" + iso_639_2T_code="div" + iso_639_1_code="dv" + name="Divehi" /> + <iso_639_entry + iso_639_2B_code="doi" + iso_639_2T_code="doi" + name="Dogri" /> + <iso_639_entry + iso_639_2B_code="dra" + iso_639_2T_code="dra" + name="Dravidian (Other)" /> + <iso_639_entry + iso_639_2B_code="dsb" + iso_639_2T_code="dsb" + name="Lower Sorbian" /> + <iso_639_entry + iso_639_2B_code="dua" + iso_639_2T_code="dua" + name="Duala" /> + <iso_639_entry + iso_639_2B_code="dum" + iso_639_2T_code="dum" + name="Dutch, Middle (ca. 1050-1350)" /> + <iso_639_entry + iso_639_2B_code="dut" + iso_639_2T_code="nld" + iso_639_1_code="nl" + name="Dutch" /> + <iso_639_entry + iso_639_2B_code="dyu" + iso_639_2T_code="dyu" + name="Dyula" /> + <iso_639_entry + iso_639_2B_code="dzo" + iso_639_2T_code="dzo" + iso_639_1_code="dz" + name="Dzongkha" /> + <iso_639_entry + iso_639_2B_code="efi" + iso_639_2T_code="efi" + name="Efik" /> + <iso_639_entry + iso_639_2B_code="egy" + iso_639_2T_code="egy" + name="Egyptian (Ancient)" /> + <iso_639_entry + iso_639_2B_code="eka" + iso_639_2T_code="eka" + name="Ekajuk" /> + <iso_639_entry + iso_639_2B_code="elx" + iso_639_2T_code="elx" + name="Elamite" /> + <iso_639_entry + iso_639_2B_code="eng" + iso_639_2T_code="eng" + iso_639_1_code="en" + name="English" /> + <iso_639_entry + iso_639_2B_code="enm" + iso_639_2T_code="enm" + name="English, Middle (1100-1500)" /> + <iso_639_entry + iso_639_2B_code="epo" + iso_639_2T_code="epo" + iso_639_1_code="eo" + name="Esperanto" /> + <iso_639_entry + iso_639_2B_code="est" + iso_639_2T_code="est" + iso_639_1_code="et" + name="Estonian" /> + <iso_639_entry + iso_639_2B_code="ewe" + iso_639_2T_code="ewe" + iso_639_1_code="ee" + name="Ewe" /> + <iso_639_entry + iso_639_2B_code="ewo" + iso_639_2T_code="ewo" + name="Ewondo" /> + <iso_639_entry + iso_639_2B_code="fan" + iso_639_2T_code="fan" + name="Fang" /> + <iso_639_entry + iso_639_2B_code="fao" + iso_639_2T_code="fao" + iso_639_1_code="fo" + name="Faroese" /> + <iso_639_entry + iso_639_2B_code="fat" + iso_639_2T_code="fat" + name="Fanti" /> + <iso_639_entry + iso_639_2B_code="fij" + iso_639_2T_code="fij" + iso_639_1_code="fj" + name="Fijian" /> + <iso_639_entry + iso_639_2B_code="fil" + iso_639_2T_code="fil" + name="Filipino; Pilipino" /> + <iso_639_entry + iso_639_2B_code="fin" + iso_639_2T_code="fin" + iso_639_1_code="fi" + name="Finnish" /> + <iso_639_entry + iso_639_2B_code="fiu" + iso_639_2T_code="fiu" + name="Finno-Ugrian (Other)" /> + <iso_639_entry + iso_639_2B_code="fon" + iso_639_2T_code="fon" + name="Fon" /> + <iso_639_entry + iso_639_2B_code="fre" + iso_639_2T_code="fra" + iso_639_1_code="fr" + name="French" /> + <iso_639_entry + iso_639_2B_code="frm" + iso_639_2T_code="frm" + name="French, Middle (ca.1400-1600)" /> + <iso_639_entry + iso_639_2B_code="fro" + iso_639_2T_code="fro" + name="French, Old (842-ca.1400)" /> + <iso_639_entry + iso_639_2B_code="fry" + iso_639_2T_code="fry" + iso_639_1_code="fy" + name="Frisian" /> + <iso_639_entry + iso_639_2B_code="ful" + iso_639_2T_code="ful" + iso_639_1_code="ff" + name="Fulah" /> + <iso_639_entry + iso_639_2B_code="fur" + iso_639_2T_code="fur" + name="Friulian" /> + <iso_639_entry + iso_639_2B_code="gaa" + iso_639_2T_code="gaa" + name="Ga" /> + <iso_639_entry + iso_639_2B_code="gay" + iso_639_2T_code="gay" + name="Gayo" /> + <iso_639_entry + iso_639_2B_code="gba" + iso_639_2T_code="gba" + name="Gbaya" /> + <iso_639_entry + iso_639_2B_code="gem" + iso_639_2T_code="gem" + name="Germanic (Other)" /> + <iso_639_entry + iso_639_2B_code="geo" + iso_639_2T_code="kat" + iso_639_1_code="ka" + name="Georgian" /> + <iso_639_entry + iso_639_2B_code="ger" + iso_639_2T_code="deu" + iso_639_1_code="de" + name="German" /> + <iso_639_entry + iso_639_2B_code="gez" + iso_639_2T_code="gez" + name="Geez" /> + <iso_639_entry + iso_639_2B_code="gil" + iso_639_2T_code="gil" + name="Gilbertese" /> + <iso_639_entry + iso_639_2B_code="gla" + iso_639_2T_code="gla" + iso_639_1_code="gd" + name="Gaelic; Scottish" /> + <iso_639_entry + iso_639_2B_code="gle" + iso_639_2T_code="gle" + iso_639_1_code="ga" + name="Irish" /> + <iso_639_entry + iso_639_2B_code="glg" + iso_639_2T_code="glg" + iso_639_1_code="gl" + name="Gallegan" /> + <iso_639_entry + iso_639_2B_code="glv" + iso_639_2T_code="glv" + iso_639_1_code="gv" + name="Manx" /> + <iso_639_entry + iso_639_2B_code="gmh" + iso_639_2T_code="gmh" + name="German, Middle High (ca.1050-1500)" /> + <iso_639_entry + iso_639_2B_code="goh" + iso_639_2T_code="goh" + name="German, Old High (ca.750-1050)" /> + <iso_639_entry + iso_639_2B_code="gon" + iso_639_2T_code="gon" + name="Gondi" /> + <iso_639_entry + iso_639_2B_code="gor" + iso_639_2T_code="gor" + name="Gorontalo" /> + <iso_639_entry + iso_639_2B_code="got" + iso_639_2T_code="got" + name="Gothic" /> + <iso_639_entry + iso_639_2B_code="grb" + iso_639_2T_code="grb" + name="Grebo" /> + <iso_639_entry + iso_639_2B_code="grc" + iso_639_2T_code="grc" + name="Greek, Ancient (to 1453)" /> + <iso_639_entry + iso_639_2B_code="gre" + iso_639_2T_code="ell" + iso_639_1_code="el" + name="Greek, Modern (1453-)" /> + <iso_639_entry + iso_639_2B_code="grn" + iso_639_2T_code="grn" + iso_639_1_code="gn" + name="Guarani" /> + <iso_639_entry + iso_639_2B_code="guj" + iso_639_2T_code="guj" + iso_639_1_code="gu" + name="Gujarati" /> + <iso_639_entry + iso_639_2B_code="gwi" + iso_639_2T_code="gwi" + name="Gwichin" /> + <iso_639_entry + iso_639_2B_code="hai" + iso_639_2T_code="hai" + name="Haida" /> + <iso_639_entry + iso_639_2B_code="hat" + iso_639_2T_code="hat" + iso_639_1_code="ht" + name="Haitian; Haitian Creole" /> + <iso_639_entry + iso_639_2B_code="hau" + iso_639_2T_code="hau" + iso_639_1_code="ha" + name="Hausa" /> + <iso_639_entry + iso_639_2B_code="haw" + iso_639_2T_code="haw" + name="Hawaiian" /> + <iso_639_entry + iso_639_2B_code="heb" + iso_639_2T_code="heb" + iso_639_1_code="he" + name="Hebrew" /> + <iso_639_entry + iso_639_2B_code="her" + iso_639_2T_code="her" + iso_639_1_code="hz" + name="Herero" /> + <iso_639_entry + iso_639_2B_code="hil" + iso_639_2T_code="hil" + name="Hiligaynon" /> + <iso_639_entry + iso_639_2B_code="him" + iso_639_2T_code="him" + name="Himachali" /> + <iso_639_entry + iso_639_2B_code="hin" + iso_639_2T_code="hin" + iso_639_1_code="hi" + name="Hindi" /> + <iso_639_entry + iso_639_2B_code="hit" + iso_639_2T_code="hit" + name="Hittite" /> + <iso_639_entry + iso_639_2B_code="hmn" + iso_639_2T_code="hmn" + name="Hmong" /> + <iso_639_entry + iso_639_2B_code="hmo" + iso_639_2T_code="hmo" + iso_639_1_code="ho" + name="Hiri" /> + <iso_639_entry + iso_639_2B_code="hsb" + iso_639_2T_code="hsb" + name="Upper Sorbian" /> + <iso_639_entry + iso_639_2B_code="hun" + iso_639_2T_code="hun" + iso_639_1_code="hu" + name="Hungarian" /> + <iso_639_entry + iso_639_2B_code="hup" + iso_639_2T_code="hup" + name="Hupa" /> + <iso_639_entry + iso_639_2B_code="iba" + iso_639_2T_code="iba" + name="Iban" /> + <iso_639_entry + iso_639_2B_code="ibo" + iso_639_2T_code="ibo" + iso_639_1_code="ig" + name="Igbo" /> + <iso_639_entry + iso_639_2B_code="ice" + iso_639_2T_code="isl" + iso_639_1_code="is" + name="Icelandic" /> + <iso_639_entry + iso_639_2B_code="ido" + iso_639_2T_code="ido" + iso_639_1_code="io" + name="Ido" /> + <iso_639_entry + iso_639_2B_code="iii" + iso_639_2T_code="iii" + iso_639_1_code="ii" + name="Sichuan Yi" /> + <iso_639_entry + iso_639_2B_code="ijo" + iso_639_2T_code="ijo" + name="Ijo" /> + <iso_639_entry + iso_639_2B_code="iku" + iso_639_2T_code="iku" + iso_639_1_code="iu" + name="Inuktitut" /> + <iso_639_entry + iso_639_2B_code="ile" + iso_639_2T_code="ile" + iso_639_1_code="ie" + name="Interlingue" /> + <iso_639_entry + iso_639_2B_code="ilo" + iso_639_2T_code="ilo" + name="Iloko" /> + <iso_639_entry + iso_639_2B_code="ina" + iso_639_2T_code="ina" + iso_639_1_code="ia" + name="Interlingua" /> + <iso_639_entry + iso_639_2B_code="inc" + iso_639_2T_code="inc" + name="Indic (Other)" /> + <iso_639_entry + iso_639_2B_code="ind" + iso_639_2T_code="ind" + iso_639_1_code="id" + name="Indonesian" /> + <iso_639_entry + iso_639_2B_code="ine" + iso_639_2T_code="ine" + name="Indo-European (Other)" /> + <iso_639_entry + iso_639_2B_code="inh" + iso_639_2T_code="inh" + name="Ingush" /> + <iso_639_entry + iso_639_2B_code="ipk" + iso_639_2T_code="ipk" + iso_639_1_code="ik" + name="Inupiaq" /> + <iso_639_entry + iso_639_2B_code="ira" + iso_639_2T_code="ira" + name="Iranian (Other)" /> + <iso_639_entry + iso_639_2B_code="iro" + iso_639_2T_code="iro" + name="Iroquoian languages" /> + <iso_639_entry + iso_639_2B_code="ita" + iso_639_2T_code="ita" + iso_639_1_code="it" + name="Italian" /> + <iso_639_entry + iso_639_2B_code="jav" + iso_639_2T_code="jav" + iso_639_1_code="jv" + name="Javanese" /> + <iso_639_entry + iso_639_2B_code="jbo" + iso_639_2T_code="jbo" + name="Lojban" /> + <iso_639_entry + iso_639_2B_code="jpn" + iso_639_2T_code="jpn" + iso_639_1_code="ja" + name="Japanese" /> + <iso_639_entry + iso_639_2B_code="jpr" + iso_639_2T_code="jpr" + name="Judeo-Persian" /> + <iso_639_entry + iso_639_2B_code="jrb" + iso_639_2T_code="jrb" + name="Judeo-Arabic" /> + <iso_639_entry + iso_639_2B_code="kaa" + iso_639_2T_code="kaa" + name="Kara-Kalpak" /> + <iso_639_entry + iso_639_2B_code="kab" + iso_639_2T_code="kab" + name="Kabyle" /> + <iso_639_entry + iso_639_2B_code="kac" + iso_639_2T_code="kac" + name="Kachin" /> + <iso_639_entry + iso_639_2B_code="kal" + iso_639_2T_code="kal" + iso_639_1_code="kl" + name="Greenlandic (Kalaallisut)" /> + <iso_639_entry + iso_639_2B_code="kam" + iso_639_2T_code="kam" + name="Kamba" /> + <iso_639_entry + iso_639_2B_code="kan" + iso_639_2T_code="kan" + iso_639_1_code="kn" + name="Kannada" /> + <iso_639_entry + iso_639_2B_code="kar" + iso_639_2T_code="kar" + name="Karen" /> + <iso_639_entry + iso_639_2B_code="kas" + iso_639_2T_code="kas" + iso_639_1_code="ks" + name="Kashmiri" /> + <iso_639_entry + iso_639_2B_code="kau" + iso_639_2T_code="kau" + iso_639_1_code="kr" + name="Kanuri" /> + <iso_639_entry + iso_639_2B_code="kaw" + iso_639_2T_code="kaw" + name="Kawi" /> + <iso_639_entry + iso_639_2B_code="kaz" + iso_639_2T_code="kaz" + iso_639_1_code="kk" + name="Kazakh" /> + <iso_639_entry + iso_639_2B_code="kbd" + iso_639_2T_code="kbd" + name="Kabardian" /> + <iso_639_entry + iso_639_2B_code="kha" + iso_639_2T_code="kha" + name="Khazi" /> + <iso_639_entry + iso_639_2B_code="khi" + iso_639_2T_code="khi" + name="Khoisan (Other)" /> + <iso_639_entry + iso_639_2B_code="khm" + iso_639_2T_code="khm" + iso_639_1_code="km" + name="Khmer" /> + <iso_639_entry + iso_639_2B_code="kho" + iso_639_2T_code="kho" + name="Khotanese" /> + <iso_639_entry + iso_639_2B_code="kik" + iso_639_2T_code="kik" + iso_639_1_code="ki" + name="Kikuyu" /> + <iso_639_entry + iso_639_2B_code="kin" + iso_639_2T_code="kin" + iso_639_1_code="rw" + name="Kinyarwanda" /> + <iso_639_entry + iso_639_2B_code="kir" + iso_639_2T_code="kir" + iso_639_1_code="ky" + name="Kirghiz" /> + <iso_639_entry + iso_639_2B_code="kmb" + iso_639_2T_code="kmb" + name="Kimbundu" /> + <iso_639_entry + iso_639_2B_code="kok" + iso_639_2T_code="kok" + name="Konkani" /> + <iso_639_entry + iso_639_2B_code="kom" + iso_639_2T_code="kom" + iso_639_1_code="kv" + name="Komi" /> + <iso_639_entry + iso_639_2B_code="kon" + iso_639_2T_code="kon" + iso_639_1_code="kg" + name="Kongo" /> + <iso_639_entry + iso_639_2B_code="kor" + iso_639_2T_code="kor" + iso_639_1_code="ko" + name="Korean" /> + <iso_639_entry + iso_639_2B_code="kos" + iso_639_2T_code="kos" + name="Kosraean" /> + <iso_639_entry + iso_639_2B_code="kpe" + iso_639_2T_code="kpe" + name="Kpelle" /> + <iso_639_entry + iso_639_2B_code="krc" + iso_639_2T_code="krc" + name="Karachay-Balkar" /> + <iso_639_entry + iso_639_2B_code="kro" + iso_639_2T_code="kro" + name="Kru" /> + <iso_639_entry + iso_639_2B_code="kru" + iso_639_2T_code="kru" + name="Kurukh" /> + <iso_639_entry + iso_639_2B_code="kua" + iso_639_2T_code="kua" + iso_639_1_code="kj" + name="Kuanyama" /> + <iso_639_entry + iso_639_2B_code="kum" + iso_639_2T_code="kum" + name="Kumyk" /> + <iso_639_entry + iso_639_2B_code="kur" + iso_639_2T_code="kur" + iso_639_1_code="ku" + name="Kurdish" /> + <iso_639_entry + iso_639_2B_code="kut" + iso_639_2T_code="kut" + name="Kutenai" /> + <iso_639_entry + iso_639_2B_code="lad" + iso_639_2T_code="lad" + name="Ladino" /> + <iso_639_entry + iso_639_2B_code="lah" + iso_639_2T_code="lah" + name="Lahnda" /> + <iso_639_entry + iso_639_2B_code="lam" + iso_639_2T_code="lam" + name="Lamba" /> + <iso_639_entry + iso_639_2B_code="lao" + iso_639_2T_code="lao" + iso_639_1_code="lo" + name="Lao" /> + <iso_639_entry + iso_639_2B_code="lat" + iso_639_2T_code="lat" + iso_639_1_code="la" + name="Latin" /> + <iso_639_entry + iso_639_2B_code="lav" + iso_639_2T_code="lav" + iso_639_1_code="lv" + name="Latvian" /> + <iso_639_entry + iso_639_2B_code="lez" + iso_639_2T_code="lez" + name="Lezghian" /> + <iso_639_entry + iso_639_2B_code="lim" + iso_639_2T_code="lim" + iso_639_1_code="li" + name="Limburgian" /> + <iso_639_entry + iso_639_2B_code="lin" + iso_639_2T_code="lin" + iso_639_1_code="ln" + name="Lingala" /> + <iso_639_entry + iso_639_2B_code="lit" + iso_639_2T_code="lit" + iso_639_1_code="lt" + name="Lithuanian" /> + <iso_639_entry + iso_639_2B_code="lol" + iso_639_2T_code="lol" + name="Mongo" /> + <iso_639_entry + iso_639_2B_code="loz" + iso_639_2T_code="loz" + name="Lozi" /> + <iso_639_entry + iso_639_2B_code="ltz" + iso_639_2T_code="ltz" + iso_639_1_code="lb" + name="Luxembourgish" /> + <iso_639_entry + iso_639_2B_code="lua" + iso_639_2T_code="lua" + name="Luba-Lulua" /> + <iso_639_entry + iso_639_2B_code="lub" + iso_639_2T_code="lub" + iso_639_1_code="lu" + name="Luba-Katanga" /> + <iso_639_entry + iso_639_2B_code="lug" + iso_639_2T_code="lug" + iso_639_1_code="lg" + name="Ganda" /> + <iso_639_entry + iso_639_2B_code="lui" + iso_639_2T_code="lui" + name="Luiseno" /> + <iso_639_entry + iso_639_2B_code="lun" + iso_639_2T_code="lun" + name="Lunda" /> + <iso_639_entry + iso_639_2B_code="luo" + iso_639_2T_code="luo" + name="Luo (Kenya and Tanzania)" /> + <iso_639_entry + iso_639_2B_code="lus" + iso_639_2T_code="lus" + name="Lushai" /> + <iso_639_entry + iso_639_2B_code="mac" + iso_639_2T_code="mkd" + iso_639_1_code="mk" + name="Macedonian" /> + <iso_639_entry + iso_639_2B_code="mad" + iso_639_2T_code="mad" + name="Madurese" /> + <iso_639_entry + iso_639_2B_code="mag" + iso_639_2T_code="mag" + name="Magahi" /> + <iso_639_entry + iso_639_2B_code="mah" + iso_639_2T_code="mah" + iso_639_1_code="mh" + name="Marshallese" /> + <iso_639_entry + iso_639_2B_code="mai" + iso_639_2T_code="mai" + name="Maithili" /> + <iso_639_entry + iso_639_2B_code="mak" + iso_639_2T_code="mak" + name="Makasar" /> + <iso_639_entry + iso_639_2B_code="mal" + iso_639_2T_code="mal" + iso_639_1_code="ml" + name="Malayalam" /> + <iso_639_entry + iso_639_2B_code="man" + iso_639_2T_code="man" + name="Mandingo" /> + <iso_639_entry + iso_639_2B_code="mao" + iso_639_2T_code="mri" + iso_639_1_code="mi" + name="Maori" /> + <iso_639_entry + iso_639_2B_code="map" + iso_639_2T_code="map" + name="Austronesian (Other)" /> + <iso_639_entry + iso_639_2B_code="mar" + iso_639_2T_code="mar" + iso_639_1_code="mr" + name="Marathi" /> + <iso_639_entry + iso_639_2B_code="mas" + iso_639_2T_code="mas" + name="Masai" /> + <iso_639_entry + iso_639_2B_code="may" + iso_639_2T_code="msa" + iso_639_1_code="ms" + name="Malay" /> + <iso_639_entry + iso_639_2B_code="mdf" + iso_639_2T_code="mdf" + name="Moksha" /> + <iso_639_entry + iso_639_2B_code="mdr" + iso_639_2T_code="mdr" + name="Mandar" /> + <iso_639_entry + iso_639_2B_code="men" + iso_639_2T_code="men" + name="Mende" /> + <iso_639_entry + iso_639_2B_code="mga" + iso_639_2T_code="mga" + name="Irish, Middle (900-1200)" /> + <iso_639_entry + iso_639_2B_code="mic" + iso_639_2T_code="mic" + name="Mi'kmaq; Micmac" /> + <iso_639_entry + iso_639_2B_code="min" + iso_639_2T_code="min" + name="Minangkabau" /> + <iso_639_entry + iso_639_2B_code="mis" + iso_639_2T_code="mis" + name="Miscellaneous languages" /> + <iso_639_entry + iso_639_2B_code="mkh" + iso_639_2T_code="mkh" + name="Mon-Khmer (Other)" /> + <iso_639_entry + iso_639_2B_code="mlg" + iso_639_2T_code="mlg" + iso_639_1_code="mg" + name="Malagasy" /> + <iso_639_entry + iso_639_2B_code="mlt" + iso_639_2T_code="mlt" + iso_639_1_code="mt" + name="Maltese" /> + <iso_639_entry + iso_639_2B_code="mnc" + iso_639_2T_code="mnc" + name="Manchu" /> + <iso_639_entry + iso_639_2B_code="mno" + iso_639_2T_code="mno" + name="Manobo languages" /> + <iso_639_entry + iso_639_2B_code="moh" + iso_639_2T_code="moh" + name="Mohawk" /> + <iso_639_entry + iso_639_2B_code="mol" + iso_639_2T_code="mol" + iso_639_1_code="mo" + name="Moldavian" /> + <iso_639_entry + iso_639_2B_code="mon" + iso_639_2T_code="mon" + iso_639_1_code="mn" + name="Mongolian" /> + <iso_639_entry + iso_639_2B_code="mos" + iso_639_2T_code="mos" + name="Mossi" /> + <iso_639_entry + iso_639_2B_code="mul" + iso_639_2T_code="mul" + name="Multiple languages" /> + <iso_639_entry + iso_639_2B_code="mun" + iso_639_2T_code="mun" + name="Munda languages" /> + <iso_639_entry + iso_639_2B_code="mus" + iso_639_2T_code="mus" + name="Creek" /> + <iso_639_entry + iso_639_2B_code="mwl" + iso_639_2T_code="mwl" + name="Mirandese" /> + <iso_639_entry + iso_639_2B_code="mwr" + iso_639_2T_code="mwr" + name="Marwari" /> + <iso_639_entry + iso_639_2B_code="myn" + iso_639_2T_code="myn" + name="Mayan languages" /> + <iso_639_entry + iso_639_2B_code="myv" + iso_639_2T_code="myv" + name="Erzya" /> + <iso_639_entry + iso_639_2B_code="nah" + iso_639_2T_code="nah" + name="Nahuatl" /> + <iso_639_entry + iso_639_2B_code="nai" + iso_639_2T_code="nai" + name="North American Indian (Other)" /> + <iso_639_entry + iso_639_2B_code="nap" + iso_639_2T_code="nap" + name="Neapolitan" /> + <iso_639_entry + iso_639_2B_code="nau" + iso_639_2T_code="nau" + iso_639_1_code="na" + name="Nauru" /> + <iso_639_entry + iso_639_2B_code="nav" + iso_639_2T_code="nav" + iso_639_1_code="nv" + name="Navaho" /> + <iso_639_entry + iso_639_2B_code="nbl" + iso_639_2T_code="nbl" + iso_639_1_code="nr" + name="Ndebele, South" /> + <iso_639_entry + iso_639_2B_code="nde" + iso_639_2T_code="nde" + iso_639_1_code="nd" + name="Ndebele, North" /> + <iso_639_entry + iso_639_2B_code="ndo" + iso_639_2T_code="ndo" + iso_639_1_code="ng" + name="Ndonga" /> + <iso_639_entry + iso_639_2B_code="nds" + iso_639_2T_code="nds" + name="German, Low" /> + <iso_639_entry + iso_639_2B_code="nep" + iso_639_2T_code="nep" + iso_639_1_code="ne" + name="Nepali" /> + <iso_639_entry + iso_639_2B_code="new" + iso_639_2T_code="new" + name="Newari" /> + <iso_639_entry + iso_639_2B_code="nia" + iso_639_2T_code="nia" + name="Nias" /> + <iso_639_entry + iso_639_2B_code="nic" + iso_639_2T_code="nic" + name="Niger-Kordofanian (Other)" /> + <iso_639_entry + iso_639_2B_code="niu" + iso_639_2T_code="niu" + name="Niuean" /> + <iso_639_entry + iso_639_2B_code="nno" + iso_639_2T_code="nno" + iso_639_1_code="nn" + name="Norwegian Nynorsk" /> + <iso_639_entry + iso_639_2B_code="nob" + iso_639_2T_code="nob" + iso_639_1_code="nb" + name="BokmÃ¥l, Norwegian" /> + <iso_639_entry + iso_639_2B_code="nog" + iso_639_2T_code="nog" + name="Nogai" /> + <iso_639_entry + iso_639_2B_code="non" + iso_639_2T_code="non" + name="Norse, Old" /> + <iso_639_entry + iso_639_2B_code="nor" + iso_639_2T_code="nor" + iso_639_1_code="no" + name="Norwegian" /> + <iso_639_entry + iso_639_2B_code="nso" + iso_639_2T_code="nso" + name="Northern Sotho; Pedi; Sepedi" /> + <iso_639_entry + iso_639_2B_code="nub" + iso_639_2T_code="nub" + name="Nubian languages" /> + <iso_639_entry + iso_639_2B_code="nym" + iso_639_2T_code="nym" + name="Nyamwezi" /> + <iso_639_entry + iso_639_2B_code="nwc" + iso_639_2T_code="nwc" + name="Classical Newari; Old Newari" /> + <iso_639_entry + iso_639_2B_code="nya" + iso_639_2T_code="nya" + iso_639_1_code="ny" + name="Chewa; Chichewa; Nyanja" /> + <iso_639_entry + iso_639_2B_code="nyn" + iso_639_2T_code="nyn" + name="Nyankole" /> + <iso_639_entry + iso_639_2B_code="nyo" + iso_639_2T_code="nyo" + name="Nyoro" /> + <iso_639_entry + iso_639_2B_code="nzi" + iso_639_2T_code="nzi" + name="Nzima" /> + <iso_639_entry + iso_639_2B_code="oci" + iso_639_2T_code="oci" + iso_639_1_code="oc" + name="Occitan (post 1500)" /> + <iso_639_entry + iso_639_2B_code="oji" + iso_639_2T_code="oji" + iso_639_1_code="oj" + name="Ojibwa" /> + <iso_639_entry + iso_639_2B_code="ori" + iso_639_2T_code="ori" + iso_639_1_code="or" + name="Oriya" /> + <iso_639_entry + iso_639_2B_code="orm" + iso_639_2T_code="orm" + iso_639_1_code="om" + name="Oromo" /> + <iso_639_entry + iso_639_2B_code="osa" + iso_639_2T_code="osa" + name="Osage" /> + <iso_639_entry + iso_639_2B_code="oss" + iso_639_2T_code="oss" + iso_639_1_code="os" + name="Ossetian" /> + <iso_639_entry + iso_639_2B_code="ota" + iso_639_2T_code="ota" + name="Turkish, Ottoman (1500-1928)" /> + <iso_639_entry + iso_639_2B_code="oto" + iso_639_2T_code="oto" + name="Otomian languages" /> + <iso_639_entry + iso_639_2B_code="paa" + iso_639_2T_code="paa" + name="Papuan (Other)" /> + <iso_639_entry + iso_639_2B_code="pag" + iso_639_2T_code="pag" + name="Pangasinan" /> + <iso_639_entry + iso_639_2B_code="pal" + iso_639_2T_code="pal" + name="Pahlavi" /> + <iso_639_entry + iso_639_2B_code="pam" + iso_639_2T_code="pam" + name="Pampanga" /> + <iso_639_entry + iso_639_2B_code="pan" + iso_639_2T_code="pan" + iso_639_1_code="pa" + name="Punjabi" /> + <iso_639_entry + iso_639_2B_code="pap" + iso_639_2T_code="pap" + name="Papiamento" /> + <iso_639_entry + iso_639_2B_code="pau" + iso_639_2T_code="pau" + name="Palauan" /> + <iso_639_entry + iso_639_2B_code="peo" + iso_639_2T_code="peo" + name="Persian, Old (ca.600-400 B.C.)" /> + <iso_639_entry + iso_639_2B_code="per" + iso_639_2T_code="fas" + iso_639_1_code="fa" + name="Persian" /> + <iso_639_entry + iso_639_2B_code="phi" + iso_639_2T_code="phi" + name="Philippine (Other)" /> + <iso_639_entry + iso_639_2B_code="phn" + iso_639_2T_code="phn" + name="Phoenician" /> + <iso_639_entry + iso_639_2B_code="pli" + iso_639_2T_code="pli" + iso_639_1_code="pi" + name="Pali" /> + <iso_639_entry + iso_639_2B_code="pol" + iso_639_2T_code="pol" + iso_639_1_code="pl" + name="Polish" /> + <iso_639_entry + iso_639_2B_code="por" + iso_639_2T_code="por" + iso_639_1_code="pt" + name="Portuguese" /> + <iso_639_entry + iso_639_2B_code="pon" + iso_639_2T_code="pon" + name="Pohnpeian" /> + <iso_639_entry + iso_639_2B_code="pra" + iso_639_2T_code="pra" + name="Prakrit languages" /> + <iso_639_entry + iso_639_2B_code="pro" + iso_639_2T_code="pro" + name="Provençal, Old (to 1500)" /> + <iso_639_entry + iso_639_2B_code="pus" + iso_639_2T_code="pus" + iso_639_1_code="ps" + name="Pushto" /> + <iso_639_entry + iso_639_2B_code="que" + iso_639_2T_code="que" + iso_639_1_code="qu" + name="Quechua" /> + <iso_639_entry + iso_639_2B_code="raj" + iso_639_2T_code="raj" + name="Rajasthani" /> + <iso_639_entry + iso_639_2B_code="rap" + iso_639_2T_code="rap" + name="Rapanui" /> + <iso_639_entry + iso_639_2B_code="rar" + iso_639_2T_code="rar" + name="Rarotongan" /> + <iso_639_entry + iso_639_2B_code="roa" + iso_639_2T_code="roa" + name="Romance (Other)" /> + <iso_639_entry + iso_639_2B_code="roh" + iso_639_2T_code="roh" + iso_639_1_code="rm" + name="Raeto-Romance" /> + <iso_639_entry + iso_639_2B_code="rom" + iso_639_2T_code="rom" + name="Romany" /> + <iso_639_entry + iso_639_2B_code="rum" + iso_639_2T_code="ron" + iso_639_1_code="ro" + name="Romanian" /> + <iso_639_entry + iso_639_2B_code="run" + iso_639_2T_code="run" + iso_639_1_code="rn" + name="Rundi" /> + <iso_639_entry + iso_639_2B_code="rus" + iso_639_2T_code="rus" + iso_639_1_code="ru" + name="Russian" /> + <iso_639_entry + iso_639_2B_code="sad" + iso_639_2T_code="sad" + name="Sandawe" /> + <iso_639_entry + iso_639_2B_code="sag" + iso_639_2T_code="sag" + iso_639_1_code="sg" + name="Sango" /> + <iso_639_entry + iso_639_2B_code="sah" + iso_639_2T_code="sah" + name="Yakut" /> + <iso_639_entry + iso_639_2B_code="sai" + iso_639_2T_code="sai" + name="South American Indian (Other)" /> + <iso_639_entry + iso_639_2B_code="sal" + iso_639_2T_code="sal" + name="Salishan languages" /> + <iso_639_entry + iso_639_2B_code="sam" + iso_639_2T_code="sam" + name="Samaritan Aramaic" /> + <iso_639_entry + iso_639_2B_code="san" + iso_639_2T_code="san" + iso_639_1_code="sa" + name="Sanskrit" /> + <iso_639_entry + iso_639_2B_code="sas" + iso_639_2T_code="sas" + name="Sasak" /> + <iso_639_entry + iso_639_2B_code="sat" + iso_639_2T_code="sat" + name="Santali" /> + <iso_639_entry + iso_639_2B_code="scc" + iso_639_2T_code="srp" + iso_639_1_code="sr" + name="Serbian" /> + <iso_639_entry + iso_639_2B_code="scn" + iso_639_2T_code="scn" + name="Sicilian" /> + <iso_639_entry + iso_639_2B_code="sco" + iso_639_2T_code="sco" + name="Scots" /> + <iso_639_entry + iso_639_2B_code="scr" + iso_639_2T_code="hrv" + iso_639_1_code="hr" + name="Croatian" /> + <iso_639_entry + iso_639_2B_code="sel" + iso_639_2T_code="sel" + name="Selkup" /> + <iso_639_entry + iso_639_2B_code="sem" + iso_639_2T_code="sem" + name="Semitic (Other)" /> + <iso_639_entry + iso_639_2B_code="sga" + iso_639_2T_code="sga" + name="Irish, Old (to 900)" /> + <iso_639_entry + iso_639_2B_code="sgn" + iso_639_2T_code="sgn" + name="Sign languages" /> + <iso_639_entry + iso_639_2B_code="shn" + iso_639_2T_code="shn" + name="Shan" /> + <iso_639_entry + iso_639_2B_code="sid" + iso_639_2T_code="sid" + name="Sidamo" /> + <iso_639_entry + iso_639_2B_code="sin" + iso_639_2T_code="sin" + iso_639_1_code="si" + name="Sinhala; Sinhalese" /> + <iso_639_entry + iso_639_2B_code="sio" + iso_639_2T_code="sio" + name="Siouan languages" /> + <iso_639_entry + iso_639_2B_code="sit" + iso_639_2T_code="sit" + name="Sino-Tibetan (Other)" /> + <iso_639_entry + iso_639_2B_code="sla" + iso_639_2T_code="sla" + name="Slavic (Other)" /> + <iso_639_entry + iso_639_2B_code="slo" + iso_639_2T_code="slk" + iso_639_1_code="sk" + name="Slovak" /> + <iso_639_entry + iso_639_2B_code="slv" + iso_639_2T_code="slv" + iso_639_1_code="sl" + name="Slovenian" /> + <iso_639_entry + iso_639_2B_code="sma" + iso_639_2T_code="sma" + name="Southern Sami" /> + <iso_639_entry + iso_639_2B_code="sme" + iso_639_2T_code="sme" + iso_639_1_code="se" + name="Northern Sami" /> + <iso_639_entry + iso_639_2B_code="smi" + iso_639_2T_code="smi" + name="Sami languages (Other)" /> + <iso_639_entry + iso_639_2B_code="smj" + iso_639_2T_code="smj" + name="Lule Sami" /> + <iso_639_entry + iso_639_2B_code="smn" + iso_639_2T_code="smn" + name="Inari Sami" /> + <iso_639_entry + iso_639_2B_code="smo" + iso_639_2T_code="smo" + iso_639_1_code="sm" + name="Samoan" /> + <iso_639_entry + iso_639_2B_code="sms" + iso_639_2T_code="sms" + name="Skolt Sami" /> + <iso_639_entry + iso_639_2B_code="sna" + iso_639_2T_code="sna" + iso_639_1_code="sn" + name="Shona" /> + <iso_639_entry + iso_639_2B_code="snd" + iso_639_2T_code="snd" + iso_639_1_code="sd" + name="Sindhi" /> + <iso_639_entry + iso_639_2B_code="snk" + iso_639_2T_code="snk" + name="Soninke" /> + <iso_639_entry + iso_639_2B_code="sog" + iso_639_2T_code="sog" + name="Sogdian" /> + <iso_639_entry + iso_639_2B_code="som" + iso_639_2T_code="som" + iso_639_1_code="so" + name="Somali" /> + <iso_639_entry + iso_639_2B_code="son" + iso_639_2T_code="son" + name="Songhai" /> + <iso_639_entry + iso_639_2B_code="sot" + iso_639_2T_code="sot" + iso_639_1_code="st" + name="Sotho, Southern" /> + <iso_639_entry + iso_639_2B_code="spa" + iso_639_2T_code="spa" + iso_639_1_code="es" + name="Spanish" /> + <iso_639_entry + iso_639_2B_code="srd" + iso_639_2T_code="srd" + iso_639_1_code="sc" + name="Sardinian" /> + <iso_639_entry + iso_639_2B_code="srr" + iso_639_2T_code="srr" + name="Serer" /> + <iso_639_entry + iso_639_2B_code="ssa" + iso_639_2T_code="ssa" + name="Nilo-Saharan (Other)" /> + <iso_639_entry + iso_639_2B_code="ssw" + iso_639_2T_code="ssw" + iso_639_1_code="ss" + name="Swati" /> + <iso_639_entry + iso_639_2B_code="suk" + iso_639_2T_code="suk" + name="Sukuma" /> + <iso_639_entry + iso_639_2B_code="sun" + iso_639_2T_code="sun" + iso_639_1_code="su" + name="Sundanese" /> + <iso_639_entry + iso_639_2B_code="sus" + iso_639_2T_code="sus" + name="Susu" /> + <iso_639_entry + iso_639_2B_code="sux" + iso_639_2T_code="sux" + name="Sumerian" /> + <iso_639_entry + iso_639_2B_code="swa" + iso_639_2T_code="swa" + iso_639_1_code="sw" + name="Swahili" /> + <iso_639_entry + iso_639_2B_code="swe" + iso_639_2T_code="swe" + iso_639_1_code="sv" + name="Swedish" /> + <iso_639_entry + iso_639_2B_code="syr" + iso_639_2T_code="syr" + name="Syriac" /> + <iso_639_entry + iso_639_2B_code="tah" + iso_639_2T_code="tah" + iso_639_1_code="ty" + name="Tahitian" /> + <iso_639_entry + iso_639_2B_code="tai" + iso_639_2T_code="tai" + name="Tai (Other)" /> + <iso_639_entry + iso_639_2B_code="tam" + iso_639_2T_code="tam" + iso_639_1_code="ta" + name="Tamil" /> + <iso_639_entry + iso_639_2B_code="tso" + iso_639_2T_code="tso" + iso_639_1_code="ts" + name="Tsonga" /> + <iso_639_entry + iso_639_2B_code="tat" + iso_639_2T_code="tat" + iso_639_1_code="tt" + name="Tatar" /> + <iso_639_entry + iso_639_2B_code="tel" + iso_639_2T_code="tel" + iso_639_1_code="te" + name="Telugu" /> + <iso_639_entry + iso_639_2B_code="tem" + iso_639_2T_code="tem" + name="Timne" /> + <iso_639_entry + iso_639_2B_code="ter" + iso_639_2T_code="ter" + name="Tereno" /> + <iso_639_entry + iso_639_2B_code="tet" + iso_639_2T_code="tet" + name="Tetum" /> + <iso_639_entry + iso_639_2B_code="tgk" + iso_639_2T_code="tgk" + iso_639_1_code="tg" + name="Tajik" /> + <iso_639_entry + iso_639_2B_code="tgl" + iso_639_2T_code="tgl" + iso_639_1_code="tl" + name="Tagalog" /> + <iso_639_entry + iso_639_2B_code="tha" + iso_639_2T_code="tha" + iso_639_1_code="th" + name="Thai" /> + <iso_639_entry + iso_639_2B_code="tib" + iso_639_2T_code="bod" + iso_639_1_code="bo" + name="Tibetan" /> + <iso_639_entry + iso_639_2B_code="tig" + iso_639_2T_code="tig" + name="Tigre" /> + <iso_639_entry + iso_639_2B_code="tir" + iso_639_2T_code="tir" + iso_639_1_code="ti" + name="Tigrinya" /> + <iso_639_entry + iso_639_2B_code="tiv" + iso_639_2T_code="tiv" + name="Tiv" /> + <iso_639_entry + iso_639_2B_code="tlh" + iso_639_2T_code="tlh" + name="Klingon; tlhIngan-Hol" /> + <iso_639_entry + iso_639_2B_code="tkl" + iso_639_2T_code="tkl" + name="Tokelau" /> + <iso_639_entry + iso_639_2B_code="tli" + iso_639_2T_code="tli" + name="Tlinglit" /> + <iso_639_entry + iso_639_2B_code="tmh" + iso_639_2T_code="tmh" + name="Tamashek" /> + <iso_639_entry + iso_639_2B_code="tog" + iso_639_2T_code="tog" + name="Tonga (Nyasa)" /> + <iso_639_entry + iso_639_2B_code="ton" + iso_639_2T_code="ton" + iso_639_1_code="to" + name="Tonga (Tonga Islands)" /> + <iso_639_entry + iso_639_2B_code="tpi" + iso_639_2T_code="tpi" + name="Tok Pisin" /> + <iso_639_entry + iso_639_2B_code="tsi" + iso_639_2T_code="tsi" + name="Tsimshian" /> + <iso_639_entry + iso_639_2B_code="tsn" + iso_639_2T_code="tsn" + iso_639_1_code="tn" + name="Tswana" /> + <iso_639_entry + iso_639_2B_code="tuk" + iso_639_2T_code="tuk" + iso_639_1_code="tk" + name="Turkmen" /> + <iso_639_entry + iso_639_2B_code="tum" + iso_639_2T_code="tum" + name="Tumbuka" /> + <iso_639_entry + iso_639_2B_code="tup" + iso_639_2T_code="tup" + name="Tupi languages" /> + <iso_639_entry + iso_639_2B_code="tur" + iso_639_2T_code="tur" + iso_639_1_code="tr" + name="Turkish" /> + <iso_639_entry + iso_639_2B_code="tut" + iso_639_2T_code="tut" + name="Altaic (Other)" /> + <iso_639_entry + iso_639_2B_code="tvl" + iso_639_2T_code="tvl" + name="Tuvalu" /> + <iso_639_entry + iso_639_2B_code="twi" + iso_639_2T_code="twi" + iso_639_1_code="tw" + name="Twi" /> + <iso_639_entry + iso_639_2B_code="tyv" + iso_639_2T_code="tyv" + name="Tuvinian" /> + <iso_639_entry + iso_639_2B_code="udm" + iso_639_2T_code="udm" + name="Udmurt" /> + <iso_639_entry + iso_639_2B_code="uga" + iso_639_2T_code="uga" + name="Ugaritic" /> + <iso_639_entry + iso_639_2B_code="uig" + iso_639_2T_code="uig" + iso_639_1_code="ug" + name="Uighur" /> + <iso_639_entry + iso_639_2B_code="ukr" + iso_639_2T_code="ukr" + iso_639_1_code="uk" + name="Ukrainian" /> + <iso_639_entry + iso_639_2B_code="umb" + iso_639_2T_code="umb" + name="Umbundu" /> + <iso_639_entry + iso_639_2B_code="und" + iso_639_2T_code="und" + name="Undetermined" /> + <iso_639_entry + iso_639_2B_code="urd" + iso_639_2T_code="urd" + ido_639_1_code="ur" + name="Urdu" /> + <iso_639_entry + iso_639_2B_code="uzb" + iso_639_2T_code="uzb" + iso_639_1_code="uz" + name="Uzbek" /> + <iso_639_entry + iso_639_2B_code="vai" + iso_639_2T_code="vai" + name="Vai" /> + <iso_639_entry + iso_639_2B_code="ven" + iso_639_2T_code="ven" + iso_639_1_code="ve" + name="Venda" /> + <iso_639_entry + iso_639_2B_code="vie" + iso_639_2T_code="vie" + iso_639_1_code="vi" + name="Vietnamese" /> + <iso_639_entry + iso_639_2B_code="vol" + iso_639_2T_code="vol" + iso_639_1_code="vo" + name="Volapuk" /> + <iso_639_entry + iso_639_2B_code="vot" + iso_639_2T_code="vot" + name="Votic" /> + <iso_639_entry + iso_639_2B_code="wak" + iso_639_2T_code="wak" + name="Wakashan languages" /> + <iso_639_entry + iso_639_2B_code="wal" + iso_639_2T_code="wal" + name="Walamo" /> + <iso_639_entry + iso_639_2B_code="war" + iso_639_2T_code="war" + name="Waray" /> + <iso_639_entry + iso_639_2B_code="was" + iso_639_2T_code="was" + name="Washo" /> + <iso_639_entry + iso_639_2B_code="wel" + iso_639_2T_code="cym" + iso_639_1_code="cy" + name="Welsh" /> + <iso_639_entry + iso_639_2B_code="wen" + iso_639_2T_code="wen" + name="Sorbian languages" /> + <iso_639_entry + iso_639_2B_code="wln" + iso_639_2T_code="wln" + iso_639_1_code="wa" + name="Walloon" /> + <iso_639_entry + iso_639_2B_code="wol" + iso_639_2T_code="wol" + iso_639_1_code="wo" + name="Wolof" /> + <iso_639_entry + iso_639_2B_code="xal" + iso_639_2T_code="xal" + name="Kalmyk" /> + <iso_639_entry + iso_639_2B_code="xho" + iso_639_2T_code="xho" + iso_639_1_code="xh" + name="Xhosa" /> + <iso_639_entry + iso_639_2B_code="yao" + iso_639_2T_code="yao" + name="Yao" /> + <iso_639_entry + iso_639_2B_code="yap" + iso_639_2T_code="yap" + name="Yapese" /> + <iso_639_entry + iso_639_2B_code="yid" + iso_639_2T_code="yid" + iso_639_1_code="yi" + name="Yiddish" /> + <iso_639_entry + iso_639_2B_code="yor" + iso_639_2T_code="yor" + iso_639_1_code="yo" + name="Yoruba" /> + <iso_639_entry + iso_639_2B_code="ypk" + iso_639_2T_code="ypk" + name="Yupik languages" /> + <iso_639_entry + iso_639_2B_code="zap" + iso_639_2T_code="zap" + name="Zapotec" /> + <iso_639_entry + iso_639_2B_code="zen" + iso_639_2T_code="zen" + name="Zenaga" /> + <iso_639_entry + iso_639_2B_code="zha" + iso_639_2T_code="zha" + iso_639_1_code="za" + name="Chuang; Zhuang" /> + <iso_639_entry + iso_639_2B_code="znd" + iso_639_2T_code="znd" + name="Zande" /> + <iso_639_entry + iso_639_2B_code="zul" + iso_639_2T_code="zul" + iso_639_1_code="zu" + name="Zulu" /> + <iso_639_entry + iso_639_2B_code="zun" + iso_639_2T_code="zun" + name="Zuni" /> +</iso_639_entries> diff --git a/scripts/languages.txt b/scripts/languages.txt index d623f6c..6378dcc 100755..100644 --- a/scripts/languages.txt +++ b/scripts/languages.txt @@ -10,8 +10,6 @@ afr Afrikaans aka Akan akk Akkadian alb Albanian -ale Aleut -alg Algonquian languages amh Amharic ang English, Old (ca.450-1100) apa Apache languages @@ -95,12 +93,10 @@ crh Crimean Turkish; Crimean Tatar crp Creoles and pidgins (Other) csb Kashubian cus Cushitic (Other) -cus Portuguese-based (Other) cze Czech dak Dakota dan Danish dar Dargwa -dsb Lower Sorbian del Delaware den Slave (Athapascan) dgr Dogrib @@ -108,6 +104,7 @@ din Dinka div Divehi doi Dogri dra Dravidian (Other) +dsb Lower Sorbian dua Duala dum Dutch, Middle (ca. 1050-1350) dut Dutch @@ -127,6 +124,7 @@ fan Fang fao Faroese fat Fanti fij Fijian +fil Filipino; Pilipino fin Finnish fiu Finno-Ugrian (Other) fon Fon @@ -262,12 +260,12 @@ mao Maori map Austronesian (Other) mar Marathi mas Masai -mal Malay +may Malay mdf Moksha mdr Mandar men Mende mga Irish, Middle (900-1200) -mic Micmac +mic Mi'kmaq; Micmac min Minangkabau mis Miscellaneous languages mkh Mon-Khmer (Other) @@ -282,6 +280,7 @@ mos Mossi mul Multiple languages mun Munda languages mus Creek +mwl Mirandese mwr Marwari myn Mayan languages myv Erzya @@ -300,15 +299,16 @@ nia Nias nic Niger-Kordofanian (Other) niu Niuean nno Norwegian Nynorsk -nob Bøkmal, Norwegian +nob BokmÃ¥l, Norwegian nog Nogai non Norse, Old nor Norwegian -nso Sotho, Northern +nso Northern Sotho; Pedi; Sepedi nub Nubian languages +nym Nyamwezi nwc Classical Newari; Old Newari nya Chewa; Chichewa; Nyanja -nym Nyankole +nyn Nyankole nyo Nyoro nzi Nzima oci Occitan (post 1500) @@ -335,7 +335,7 @@ pol Polish por Portuguese pon Pohnpeian pra Prakrit languages -pro Proveçal, Old (to 1500) +pro Provençal, Old (to 1500) pus Pushto que Quechua raj Rajasthani @@ -357,6 +357,7 @@ san Sanskrit sas Sasak sat Santali scc Serbian +scn Sicilian sco Scots scr Croatian sel Selkup @@ -365,7 +366,7 @@ sga Irish, Old (to 900) sgn Sign languages shn Shan sid Sidamo -sin Sinhalese +sin Sinhala; Sinhalese sio Siouan languages sit Sino-Tibetan (Other) sla Slavic (Other) @@ -385,7 +386,7 @@ sog Sogdian som Somali son Songhai sot Sotho, Southern -spa Spanish (Castilian) +spa Spanish srd Sardinian srr Serer ssa Nilo-Saharan (Other) @@ -413,8 +414,8 @@ tib Tibetan tig Tigre tir Tigrinya tiv Tiv -tkl Tokelau tlh Klingon; tlhIngan-Hol +tkl Tokelau tli Tlinglit tmh Tamashek tog Tonga (Nyasa) diff --git a/mg_actions.c b/vdr_actions.c index 1f80325..b389e38 100644 --- a/mg_actions.c +++ b/vdr_actions.c @@ -1,15 +1,16 @@ /*! - * \file mg_actions.c + * \file vdr_actions.c * \brief Implements all actions for browsing media libraries within VDR * * \version $Revision: 1.27 $ * \date $Date: 2004-12-25 16:52:35 +0100 (Sat, 25 Dec 2004) $ * \author Wolfgang Rohdewald * \author Responsible author: $Author: wr61 $ * - * $Id: mg_actions.c 276 2004-12-25 15:52:35Z wr61 $ + * $Id: vdr_actions.c 276 2004-12-25 15:52:35Z wr61 $ */ #include <stdio.h> +#include <libintl.h> #include <typeinfo> #include <string> @@ -20,7 +21,7 @@ #include <plugin.h> #include "vdr_setup.h" -#include "mg_actions.h" +#include "vdr_actions.h" #include "vdr_menu.h" #include "i18n.h" #include <vdr/interface.h> @@ -42,6 +43,12 @@ class mgOsdItem : public mgAction, public cOsdItem }; +void +mgAction::setHandle(unsigned int handle) +{ + m_handle = handle; +} + eOSState mgAction::ProcessKey(eKeys key) { @@ -80,6 +87,14 @@ class mgKeyItem : public mgAction, public cMenuEditStraItem eOSState Process(eKeys key); }; +class mgBoolItem: public mgAction, public cMenuEditBoolItem +{ + public: + mgBoolItem(const char *Name,int *Value) : cMenuEditBoolItem(Name, Value) {} + eOSState ProcessKey(eKeys key) { return mgAction::ProcessKey(key); } + eOSState Process(eKeys key); +}; + class mgDoCollEntry : public mgEntry { public: @@ -207,8 +222,9 @@ mgAction::Enabled(mgActions on) mgAction::mgAction() { - m = NULL; - m_osd = NULL; + m = 0; + m_osd = 0; + m_handle = 0; IgnoreNextEvent = false; } @@ -303,7 +319,7 @@ mgAction::Back() void mgEntry::Notify() { - selection()->setPosition(osd()->Current()); + selection()->setPosition(m_handle); selection()->gotoPosition(); osd()->SaveState(); mgAction::Notify(); // only after selection is updated @@ -329,6 +345,8 @@ mgEntry::MenuName(const unsigned int idx,const string value) } else if (selection()->inCollection()) asprintf(&result,"%4d %s%s",idx,value.c_str(),ct); + else if (selection()->isLanguagelist()) + asprintf(&result,"%s%s",dgettext("iso_639",value.c_str()),ct); else asprintf(&result,"%s%s",value.c_str(),ct); return result; @@ -641,7 +659,7 @@ const char * mgToggleSelection::ButtonName () { if (osd ()->UsingCollection) - return tr ("Search"); + return tr ("Browse"); else return tr ("Collections"); } @@ -659,6 +677,18 @@ mgToggleSelection::Execute () osd()->newposition = selection ()->gotoPosition (); } +class mgSync : public mgCommand +{ + public: + void Execute(); + const char *ButtonName() { return tr("Synchronize"); } +}; + +void +mgSync::Execute() +{ + // selection()->Sync("."); +} //! \brief sets the default collection selection class mgSetDefaultCollection:public mgCommand @@ -1198,6 +1228,7 @@ mgAction::Type() if (t == typeid(mgCreateOrder)) return actCreateOrder; if (t == typeid(mgDeleteOrder)) return actDeleteOrder; if (t == typeid(mgEditOrder)) return actEditOrder; + if (t == typeid(mgSync)) return actSync; if (t == typeid(mgExternal0)) return actExternal0; if (t == typeid(mgExternal1)) return actExternal1; if (t == typeid(mgExternal2)) return actExternal2; @@ -1228,6 +1259,12 @@ actGenerateKeyItem(const char *Name, int *Value, int NumStrings, const char * co } mgAction* +actGenerateBoolItem(const char *Name, int *Value) +{ + return new mgBoolItem(Name,Value); +} + +mgAction* actGenerate(const mgActions action) { mgAction * result = NULL; @@ -1253,7 +1290,7 @@ actGenerate(const mgActions action) case actSetButton: result = new mgSetButton;break; case actShowList: result = new mgShowList;break; case actShowCommands: result = new mgShowCommands;break; - case actUnused5: break; + case actSync: result = new mgSync;break; case actSetDefaultCollection: result = new mgSetDefaultCollection;break; case actOrder: result = new mgActOrder;break; case actUnused6: break; @@ -1323,3 +1360,29 @@ mgKeyItem::Process(eKeys key) return cMenuEditStraItem::ProcessKey(key); } + +eOSState +mgBoolItem::Process(eKeys key) +{ + mgMenuOrder *menu = dynamic_cast<mgMenuOrder*>(m); + if (key==kOk) + { + if (menu->ChangeOrder(key)) + return osContinue; + else + { + menu->SaveOrder(); + osd ()->newmenu = NULL; + return osContinue; + } + } else if (key==kBack) + { + osd ()->newmenu = NULL; + return osContinue; + } + if (key==kUp || key==kDown) + if (menu->ChangeOrder(key)) + return osContinue; + return cMenuEditBoolItem::ProcessKey(key); +} + diff --git a/mg_actions.h b/vdr_actions.h index 3994b10..29713f1 100644 --- a/mg_actions.h +++ b/vdr_actions.h @@ -1,5 +1,5 @@ /*! - * \file mg_actions.h + * \file vdr_actions.h * \brief Implements all actions for broswing media libraries within VDR * * \version $Revision: 1.13 $ @@ -7,17 +7,16 @@ * \author Wolfgang Rohdewald * \author Responsible author: $Author: wr61 $ * - * $Id: mg_actions.h 276 2004-12-25 15:52:35Z wr61 $ + * $Id: vdr_actions.h 276 2004-12-25 15:52:35Z wr61 $ */ -#ifndef _MG_ACTIONS_H -#define _MG_ACTIONS_H +#ifndef _VDR_ACTIONS_H +#define _VDR_ACTIONS_H #include <string> #include <osd.h> #include <plugin.h> -#include "i18n.h" using namespace std; @@ -50,7 +49,7 @@ enum mgActions { actShowCommands, actCreateOrder, actDeleteOrder, - actUnused5, //!< order by Genre1/Artist/Album/Title + actSync, actAddAllToDefaultCollection, actAddThisToDefaultCollection, actSetDefaultCollection, @@ -137,6 +136,12 @@ class mgAction * or in muggle ? */ bool IgnoreNextEvent; + + /*! \brief defines a reference. Can be used depending on the + * concrete class type. Currently used only by mgEntry + */ + void setHandle(unsigned int handle); + protected: //! \brief returns the OSD owning the menu owning this item @@ -157,6 +162,8 @@ class mgAction virtual void Notify(); eOSState ProcessKey(eKeys key); virtual eOSState Process(eKeys key) { return osUnknown; } + + unsigned int m_handle; private: mgMainMenu *m_osd; }; @@ -173,6 +180,7 @@ class mgActionWithIntValue: public mgAction //! \brief generate an mgAction for action mgAction* actGenerate(const mgActions action); +mgAction* actGenerateBoolItem(const char *Name, int *Value); mgAction* actGenerateKeyItem(const char *Name, int *Value, int NumStrings, const char * const * Strings); #endif diff --git a/vdr_decoder.c b/vdr_decoder.c index 62e6a6d..5ac9706 100644 --- a/vdr_decoder.c +++ b/vdr_decoder.c @@ -21,7 +21,7 @@ #include <sys/stat.h> #include <sys/vfs.h> -#include "mg_db.h" +#include "mg_selection.h" #include <videodir.h> #include <interface.h> @@ -31,7 +31,7 @@ #include "vdr_decoder.h" #include "vdr_decoder_mp3.h" -extern void showmessage(const char *); +extern void showmessage(const char *,int duration=0); #ifdef HAVE_VORBISFILE #include "vdr_decoder_ogg.h" diff --git a/vdr_decoder_flac.c b/vdr_decoder_flac.c index 2861d4e..fd1ca53 100644 --- a/vdr_decoder_flac.c +++ b/vdr_decoder_flac.c @@ -17,7 +17,7 @@ #include <stdio.h> #include "mg_tools.h" -#include "mg_db.h" +#include "mg_content.h" #include "vdr_setup.h" #include "vdr_decoder_flac.h" diff --git a/vdr_decoder_mp3.c b/vdr_decoder_mp3.c index 2ad6bc3..6569760 100644 --- a/vdr_decoder_mp3.c +++ b/vdr_decoder_mp3.c @@ -29,7 +29,7 @@ #include "vdr_setup.h" #include "mg_tools.h" -#include "mg_db.h" +#include "mg_content.h" #define d(x) x diff --git a/vdr_decoder_ogg.c b/vdr_decoder_ogg.c index 96c44cf..47011eb 100644 --- a/vdr_decoder_ogg.c +++ b/vdr_decoder_ogg.c @@ -21,7 +21,7 @@ #include "vdr_setup.h" -#include "mg_db.h" +#include "mg_content.h" // --- mgOggFile ---------------------------------------------------------------- @@ -248,8 +248,8 @@ mgOggDecoder::mgOggDecoder (mgContentItem * item):mgDecoder (item) mgOggDecoder::~mgOggDecoder () { - delete m_file; clean (); + delete m_file; } @@ -10,6 +10,7 @@ */ #include <stdio.h> +#include <assert.h> #include <typeinfo> #include <string> @@ -225,13 +226,9 @@ mgMainMenu::DumpOrders(mgValmap& nv) mgOrder *o = orders[idx]; if (!o) mgError("DumpOrders:order[%u] is 0",idx); - char *n; - for (unsigned int i=0;i<o->size();i++) - { - asprintf(&n,"order%u.Keys.%d.Type",idx,i); - nv.put(n,int(o->Key(i)->Type())); - free(n); - } + char prefix[20]; + sprintf(prefix,"order%u",idx); + o->DumpState(nv,prefix); } } @@ -262,9 +259,9 @@ mgMainMenu::SaveState() nmain.put("CurrentOrder",m_current_order); DumpOrders(nmain); mgValmap nsel("tree"); - m_treesel.DumpState(nsel); + m_treesel->DumpState(nsel); mgValmap ncol("collection"); - m_collectionsel.DumpState(ncol); + m_collectionsel->DumpState(ncol); nmain.Write(f); nsel.Write(f); ncol.Write(f); @@ -315,25 +312,29 @@ mgMainMenu::mgMainMenu ():cOsdMenu ("",25) UsingCollection = nmain.getbool("UsingCollection"); InitMapFromSetup(nsel); InitMapFromSetup(ncol); - m_treesel.setOrder(getOrder(m_current_order)); - m_treesel.InitFrom (nsel); - m_treesel.CreateCollection(default_collection); + m_treesel = new mgSelection; + m_treesel->setOrder(getOrder(m_current_order)); + m_treesel->InitFrom (nsel); + m_treesel->CreateCollection(default_collection); if (default_collection!=play_collection) - m_treesel.CreateCollection(play_collection); + m_treesel->CreateCollection(play_collection); vector<mgKeyTypes> kt; kt.push_back(keyCollection); + kt.push_back(keyCollectionItem); mgOrder o; o.setKeys(kt); - m_collectionsel.setOrder(&o); - m_collectionsel.InitFrom (ncol); - m_playsel.setOrder(&o); - m_playsel.InitFrom(ncol); + m_collectionsel = new mgSelection; + m_collectionsel->setOrder(&o); + m_collectionsel->InitFrom (ncol); + m_playsel = new mgSelection; + m_playsel->setOrder(&o); + m_playsel->InitFrom(ncol); // initialize - if (m_playsel.level()!=1) + if (m_playsel->level()!=1) { - m_playsel.leave_all(); - m_playsel.enter(play_collection); + m_playsel->leave_all(); + m_playsel->enter(play_collection); } UseNormalSelection (); unsigned int posi = selection()->gotoPosition(); @@ -433,6 +434,9 @@ mgMainMenu::LoadExternalCommands() mgMainMenu::~mgMainMenu() { + delete m_treesel; + delete m_collectionsel; + delete m_playsel; delete m_Status; delete moveselection; delete m_root; @@ -496,7 +500,18 @@ mgMenu::AddSelectionItems (mgSelection *sel,mgActions act) { mgAction *a = GenerateAction(act, actEntry); if (!a) continue; - a->SetText(a->MenuName(i+1,sel->values[i]),false); + const char *name = a->MenuName(i+1,sel->values[i]); + // add incremental filter here +#if 0 + // example: + if (name[0]!='C') + continue; +#endif + // adapt newposition since it refers to position in mgSelection: + if ((signed int)i==osd()->newposition) + osd()->newposition = osd()->Count(); + a->SetText(name,false); + a->setHandle(i); osd()->AddItem(a); } if (osd()->ShowingCollections ()) @@ -593,6 +608,9 @@ mgSubmenu::BuildOsd () AddAction(actClearCollection,on); AddAction(actChooseOrder,on); AddAction(actExportTracklist,on); +#if 0 + AddAction(actSync,on); +#endif cCommand *command; if (osd()->external_commands) { @@ -789,9 +807,9 @@ otherkeys: AddMenu (newmenu,newposition); if (UsingCollection) - forcerefresh |= m_collectionsel.cacheIsEmpty(); + forcerefresh |= m_collectionsel->cacheIsEmpty(); else - forcerefresh |= m_treesel.cacheIsEmpty(); + forcerefresh |= m_treesel->cacheIsEmpty(); forcerefresh |= (newposition>=0); @@ -800,7 +818,7 @@ otherkeys: forcerefresh = false; if (newposition<0) newposition = selection()->gotoPosition(); - Menus.back ()->Display (newposition); + Menus.back ()->Display (); } pr_exit: showMessage(); @@ -828,10 +846,10 @@ mgMainMenu::showMessage() } void -showmessage(const char * msg) +showmessage(const char * msg,int duration) { #if VDRVERSNUM >= 10307 - Skins.Message (mtInfo, msg,2); + Skins.Message (mtInfo, msg,duration); Skins.Flush (); #else Interface->Status (msg); @@ -840,6 +858,15 @@ showmessage(const char * msg) } void +showimportcount(unsigned int count) +{ + char b[100]; + sprintf(b,tr("Imported %d tracks..."),count); + assert(strlen(b)<100); + showmessage(b,1); +} + +void mgMainMenu::AddMenu (mgMenu * m,unsigned int position) { Menus.push_back (m); @@ -847,7 +874,8 @@ mgMainMenu::AddMenu (mgMenu * m,unsigned int position) m->setParentIndex(Current()); if (Get(Current())) m->setParentName(Get(Current())->Text()); - m->Display (position); + newposition = position; + m->Display (); } void @@ -898,6 +926,8 @@ mgMenuOrder::BuildOsd () m_keytypes.reserve(mgKeyTypesNr+1); m_keynames.clear(); m_keynames.reserve(50); + m_orderbycount = m_order->getOrderByCount(); + mgDebug(1,"m_orderbycount wird %d",m_orderbycount); for (unsigned int i=0;i<m_order->size();i++) { unsigned int kt; @@ -909,6 +939,9 @@ mgMenuOrder::BuildOsd () a->SetMenu(this); osd()->AddItem(a); } + mgAction *a = actGenerateBoolItem(tr("Sort by count"),&m_orderbycount); + a->SetMenu(this); + osd()->AddItem(a); } bool @@ -919,6 +952,8 @@ mgMenuOrder::ChangeOrder(eKeys key) for (unsigned int i=0; i<m_keytypes.size();i++) newtypes.push_back(ktValue(m_keynames[i][m_keytypes[i]])); mgOrder n = mgOrder(newtypes); + n.setOrderByCount(m_orderbycount); + mgDebug(1,"m_orderbycount %d nach n",m_orderbycount); bool result = !(n == *m_order); *m_order = n; if (result) @@ -977,13 +1012,13 @@ mgTreeRemoveFromCollSelector::mgTreeRemoveFromCollSelector(string title) } void -mgMainMenu::DisplayGoto (unsigned int select) +mgMainMenu::DisplayGoto () { - if (select >= 0) + if (newposition >= 0) { - if ((int)select>=Count()) - select = Count() -1; - SetCurrent (Get (select)); + if ((int)newposition>=Count()) + newposition = Count() -1; + SetCurrent (Get (newposition)); RefreshCurrent (); } Display (); @@ -991,9 +1026,9 @@ mgMainMenu::DisplayGoto (unsigned int select) void -mgMenu::Display (const unsigned int position) +mgMenu::Display () { BuildOsd (); - osd ()->DisplayGoto (position); + osd ()->DisplayGoto (); } @@ -20,8 +20,7 @@ #include <osd.h> #include <plugin.h> #include <status.h> -#include "i18n.h" -#include "mg_actions.h" +#include "vdr_actions.h" #include "vdr_player.h" @@ -31,7 +30,8 @@ using namespace std; //! \param select if true, play only what the current position selects void Play(mgSelection *sel,const bool select=false); -void showmessage(const char *msg); +void showmessage(const char *msg,int duration=2); +void showimportcount(unsigned int count); class cCommands; @@ -64,9 +64,9 @@ class mgStatus : public cStatus class mgMainMenu:public cOsdMenu { private: - mgSelection m_treesel; - mgSelection m_playsel; - mgSelection m_collectionsel; + mgSelection *m_treesel; + mgSelection *m_playsel; + mgSelection *m_collectionsel; char *m_message; void showMessage(); void LoadExternalCommands(); @@ -157,9 +157,8 @@ class mgMainMenu:public cOsdMenu string play_collection; /*! \brief selects a certain line on the OSD and displays the OSD - * \param select the line that we want to be selected */ - void DisplayGoto (unsigned int select); + void DisplayGoto (); //! \brief external commands cCommands *external_commands; @@ -204,21 +203,21 @@ class mgMainMenu:public cOsdMenu mgSelection* selection () { if (UsingCollection) - return &m_collectionsel; + return m_collectionsel; else - return &m_treesel; + return m_treesel; } //! \brief the collection selection mgSelection* collselection() { - return &m_collectionsel; + return m_collectionsel; } //! \brief the "now playing" selection mgSelection* playselection () { - return &m_playsel; + return m_playsel; } //! \brief true if the cursor is placed in the collection list @@ -306,8 +305,8 @@ class mgMenu //! \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 - void Display (const unsigned int position); +//! \brief display OSD and go to osd()->newposition + void Display (); //! \brief BuildOsd() should be abstract but then we cannot compile virtual void BuildOsd () @@ -376,6 +375,7 @@ class mgMenuOrder : public mgMenu private: void AddKeyActions(mgMenu *m,mgOrder *o); mgOrder * m_order; + int m_orderbycount; vector<int> m_keytypes; vector < vector <const char*> > m_keynames; }; diff --git a/vdr_player.c b/vdr_player.c index bf0a504..3b3035f 100644 --- a/vdr_player.c +++ b/vdr_player.c @@ -41,7 +41,6 @@ #include "vdr_config.h" #include "vdr_setup.h" #include "i18n.h" -#include "mg_db.h" #include "mg_tools.h" diff --git a/vdr_player.h b/vdr_player.h index 5b8ea9f..5d60344 100644 --- a/vdr_player.h +++ b/vdr_player.h @@ -18,7 +18,7 @@ #define ___VDR_PLAYER_H #include <player.h> -#include "mg_db.h" +#include "mg_selection.h" #if VDRVERSNUM >= 10307 class cOsd; #endif |