summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLarsAC <LarsAC@e10066b5-e1e2-0310-b819-94efdf66514b>2005-07-29 23:00:53 +0000
committerLarsAC <LarsAC@e10066b5-e1e2-0310-b819-94efdf66514b>2005-07-29 23:00:53 +0000
commit335def9f61ce75a3857bd21745c629005c37db79 (patch)
treedaae8b1105c422542181a4bf5568f684b6e955b9
parentda2367d81a6a117554b31080a6e722b51150b8f8 (diff)
downloadvdr-plugin-muggle-335def9f61ce75a3857bd21745c629005c37db79.tar.gz
vdr-plugin-muggle-335def9f61ce75a3857bd21745c629005c37db79.tar.bz2
Merged from 0.1.7-wr
git-svn-id: https://vdr-muggle.svn.sourceforge.net/svnroot/vdr-muggle/trunk/muggle-plugin@791 e10066b5-e1e2-0310-b819-94efdf66514b
-rw-r--r--HISTORY37
-rw-r--r--Makefile80
-rw-r--r--README226
-rw-r--r--README.mysql85
-rw-r--r--README.postgresql58
-rw-r--r--README.sqlite38
-rw-r--r--TODO1
-rw-r--r--i18n.c382
-rw-r--r--mg_content.c351
-rw-r--r--mg_content.h141
-rw-r--r--mg_db.c1628
-rw-r--r--mg_db.h313
-rw-r--r--mg_db_gd_mysql.c668
-rw-r--r--mg_db_gd_mysql.h82
-rw-r--r--mg_db_gd_pg.c351
-rw-r--r--mg_db_gd_pg.h70
-rw-r--r--mg_db_gd_sqlite.c372
-rw-r--r--mg_db_gd_sqlite.h67
-rw-r--r--mg_item.c114
-rw-r--r--mg_item.h71
-rw-r--r--mg_item_gd.c355
-rw-r--r--mg_item_gd.h78
-rw-r--r--mg_keytypes.h51
-rw-r--r--mg_listitem.c114
-rw-r--r--mg_listitem.h41
-rw-r--r--mg_mysql.c590
-rw-r--r--mg_mysql.h76
-rw-r--r--mg_order.c1254
-rw-r--r--mg_order.h162
-rw-r--r--mg_sel_gd.c354
-rw-r--r--mg_sel_gd.h48
-rw-r--r--mg_selection.c974
-rw-r--r--mg_selection.h264
-rw-r--r--mg_setup.c182
-rw-r--r--mg_setup.h15
-rw-r--r--mg_sync.c313
-rw-r--r--mg_sync.h73
-rw-r--r--mg_thread_sync.c25
-rw-r--r--mg_thread_sync.h7
-rw-r--r--mg_tools.c69
-rw-r--r--mg_tools.h53
-rw-r--r--mg_valmap.c108
-rw-r--r--mg_valmap.h34
-rw-r--r--muggle.c140
-rw-r--r--muggle.doxygen2
-rwxr-xr-xmugglei.c160
-rw-r--r--mugglei.h0
-rwxr-xr-xscripts/image_convert.sh80
-rw-r--r--vdr_actions.c366
-rw-r--r--vdr_actions.h6
-rw-r--r--vdr_config.h2
-rw-r--r--vdr_decoder.c41
-rw-r--r--vdr_decoder.h10
-rw-r--r--vdr_decoder_flac.c40
-rw-r--r--vdr_decoder_flac.h2
-rw-r--r--vdr_decoder_mp3.c6
-rw-r--r--vdr_decoder_mp3.h3
-rw-r--r--vdr_decoder_ogg.c5
-rw-r--r--vdr_decoder_ogg.h2
-rw-r--r--vdr_decoder_sndfile.c412
-rw-r--r--vdr_decoder_sndfile.h104
-rw-r--r--vdr_menu.c442
-rw-r--r--vdr_menu.h44
-rw-r--r--vdr_player.c626
-rw-r--r--vdr_player.h3
-rw-r--r--vdr_setup.c30
-rw-r--r--vdr_sound.c2
-rw-r--r--vdr_stream.c4
68 files changed, 7898 insertions, 5009 deletions
diff --git a/HISTORY b/HISTORY
index 829f22d..cbfb927 100644
--- a/HISTORY
+++ b/HISTORY
@@ -200,5 +200,38 @@ XXXXXXXXXX: Version 0.0.8-ALPHA
first track restarted the first track. Now it goes to the last track.
- Added incremental search
-2005-xx-xx: Version 0.1.8-BETA
-- Added finnish translations. Thanks to Rolf Ahrenberg.
+xxxxxxxx: Version 0.1.8-BETA
+- WARNING: muggle currently only works correctly with LD_ASSUME_KERNEL=2.4.1.
+ Since 1.3.27 vdr does not enforce this anymore
+- reimplemented mugglei option -z (DeleteStaleReferences), also in the
+ muggle setup menu
+- rewrote most of the SQL interface to allow easier implementation
+ of alternative data bases backends or alternative data source like EPG
+- major edits to README - split off README.mysql
+- implemented support for SQLite. Tested with version 3.2.1. The
+ field genre2 is not supported because it would be too slow.
+ Define HAVE_SQLITE in Make.config, details see README.mysql
+ Parameters h,u,s,p,w are not needed and not supported with SQLite.
+- implemented support for Postgresql. Tested with version 8.0.
+ Define HAVE_PG in Make.config, details see README.postgresql
+- if instant play is used while playing from the 'play' collection,
+ the latter will be resumed after instant play finishes.
+- progress display: if the progress of the whole selection is shown,
+ the current title will be displayed and not the name of the selection
+- if you use an order like Interpret/Album/Tracks and all songs
+ have the same track number, instantly playing any of them played
+ them all. Fixed
+- muggle can now import genre1 from id3v2 tags (the TCON tag). Only the
+ first genre is imported, and refinements will be ignored. But
+ this allows you to define your own genres. If you use the genre
+ hierarchies, they will show up under 'Extra'
+- import items from the setup menu: This is done by a background thread.
+ Those threads are not allowed to access the OSD, so we removed the
+ status messages of the import progress.
+- mugglei: import counter counts only successes.
+- mugglei: do not change directory to TLD before starting the import.
+ mugglei has to be started in TLD or below. This way wildcards will
+ always be expanded as expected.
+- display covers on the TV and with graphTFT. Details see README
+- when tracks are added to the playing list, the playlist progress
+ did not get updated.
diff --git a/Makefile b/Makefile
index e17cb87..3604eb1 100644
--- a/Makefile
+++ b/Makefile
@@ -11,12 +11,19 @@ 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
+HAVE_SNDFILE=1
-#if you do not want to compile in code for embedded sql,
+#if you do not want to compile in code for embedded mysql,
#define this in $VDRDIR/Make.config:
-# HAVE_ONLY_SERVER=1
+HAVE_ONLY_SERVER=1
+
+#define what database you want to use. Default is mysql. HAVE_SQLITE
+#removes mysql support and adds SQLite support
+# HAVE_SQLITE = 1
+
+HAVE_MYSQL = 1
### The version number of this plugin (taken from the main source file):
@@ -38,6 +45,17 @@ TMPDIR ?= /tmp
-include $(VDRDIR)/Make.config
+ifdef HAVE_SQLITE
+HAVE_MYSQL =
+HAVE_ONLY_SERVER =
+else
+ifdef HAVE_PG
+HAVE_MYSQL =
+HAVE_SQLITE =
+HAVE_ONLY_SERVER =
+endif
+endif
+
### The version number of VDR (taken from VDR's "config.h"):
VDRVERSION = $(shell grep 'define VDRVERSION ' $(VDRDIR)/config.h | awk '{ print $$3 }' | sed -e 's/"//g')
@@ -50,38 +68,66 @@ PACKAGE = vdr-$(ARCHIVE)
### Includes and Defines (add further entries here):
INCLUDES += -I$(VDRDIR) -I$(VDRDIR)/include -I$(DVBDIR)/include \
- $(shell mysql_config --cflags) $(shell taglib-config --cflags)
+ $(shell taglib-config --cflags)
-DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' -DMYSQLCLIENTVERSION='"$(shell mysql_config --version)"'
+DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
### The object files (add further files here):
-OBJS = $(PLUGIN).o i18n.o mg_valmap.o mg_mysql.o mg_sync.o mg_thread_sync.o mg_order.o \
- mg_content.o mg_selection.o vdr_actions.o vdr_menu.o mg_tools.o \
+OBJS = $(PLUGIN).o i18n.o mg_valmap.o mg_db.o mg_thread_sync.o \
+ mg_item.o mg_item_gd.o mg_listitem.o mg_selection.o mg_sel_gd.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 mg_incremental_search.o
-LIBS = -lmad $(shell taglib-config --libs)
+PLAYLIBS = -lmad $(shell taglib-config --libs)
MILIBS = $(shell taglib-config --libs)
+ifdef HAVE_SQLITE
+SQLLIBS += -lsqlite3
+DB_OBJ = mg_db_gd_sqlite.o
+DEFINES += -DHAVE_SQLITE
+endif
+
+ifdef HAVE_MYSQL
+INCLUDES += $(shell mysql_config --cflags)
+DB_OBJ = mg_db_gd_mysql.o
+DEFINES += -DHAVE_MYSQL
ifdef HAVE_ONLY_SERVER
SQLLIBS = $(shell mysql_config --libs)
DEFINES += -DHAVE_ONLY_SERVER
else
-SQLLIBS = $(shell mysql_config --libmysqld-libs) -L/lib
+SQLLIBS = $(shell mysql_config --libmysqld-libs)
+endif
+endif
+
+ifdef HAVE_PG
+INCLUDES += -I$(shell pg_config --includedir)
+SQLLIBS = -L$(shell pg_config --libdir) -lpq
+DB_OBJ = mg_db_gd_pg.o
+DEFINES += -DHAVE_PG
endif
ifdef HAVE_VORBISFILE
DEFINES += -DHAVE_VORBISFILE
OBJS += vdr_decoder_ogg.o
-LIBS += -lvorbisfile -lvorbis
+PLAYLIBS += -lvorbisfile -lvorbis
endif
+
ifdef HAVE_FLAC
DEFINES += -DHAVE_FLAC
OBJS += vdr_decoder_flac.o
-LIBS += -lFLAC++ -lFLAC
+PLAYLIBS += -lFLAC++ -lFLAC
+endif
+
+ifdef HAVE_SNDFILE
+DEFINES += -DHAVE_SNDFILE
+OBJS += vdr_decoder_sndfile.o
+PLAYLIBS += -lsndfile
endif
+
+OBJS += $(DB_OBJ)
+
### Targets:
all: libvdr-$(PLUGIN).so mugglei
@@ -97,17 +143,18 @@ $(DEPFILE): Makefile
### Implicit rules:
-%.o: %.c %.h
+%.o: %.c
$(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) $(SQLLIBS) -o $@
+# das hier nur voruebergehend, zum einfacheren Testen, ob noch alles kompiliert:
+libvdr-$(PLUGIN).so: $(OBJS) $(DB_OBJ) mg_db_gd_mysql.o # mg_db_gd_sqlite.o mg_db_gd_pg.o
+ $(CXX) $(CXXFLAGS) -shared $(OBJS) $(PLAYLIBS) $(SQLLIBS) -o $@
@cp $@ $(LIBDIR)/$@.$(VDRVERSION)
-mugglei: mg_tools.o mugglei.o mg_sync.o mg_mysql.o mg_setup.o
+mugglei: mg_tools.o mugglei.o mg_db.o $(DB_OBJ) mg_listitem.o mg_item.o mg_item_gd.o mg_valmap.o mg_setup.o
$(CXX) $(CXXFLAGS) $^ $(MILIBS) $(SQLLIBS) -o $@
install:
@@ -125,4 +172,3 @@ dist: clean mg_tables.h
clean:
@-rm -f $(OBJS) $(BINOBJS) $(DEPFILE) *.so *.tgz core* *~ mugglei.o mugglei
-
diff --git a/README b/README
index 67004ec..1f3f4a2 100644
--- a/README
+++ b/README
@@ -28,20 +28,21 @@ or useless in any other sense.
\section desc DESCRIPTION
The muggle plugin provides a database link for VDR so that selection of media becomes more flexible.
-Prerequisites are describedin Section 2, Notes on Compilation are in Section 3. Before using the plugin,
+Prerequisites are described in Section 2, Notes on Compilation are in Section 3. Before using the plugin,
you need to import your media into the database (cf. Section 4). The configuration of VDR and startup
parameters are descibed in Section 5.
-\section prereq PREREQUISITES
-The plugin currently runs on versions 1.3.17- of VDR (including 1.2.6). It also compiles on 1.3.18
-but your mileage may vary. In addition, the following pieces of software are required:
+The plugin currently runs on versions 1.3.17- of VDR (including 1.2.6). It also compiles on versions
+up to at least 1.3.23 but your mileage may vary. In addition, the following pieces of software are required:
- - mySQL client libraries
- (Debian package libmysqlclient-dev or
- http://www.mysql.org)
- - mySQL server (tested with 4.0.18) (Debian packages mysql-server, mysql-client)
- only needed, if you want to run the database server on a remote machine
+ - a database backend. You can use mysql, embedded mysql, postgresql and SQLite. If you want to be
+ compatible with GiantDisc, use mysql. If other applications will access this data base, do not
+ use embedded mysql. The easiest to install are embedded mysql and SQLite. SQLite will use
+ much less RAM than the others.
+ By default muggle will be built using the embedded mysql library so that it is
+ not required to install further packages or run additional services.
+ For details see README.mysql, README.postgresql, README.sqlite.
- libmad (for mp3 decoding)
(Debian package libmad0-dev or
http://www.underbit.com/products/mad/)
@@ -53,11 +54,18 @@ but your mileage may vary. In addition, the following pieces of software are req
http://www.xiph.org/ogg/vorbis/)
- optionally libFLAC++ to replay FLAC files
(Debian package libflac++-dev or sources from flac.sourceforge.net)
+
The developer versions are needed because their headers are required for compilation.
The server need not be on the same machine as the VDR. Also, music tracks can reside somewhere else,
if they are available through a remote filesystem (NFS, Samba). However, in this case you should
know what you are doing in terms of networking and security issues.
+Support for using a libsndfile decoder requires libsndfile1-dev as a debian package or a corresponding library installation.
+
+Since relase 0.1.8 Muggle is able to display cover pictures. The required packages are
+ - mjpegtools (Debian package mjpegtools)
+ - netpbm (Debian package netpbm)
+
\section install INSTALLING
Unpack the sources in PLUGINS/src below your VDR directory (i.e. where all your other plugins are.
@@ -74,21 +82,9 @@ Establish a symlink as you would for other plugins:
ln -s muggle-0.1.1 muggle
\endverbatim
-Adapt the Makefile to your system. Define HAVE_VORBIS and/or HAVE_FLAC and adapt
-the LIBS variable accordingly.
-
-NOTE: By default, muggle will be built using the embedded mysql library so that it is
-not required to install further packages or run additional services. If you want to use
-the remote server as in previous versions, you have to define the variable HAVE_SERVER
-in the Makefile by uncommenting the corresponding line.
-
-NOTE: If you have not installed the mysql server package on your machine the files
-containing error messages for MySQL you will see an error message like this:
-
-050306 9:29:14 Can't find messagefile '/usr/share/mysql/english/errmsg.sys'
-050306 9:29:14 Aborting
-
-In this case you need to obtain these files and put them there.
+Adapt the Makefile to your system. Or better yet adapt your Make.config. This file might
+be in /usr/include. Define HAVE_VORBIS and/or HAVE_FLAC and the database
+definitions as described in README.mysql, README.postgresql, README.SQLite.
Then, within the VDR main directory (e.g. /usr/local/src/VDR) issue
@@ -99,68 +95,131 @@ Then, within the VDR main directory (e.g. /usr/local/src/VDR) issue
This should build all relevant stuff. If you have difficulties, check that required libraries are
in the library directories stated in the muggle Makefile.
-Note: On my Debian sarge system, I had difficulties because a proper symlink for libwrap.so was
+Note: On some Debian sarge systems a proper symlink for libwrap.so might be missing.
missing. Check this in case the compiler complains about a missing -lwrap.
-\section SET UP MUGGLE WITH EMBEDDED MYSQL
+\section SET UP MUGGLE
+
+First let's define what the top level directory (short TLD) is. Muggle expects
+all music files to be in the same directory (and its subdirectories). Symbolic links
+will be followed. You specify the TLD with the argument -t. The default is /mnt/music.
+It is recommended to store only music files in the TLD. You can organize
+files in orders per genre, album or whatever.
+
+It is essential that you always use the same TLD when using the muggle plugin
+or the external program mugglei, otherwise muggle will not find your files. So
+please take some time to plan where to put your music files. The plugin must
+be able to find and to read them. The data base only holds the names of your
+music files but not the content. The database does NOT hold the TLD but only
+the file names relative to the TLD. Example: let's assume you have a file
+/home/music/Pop/Rea/title.mp3, and all other music is also in /home/music:
+
+\verbatim
+mugglei -t /home/music Pop/Rea/title.mp3
+\endverbatim
+
+will save Pop/Rea/title.mp3 as file name. If you want to play this,
+you will also have to tell the plugin with option -t /home/music where
+to find it. muggle only stores the file names relative to the TLD because
+this makes it possible to move the TLD somewhere else without having to
+reimport everything. You could as an example rename the TLD /home/music
+to /server5/MUSIK and simply change the value of the -t argument when
+starting the plugin.
+
+Of course, you will normally not import single files. The normal command would be
+
+\verbatim
+mugglei -t /home/music .
+\endverbatim
+
+(Notice the single dot meaning "this directory")
-The step of setting up the database and importing music has been simplified a lot
-for Muggle using the embedded MySQL server. When starting up muggle the first time
-it will determine that the database does not exist and will ask, whether to
-create the database. Confirm this with Ok.
+Next you have to decide which database software best suits your needs.
+We assume that the database software is already configured. See the corresponding
+README.*
-After successfully creating the database, muggle will query whether to import music.
+When starting up muggle the first time it will check whether the database exists
+and might ask whether to create it. Confirm this with Ok.
+
+After successfully creating the database, muggle will ask you whether to import music.
Confirm this question with Ok, too. Muggle will now recursively descend into the
-directories below the music directory specified with the -t option on the command line.
+directories below the TLD.
Once muggle is running, you can import new tracks and read updated tags by a command
-in the setup menu. The use of mugglei is still possible, but only while VDR is not
-running (because muggle will then block the use of the database).
+in the setup menu. The use of mugglei is still possible, but possibly only while VDR is not
+running, depending on the database you are using. (because muggle will then block the use
+of the database). This is especially true for SQLite. The remote mysql and postgresql
+versions do not block.
+
+\section SET UP MUGGLE USING MUGGLEI
-NOTE: The embedded MySQL server cannot be used by other programs. The use of the
-GiantDisc web interface for example is not possible.
+You can also initialize and populate the data base from the command line with
+the external program mugglei. This can be done on the database server or on some
+other client machine as long as it can access the data base and the files it
+should import.
-\section SET UP MUGGLE WITH REMOTE MYSQL
+mugglei takes some parameters defining how the data base can be accessed.
+These parameters vary slighlty depending of the data base software you have
+chosen. Start mugglei without arguments to see a list and explanation for
+all available parameters.
-If you already have a MySQL server running in your network (e.g. as a basis
-for a webserver) or want to access the music database with other programs
-(e.g. the GiantDisc web interface) you may be interested in using
+The simplest form for initializing (parameter -c) and populating (the rest
+on the command line) the data base is
-This step can be done on the database server or on some other client machine.
-The scripts in the directory scripts is no longer needed. Instead, mugglei can
-now also create new databases and provide basic information about existing languages,
-genres etc. A small utility called 'mugglei' to administer the database has been
-created along with the plugin. Run
+\verbatim
+mugglei -c -t /home/music .
+\endverbatim
+
+(notice the single dot). This uses default values for all other parameters.
+mugglei will create a new databases and initialize it with basic information
+about languages, genres etc. It will also import all files in the TLD /home/music
+and its subdirectories. For details call mugglei without arguments, some help
+will be displayed.
+
+
+\section config MUGGLE CONFIGURATION
+
+muggle uses a small set of command line parameters which define
+how the database can be accessed. These are the same as for mugglei. You get
+help about them with
+
+\verbatim
+vdr -h -Pmuggle
+\endverbatim
+
+Let's look at an example:
\verbatim
- mugglei -c
+vdr -P'muggle -t/home/music'
\endverbatim
-to create a database and initialize the tables. This replaces the series of commands
-needed in former commands. If you want to change the server or database name look at
-the command line arguments (execute mugglei without arguments to see a list).
+The -t argument specifies the TLD as described above.
+
+Depending on your data base you may have to give mugglei more arguments,
+telling mugglei how to access the data base.
+For details see the README.* that corresponds to your data base software.
-\subsection importremote Import for Muggle with remote MySQL
+\section importremote IMPORTING MUSIC FILES
-The next step is to feed all music information into the database. To accomplish this, mugglei
-connects to the database, evaluates ID3 tags from a file, and writes the tags into the
-database. Since release 0.1.4 mugglei recursively descends a file system hierarchy so that
-the use of find is no longer needed.
+You can do this directly from vdr (in the muggle configuration menu) or
+by using mugglei as explained above. The program will connect to the
+database, evaluate ID3 tags from files and write the tags into the database.
-For this step, it is helpful, that all music files are somehow gathered under a toplevel directory.
-It does not matter whether there are further subdirectories which organize files into genres, artists,
-album or whatever. If this is not the case, you may want to take some time to do this. Read on before
-you start. Executing
\verbatim
- mugglei *
+ mugglei -t /home/music .
\endverbatim
-will import all music files (*.mp3., *.ogg, *.flac) below the current directory. Obviously,
-you may need additional options for the database host, user, etc.
-It is important that you perform various import steps from the same location so the
-filenames are relative to exactly the same directory (e.g. /home/music in the example case).
+(Notice the single dot) will import all music files (*.mp3., *.ogg, *.flac) in
+the directory . (the single dot stands for the current directory)
+As already explained, it is important that you always use the same value
+for -t whenever you import so that filenames are relative to exactly the same
+directory (e.g. /home/music in the example case).
+
+NOTE: mugglei can only be called in the TLD or in some subdirectory of the TLD.
+Otherwise mugglei displays an error and does nothing.
-NOTE: The options -f and -a are no longer needed. mugglei should now automatically do the right thing.
+As with the muggle plugin, you may need additional options for the
+database host, user, etc.
If a track has no ID3 tags, the following defaults will be applied:
@@ -170,30 +229,24 @@ If a track has no ID3 tags, the following defaults will be applied:
- Track: 0
- Year: 0
-\section config MUGGLE CONFIGURATION
-
-In case you use mugglei with the embedded MySQL server the most important
-options are -d DIR (controls where the database file is created) and
--t DIR (controls where the music resides).
+\section covers COVERS
-When using the remote MySQL server, muggle uses a small set of command line
-parameters in order to control the interaction with the mySQL server. Let's
-look at an example:
+muggle can display cover images. This is how it tries to find them:
+1. if the database field album.coverimg contains something: This is
+ displayed. If the image file does not exist, show an error.
+2. else: take the track filename and replace its extension by .jpg
+ if this file exists, display it
+3. else try the file "cover.jpg" in the current directory. If it
+ does not exist, try parent directory. Repeat until the TLD is
+ reached.
-\verbatim
- -P'muggle -h localhost -u vdr -n GiantDisc -t/home/music'
-\endverbatim
-
-The -h parameter specifies the database host, -u specifies the user,
--n is the database name. The scripts mentioned above do not make use
-of passwords, but restrict database acccess on a server basis.
+So if you want a default background for all tracks you should put it
+into TLD/cover.jpg. It is strongly recommended to define such a default
+background. Otherwise if no cover is found for a track, the previously
+displayed cover stays on the screen.
-The -t argument specifies the top level directory of the music files.
-On a local installation, this is the directory in which you executed the
-import steps (Chapter 4.2).
-
-In case you want to use Muggle with the embedded MySQL server, specify the
-directory to place the database into with the option -d DIR.
+During installation, make sure that the script called image_convert.sh
+(from the scripts directory) is somewhere on your path.
\section quickuse QUICK INTRO
@@ -382,4 +435,7 @@ OSD. If Stop is pressed, muggle first stops playing what was started by Instant
Play. Muggle will then continue playing the 'play' collection. A second Stop will
stop playing the 'play' collection.
+If instant play is used while playing the 'play' collection, the latter will resume
+after instant play finishes.
+
*/
diff --git a/README.mysql b/README.mysql
new file mode 100644
index 0000000..bbf71a0
--- /dev/null
+++ b/README.mysql
@@ -0,0 +1,85 @@
+/*! \mainpage Muggle: Usage with mysql
+
+This is a plugin for the Video Disk Recorder (VDR).
+
+Written by: Andi Kellner,
+ Lars von Wedel <vonwedel@web.de>,
+ Ralf Klueber <r@lf-klueber.de>,
+ Wolfgang Rohdewald <wolfgang@rohdewald.de>
+
+Project's homepage: http://www.htpc-tech.de/htpc/muggle.htm
+
+Latest version available at: http://www.htpc-tech.de/htpc/muggle-dev.htm
+
+See the file COPYING for license information.
+
+\section prereq PREREQUISITES
+
+You will need:
+
+ - mySQL client libraries
+ (Debian package libmysqlclient-dev or
+ http://www.mysql.org)
+ - mySQL server (tested with 4.0.18) (Debian packages mysql-server, mysql-client)
+ only needed, if you want to run the database server on a remote machine
+
+The developer versions are needed because their headers are required for compilation.
+The server need not be on the same machine as the VDR. Also, music tracks can reside somewhere else,
+if they are available through a remote filesystem (NFS, Samba). However, in this case you should
+know what you are doing in terms of networking and security issues.
+
+\section install INSTALLING
+
+Adapt the Makefile or Make.config to your system. Define HAVE_MYSQL and HAVE_ONLY_SERVER if
+you do not want the embedded version.
+
+HAVE_MYSQL=1
+HAVE_ONLY_SERVER=1
+
+NOTE: If the package mysql-server is not installed on your machine you might
+see an error message like this:
+
+050306 9:29:14 Can't find messagefile '/usr/share/mysql/english/errmsg.sys'
+050306 9:29:14 Aborting
+
+In this case you need to obtain these files and put them there.
+
+Then you can build and install the plugin as described in README
+
+
+\section SET UP MUGGLE WITH EMBEDDED MYSQL
+
+Embedded MySQL means simpler configuration because you do not have
+to login into a remote data base server. You will only need the argument
+-d Directory. muggle will store the data base in that directory. Start
+mugglei without arguments for details.
+
+NOTE: The embedded MySQL server cannot be used by other programs.
+
+NOTE: The GiantDisc web interface only works with remote MySQL.
+
+
+\section SET UP MUGGLE WITH REMOTE MYSQL
+
+If you already have a MySQL server running in your network (e.g. as a basis
+for a webserver) or want to access the music database with other programs
+(e.g. the GiantDisc web interface) you may be interested in using
+
+\section config MUGGLE CONFIGURATION
+
+When using the remote MySQL server, muggle and mugglei use a small set of
+parameters in order to control the interaction with the mySQL server. Let's
+look at an example:
+
+\verbatim
+ -P'muggle -h localhost -u vdr -p pw -n GiantDisc -t/home/music'
+\endverbatim
+
+The -h parameter specifies the database host, -u specifies the user,
+-p specifies the password, -n is the database name.
+
+Start mugglei without arguments to see a list and explanation of
+all available options. They can vary slightly depending on the
+chosen data base software.
+
+*/
diff --git a/README.postgresql b/README.postgresql
new file mode 100644
index 0000000..8f4aa81
--- /dev/null
+++ b/README.postgresql
@@ -0,0 +1,58 @@
+/*! \mainpage Muggle: Usage with postgresql
+
+This is a plugin for the Video Disk Recorder (VDR).
+
+Written by: Andi Kellner,
+ Lars von Wedel <vonwedel@web.de>,
+ Ralf Klueber <r@lf-klueber.de>,
+ Wolfgang Rohdewald <wolfgang@rohdewald.de>
+
+Project's homepage: http://www.htpc-tech.de/htpc/muggle.htm
+
+Latest version available at: http://www.htpc-tech.de/htpc/muggle-dev.htm
+
+See the file COPYING for license information.
+
+\section prereq PREREQUISITES
+
+You will need:
+
+ - postgresql client libraries: Debian package postgresql-client
+ - postgresql server, possibly on another machine: Debian package postgresql
+ - postgresql-dev for the compilation
+
+Please execute these steps before starting muggle the first time:
+(tested on Debian unstable)
+
+1. log in as root
+2. su - postgres
+3. createuser vdr
+4. createdb GiantDisc
+
+
+Everything else will be done by muggle.
+
+\section install INSTALLING
+
+Adapt the Makefile or Make.config to your system. Define HAVE_PG
+
+HAVE_PG=1
+
+
+\section SET UP MUGGLE WITH POSTGRESQL
+
+Muggle and mugglei use a small set of parameters in order to control
+the interaction with the Postgresql server. Let's look at an example:
+
+\verbatim
+ -P'muggle -h /tmp -u vdr -t/home/music'
+\endverbatim
+
+The -h parameter says to look for a socket in /tmp (this is what I
+had to to on Debian unstable).
+
+Start mugglei without arguments to see a list and explanation of
+all available options. They can vary slightly depending on the
+chosen data base software.
+
+*/
diff --git a/README.sqlite b/README.sqlite
new file mode 100644
index 0000000..d8c13a9
--- /dev/null
+++ b/README.sqlite
@@ -0,0 +1,38 @@
+/*! \mainpage Muggle: Usage with SQLite
+
+This is a plugin for the Video Disk Recorder (VDR).
+
+Written by: Andi Kellner,
+ Lars von Wedel <vonwedel@web.de>,
+ Ralf Klueber <r@lf-klueber.de>,
+ Wolfgang Rohdewald <wolfgang@rohdewald.de>
+
+Project's homepage: http://www.htpc-tech.de/htpc/muggle.htm
+
+Latest version available at: http://www.htpc-tech.de/htpc/muggle-dev.htm
+
+See the file COPYING for license information.
+
+\section prereq PREREQUISITES
+
+You will need:
+
+ - the sqlite3 library: Debian package sqlite3
+ - the Debian package libsqlite3-dev for the compilation
+
+\section install INSTALLING
+
+Adapt the Makefile or Make.config to your system. Define
+
+HAVE_SQLITE=1
+
+
+\section SET UP MUGGLE WITH SQLITE
+
+SQLITE means simpler configuration because you do not have
+to login into a remote data base server. You will only need the argument
+-d Directory. muggle will store the data base in that directory. There
+is a default, so you can even omit -d. Start mugglei without arguments
+for details.
+
+*/
diff --git a/TODO b/TODO
index 6594daf..0f9a843 100644
--- a/TODO
+++ b/TODO
@@ -117,6 +117,7 @@
- Import playlist from m3u
- Run import/update from within OSD?
+ - define regexp per import field for things like s'/^The//'
\subsection midcode Code issues
diff --git a/i18n.c b/i18n.c
index de4a4f8..173e40c 100644
--- a/i18n.c
+++ b/i18n.c
@@ -21,7 +21,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Sort by count", // TODO
"", // TODO
- "Järjestä lukumäärän mukaan",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -38,7 +38,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Key %d", // TODO
"", // TODO
- "Avain %d",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -55,7 +55,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Nouveau", // TODO
"", // TODO
- "Luo",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -72,7 +72,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Naviguer", // TODO
"", // TODO
- "Selaa",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -89,7 +89,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Ordre", // TODO
"", // TODO
- "Järjestä",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -106,7 +106,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Collections", // TODO
"", // TODO
- "Kokoelmat",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -123,7 +123,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Vider la collection?", // TODO
"", // TODO
- "Tyhjennetäänkö kokoelma?",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -140,7 +140,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Effacer la collection?", // TODO
"", // TODO
- "Tuhotaanko kokoelma?",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -157,7 +157,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Créer un ordre nouveaux", // TODO
"", // TODO
- "Luo järjestys",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -174,7 +174,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Créer une nouvelle collection", // TODO
"", // TODO
- "Luo kokoelma",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -191,7 +191,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Effacer la collection", // TODO
"", // TODO
- "Tuhoa kokoelma",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -208,7 +208,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Effacer la collection '%s'", // TODO
"", // TODO
- "Tuhoa kokoelma '%s'",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -225,7 +225,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Collections", // TODO
"", // TODO
- "Kokoelmat",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -242,7 +242,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Commandes", // TODO
"", // TODO
- "Komennot",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -259,7 +259,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Commandes:%s", // TODO
"", // TODO
- "Komennot: %s",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -276,7 +276,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Collection", // TODO
"", // TODO
- "Kokoelma",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -293,7 +293,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Liste", // TODO
"", // TODO
- "Lista",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -310,7 +310,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Exporter", // TODO
"", // TODO
- "Vie",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -319,7 +319,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
},
{
- "Export track list",
+ "Export item list",
"Stückliste exportieren",
"", // TODO
"", // TODO
@@ -327,7 +327,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Exporter la liste", // TODO
"", // TODO
- "Vie kappalelista",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -344,7 +344,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"commande externe playlist", // TODO
"", // TODO
- "Ulkoiset soittolistakomennot",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -361,7 +361,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Déclancher le mode répétition", // TODO
"", // TODO
- "Jatkuvasoitto poissa",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -378,7 +378,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Mode répétition titre seul", // TODO
"", // TODO
- "Jatkuvasoitto kappaleelle",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -395,7 +395,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Mode répétition playlist", // TODO
"", // TODO
- "Jatkuvasoitto soittolistalle",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -412,7 +412,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"mode allèatoire déclenché", // TODO
"", // TODO
- "Satunnaissoitto poissa",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -429,7 +429,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Mode allèatoire normal", // TODO
"", // TODO
- "Satunnaissoitto päällä",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -446,7 +446,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Interprète",
"", // TODO
- "Artisti",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -463,7 +463,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"InterprèteABC",
"", // TODO
- "ArtistiABC",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -480,7 +480,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Jouer tout",
"", // TODO
- "Soita kaikki",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -497,7 +497,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Définir",
"", // TODO
- "Aseta",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -514,7 +514,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Jouer en direct",
"", // TODO
- "Soita nyt",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -531,7 +531,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Jouer '%s' en direct",
"", // TODO
- "Soita '%s' nyt",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -548,7 +548,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Mode allèatoire fêtes", // TODO
"", // TODO
- "Satunnaissoitto biletykseen",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -565,7 +565,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Destinataire", // TODO
"", // TODO
- "Oletus",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -582,7 +582,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Ajoute '%s' à une collection", // TODO
"", // TODO
- "'%s' kokoelmaan",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -599,7 +599,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Changer destination à la collection '%s'", // TODO
"", // TODO
- "Aseta kokoelma '%s' oletukseksi",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -616,7 +616,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"La collection destinataire est maintenant '%s'", // TODO
"", // TODO
- "Oletuskokoelma on nyt '%s'",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -633,7 +633,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Ajouter", // TODO
"", // TODO
- "Lisää",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -650,7 +650,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Ajouter à une collection", // TODO
"", // TODO
- "Lisää kokoelmaan",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -667,7 +667,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Ajouter à '%s'", // TODO
"", // TODO
- "Lisää kokoelmaan '%s'",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -684,7 +684,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Effacer", // TODO
"", // TODO
- "Poista",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -701,7 +701,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Vider", // TODO
"", // TODO
- "Tyhjennä",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -718,7 +718,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Vider la collection", // TODO
"", // TODO
- "Tyhjennä kokoelma",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -735,7 +735,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Effacer d'une collection", // TODO
"", // TODO
- "Poista kokoelmasta",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -752,7 +752,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Ajouter une collection", // TODO
"", // TODO
- "Uusi kokoelma",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -769,7 +769,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Effacer cette collection", // TODO
"", // TODO
- "Poista tämä kokoelma",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -786,7 +786,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Effacer de cette collection", // TODO
"", // TODO
- "Poista kappale kokoelmasta",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -803,7 +803,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Ajouté %s pièces", // TODO
"", // TODO
- "Lisätty %s kappaletta",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -820,7 +820,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Effacé %s pièces", // TODO
"", // TODO
- "Poistettu %s kappaletta",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -837,7 +837,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Effacé toutes les pièces", // TODO
"", // TODO
- "Poistettu kaikki kappaleet",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -854,7 +854,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"En jouant", // TODO
"", // TODO
- "Nyt soitetaan",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -871,7 +871,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"", // TODO
"", // TODO
- "Arvosana",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -888,7 +888,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Décade", // TODO
"", // TODO
- "Vuosikymmen",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -905,7 +905,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Année", // TODO
"", // TODO
- "Vuosi",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -922,7 +922,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Album", // TODO
"", // TODO
- "Albumi",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -939,7 +939,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Folder1", // TODO
"", // TODO
- "Kansio1",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -956,7 +956,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Folder2", // TODO
"", // TODO
- "Kansio2",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -973,7 +973,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Folder3", // TODO
"", // TODO
- "Kansio3",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -990,7 +990,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Folder4", // TODO
"", // TODO
- "Kansio4",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -1007,7 +1007,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Genre", // TODO
"", // TODO
- "Tyylilaji",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -1024,7 +1024,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Genre 2", // TODO
"", // TODO
- "Tyylilaji 2",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -1041,7 +1041,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Titre", // TODO
"", // TODO
- "Nimi",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -1058,7 +1058,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"TitreABC", // TODO
"", // TODO
- "NimiABC",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -1076,7 +1076,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Pièce", // TODO
"", // TODO
- "Kappale",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -1092,7 +1092,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"jouer", // TODO
"", // TODO
- "soita",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -1109,7 +1109,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Pièce de collection", // TODO
"", // TODO
- "Kokoelma",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -1126,7 +1126,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Collection '%s' PAS effacée", // TODO
"", // TODO
- "Kokoelmaa '%s' ei tuhottu",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -1143,7 +1143,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Collection '%s' effacée", // TODO
"", // TODO
- "Kokoelma '%s' tuhottu",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -1160,7 +1160,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Choisir un ordre", // TODO
"", // TODO
- "Valitse järjestys",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -1177,7 +1177,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Synchroniser la base des données", // TODO
"", // TODO
- "Tahdista tietokanta",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -1194,7 +1194,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Générer la base des données %s?", // TODO
"", // TODO
- "Luodaanko tietokanta %s?",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -1211,7 +1211,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Synchroniser la base des données avec les tracks?", // TODO
"", // TODO
- "Tahdistetaanko tietokanta kappaleilla?",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -1220,15 +1220,15 @@ const tI18nPhrase Phrases[] =
"", // TODO
},
{
- "Import tracks?",
- "Tracks importieren?",
+ "Import items?",
+ "Titel importieren?",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
+ "Importer les titres?", // TODO
"", // TODO
- "Importer les tracks?", // TODO
"", // TODO
- "Tuodaanko kappaleet?",
"", // TODO
"", // TODO
"", // TODO
@@ -1245,7 +1245,7 @@ const tI18nPhrase Phrases[] =
"", // TODO
"%d titres importés...", // TODO
"", // TODO
- "Tuotiin %d kappaletta...",
+ "", // TODO
"", // TODO
"", // TODO
"", // TODO
@@ -1262,228 +1262,24 @@ const tI18nPhrase Phrases[] =
"", // TODO
"Import finis:%d titres importés...", // TODO
"", // TODO
- "Tuonti valmis: %d kappaletta",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- },
- {
- "Muggle",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "Muggle",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- },
- {
- "Media juggle plugin for VDR",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "Muggle-levyautomaatti jästeille",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- },
- {
- "Setup.Muggle$Initial loop mode",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "Jatkuvasoitto oletuksena",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- },
- {
- "Setup.Muggle$Initial shuffle mode",
- "", // TODO
"", // TODO
"", // TODO
"", // TODO
"", // TODO
"", // TODO
"", // TODO
- "Satunnaissoitto oletuksena",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- },
- {
- "Setup.Muggle$Audio mode",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "Äänimoodi",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- },
- {
- "Round",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "pyöristetty",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- },
- {
- "Dither",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "ditteroitu",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
"", // TODO
},
- {
- "Setup.Muggle$Use 48kHz mode only",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "Käytä vain 48kHz moodia",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- },
- {
- "Setup.Muggle$Display mode",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "Näyttömoodi",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- },
- {
- "Setup.Muggle$Background mode",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "Taustamoodi",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- },
- {
- "Black",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "musta",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- },
- {
- "Live",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "live",
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- "", // TODO
- },
- {
- "Setup.Muggle$Normalizer level",
- "", // TODO
+ {
+ "Order is undefined",
+ "Sortierung ist nicht definiert",
"", // TODO
"", // TODO
"", // TODO
"", // TODO
+ "Ordre pas défini", // TODO
"", // TODO
"", // TODO
- "Normalisoinnin taso",
"", // TODO
"", // TODO
"", // TODO
@@ -1491,16 +1287,16 @@ const tI18nPhrase Phrases[] =
"", // TODO
"", // TODO
},
- {
- "Setup.Muggle$Limiter level",
- "", // TODO
+ {
+ "%s not readable, errno=%d",
+ "%s nicht lesbar, errno=%d",
"", // TODO
"", // TODO
"", // TODO
"", // TODO
+ "Ne peux pas lire %s, errno=%d",
"", // TODO
"", // TODO
- "Rajoittimen taso",
"", // TODO
"", // TODO
"", // TODO
@@ -1508,16 +1304,16 @@ const tI18nPhrase Phrases[] =
"", // TODO
"", // TODO
},
- {
- "Setup.Muggle$Delete stale references",
- "", // TODO
+ {
+ "%s..%s not readable, errno=%d",
+ "%s..%s nicht lesbar, errno=%d",
"", // TODO
"", // TODO
"", // TODO
"", // TODO
+ "Ne peux pas lire %s..%s, errno=%d",
"", // TODO
"", // TODO
- "Tuhoa vanhentuneet viittaukset",
"", // TODO
"", // TODO
"", // TODO
diff --git a/mg_content.c b/mg_content.c
deleted file mode 100644
index 5ab6874..0000000
--- a/mg_content.c
+++ /dev/null
@@ -1,351 +0,0 @@
-/*!
- * \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 <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include "i18n.h"
-#include <tools.h>
-#include "mg_selection.h"
-#include "mg_setup.h"
-#include "mg_tools.h"
-
-
-mgListItem zeroitem;
-
-mgListItem::mgListItem()
-{
- m_valid=false;
- m_count=0;
-}
-
-mgListItem::mgListItem(string v,string i,unsigned int c)
-{
- set(v,i,c);
-}
-
-void
-mgListItem::set(string v,string i,unsigned int c)
-{
- m_valid=true;
- m_value=v;
- m_id=i;
- m_count=c;
-}
-
-void
-mgListItem::operator=(const mgListItem& from)
-{
- m_valid=from.m_valid;
- m_value=from.m_value;
- m_id=from.m_id;
- m_count=from.m_count;
-}
-
-void
-mgListItem::operator=(const mgListItem* from)
-{
- m_valid=from->m_valid;
- m_value=from->m_value;
- m_id=from->m_id;
- m_count=from->m_count;
-}
-
-bool
-mgListItem::operator==(const mgListItem& other) const
-{
- return m_value == other.m_value
- && m_id == other.m_id;
-}
-
-mgListItem*
-mgContentItem::getKeyItem(mgKeyTypes kt)
-{
- string val;
- string id;
- if (m_trackid>=0)
- {
- switch (kt) {
- case keyGenres:
- case keyGenre1:
- case keyGenre2:
- case keyGenre3: val = getGenre();id=m_genre1_id;break;
- case keyArtist: val = id = getArtist();break;
- case keyArtistABC: val = id = getArtist()[0];break;
- case keyAlbum: val = id = getAlbum();break;
- case keyYear: val = id = string(ltos(getYear()));break;
- case keyDecade: val = id = string(ltos(int((getYear() % 100) / 10) * 10));break;
- case keyTitle: val = id = getTitle();break;
- case keyTitleABC: val = id = getTitle()[0];break;
- case keyTrack: val = id = getTitle();break;
- case keyLanguage: val = getLanguage();id=m_language_id ; break;
- case keyRating: val = id = getRating();break;
- case keyFolder1:
- case keyFolder2:
- case keyFolder3:
- case keyFolder4:
- {
- char *folders[4];
- char *fbuf=SeparateFolders(m_mp3file.c_str(),folders,4);
- val = id = folders[int(kt)-int(keyFolder1)];
- free(fbuf);
- break;
- }
- default: return new mgListItem;
- }
- }
- return new mgListItem(val,id);
-}
-
-
-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::getLanguage() const
-{
- return m_language;
-}
-
-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_valid = true;
- m_validated = false;
- m_trackid = -1;
-}
-
-mgContentItem::mgContentItem (const mgContentItem* c)
-{
- m_valid = true;
- m_validated = false;
- m_trackid = c->m_trackid;
- 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_language = c->m_language;
- m_language_id = c->m_language_id;
- 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;
-}
-
-bool
-mgContentItem::Valid() const
-{
- if (!m_validated)
- {
- getSourceFile(); // sets m_valid as a side effect
- m_validated=true;
- }
- return m_valid;
-}
-
-static bool music_dir_exists[100];
-static bool music_dirs_scanned=false;
-
-bool
-mgContentItem::readable(string filename) const
-{
- return !access(filename.c_str(),R_OK);
-}
-
-string
-mgContentItem::getSourceFile(bool AbsolutePath) const
-{
- string tld = the_setup.ToplevelDir;
- string result = m_mp3file;
- if (m_validated && !m_valid)
- return m_mp3file;
- if (!readable(tld+result))
- {
- result.clear();
- if (!music_dirs_scanned)
- {
- for (unsigned int i =0 ; i < 100 ; i++)
- {
- struct stat stbuf;
- char *dir;
- asprintf(&dir,"%s%02d",tld.c_str(),i);
- music_dir_exists[i]=!stat(dir,&stbuf);
- free(dir);
- }
- music_dirs_scanned=true;
- }
- for (unsigned int i =0 ; i < 100 ; i++)
- {
- if (!music_dir_exists[i])
- continue;
- char *file;
- asprintf(&file,"%02d/%s",i,m_mp3file.c_str());
- if (readable(tld+file))
- {
- m_mp3file = file;
- result = m_mp3file;
- }
- free(file);
- if (!result.empty())
- break;
- }
- }
- if (result.empty())
- {
- char *b=0;
- int nsize = m_mp3file.size();
- if (nsize<30)
- asprintf(&b,tr("%s not readable"),m_mp3file.c_str());
- else
- asprintf(&b,tr("%s..%s not readable"),m_mp3file.substr(0,20).c_str(),m_mp3file.substr(nsize-20).c_str());;
- extern void showmessage(const char*,int duration=0);
- showmessage(b);
- free(b);
- esyslog ("ERROR: cannot stat %s. Meaning not found, not a valid file, or no access rights", m_mp3file.c_str ());
- m_valid = false;
- return m_mp3file;
- }
- if (AbsolutePath)
- result = tld + result;
- return result;
-}
-
-mgContentItem::mgContentItem (const MYSQL_ROW row)
-{
- m_valid = true;
- m_validated = false;
- m_trackid = 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 = KeyMaps.value(keyGenres,row[5]);
- }
- else
- m_genre1 = "NULL";
- if (row[6])
- {
- m_genre2_id = row[6];
- m_genre2 = KeyMaps.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;
- if (row[13])
- {
- m_language_id = row[13];
- m_language = KeyMaps.value(keyLanguage,row[13]);
- }
- else
- m_language_id = "NULL";
-};
-
diff --git a/mg_content.h b/mg_content.h
deleted file mode 100644
index 653299c..0000000
--- a/mg_content.h
+++ /dev/null
@@ -1,141 +0,0 @@
-/*!
- * \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 <string>
-#include <list>
-#include <vector>
-#include <map>
-#include <mysql/mysql.h>
-using namespace std;
-
-#include "mg_tools.h"
-#include "mg_valmap.h"
-
-typedef vector<string> strvector;
-
-
-class mgSelection;
-
-class mgListItem
-{
- public:
- mgListItem();
- mgListItem(string v,string i,unsigned int c=0);
- void set(string v,string i,unsigned int c=0);
- void operator=(const mgListItem& from);
- void operator=(const mgListItem* from);
- bool operator==(const mgListItem& other) const;
- string value() const { return m_value; }
- string id() const { return m_id; }
- unsigned int count() const { return m_count; }
- bool valid() const { return m_valid; }
- private:
- bool m_valid;
- string m_value;
- string m_id;
- unsigned int m_count;
-};
-
-//! \brief represents a content item like an mp3 file.
-class mgContentItem
-{
- public:
- mgContentItem ();
-
- mgListItem* getKeyItem(mgKeyTypes kt);
-
- //! \brief copy constructor
- mgContentItem(const mgContentItem* c);
-
- //! \brief construct an item from an SQL row
- mgContentItem (const MYSQL_ROW row);
-//! \brief returns track id
- long getItemid () const
- {
- return m_trackid;
- }
-
-//! \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 name of the language
- string getLanguage () 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;
-
- bool Valid() const;
-
- private:
- mutable bool m_valid;
- mutable bool m_validated;
- long m_trackid;
- string m_title;
- mutable string m_mp3file;
- string m_realfile;
- string m_artist;
- string m_albumtitle;
- string m_genre1_id;
- string m_genre2_id;
- string m_genre1;
- string m_genre2;
- string m_bitrate;
- string m_language_id;
- string m_language;
- int m_year;
- int m_rating;
- int m_duration;
- int m_samplerate;
- int m_channels;
- bool readable(string filename) const;
-};
-
-extern mgListItem zeroitem;
-#endif
diff --git a/mg_db.c b/mg_db.c
new file mode 100644
index 0000000..f75ad81
--- /dev/null
+++ b/mg_db.c
@@ -0,0 +1,1628 @@
+/*! * \file mg_db.c
+ * \brief A capsule around database access
+ *
+ * \version $Revision: 1.2 $
+ * \date $Date: 2005-04-13 17:42:54 +0100 (Thu, 13 Apr 2005) $
+ * \author Wolfgang Rohdewald
+ * \author Responsible author: $Author: wolfgang61 $
+ */
+
+#include <string>
+#include <assert.h>
+
+using namespace std;
+
+#include "mg_db.h"
+#include "mg_item_gd.h"
+
+#include "mg_setup.h"
+#include "mg_tools.h"
+#ifdef HAVE_SQLITE
+#include "mg_db_gd_sqlite.h"
+#elif HAVE_PG
+#include "mg_db_gd_pg.h"
+#else
+#include "mg_db_gd_mysql.h"
+#endif
+
+
+#include <sys/stat.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fts.h>
+#include <mpegfile.h>
+#include <flacfile.h>
+
+static map <mgKeyTypes, map<string,string> > map_values;
+static map <mgKeyTypes, map<string,string> > map_ids;
+
+mgDbServer* DbServer;
+
+mgSQLString::~mgSQLString()
+{
+ delete m_str;
+ free(m_original);
+}
+
+void
+mgSQLString::Init(const char* s)
+{
+ // strip trailing spaces
+
+ m_original = strdup(s);
+ char *p=strrchr(m_original,' ');
+ if (p+1 == strchr(m_original,0))
+ while (p>=m_original && *p==' ')
+ *p-- = 0;
+
+#ifdef HAVE_SQLITE
+ m_str = new mgSQLStringSQLite(m_original);
+#elif HAVE_PG
+ m_str = new mgSQLStringPG(m_original);
+#else
+ m_str = new mgSQLStringMySQL(m_original);
+#endif
+}
+
+mgSQLString::mgSQLString(const char*s)
+{
+ Init(s);
+}
+
+mgSQLString::mgSQLString(string s)
+{
+ Init(s.c_str());
+}
+
+mgSQLString::mgSQLString(TagLib::String s)
+{
+ Init(s.toCString());
+}
+
+const char*
+mgSQLString::original() const
+{
+ return m_str->original();
+}
+
+char*
+mgSQLString::unquoted() const
+{
+ return m_str->unquoted();
+}
+
+
+char*
+mgSQLString::quoted() const
+{
+ return m_str->quoted();
+}
+
+bool
+mgSQLString::operator==(const mgSQLString& b) const
+{
+ return !strcmp(m_str->original(),b.original());
+}
+
+bool
+mgSQLString::operator==(const char* b) const
+{
+ return !strcmp(m_str->original(),b);
+}
+
+bool
+mgSQLString::operator==(string b) const
+{
+ return !strcmp(m_str->original(),b.c_str());
+}
+
+bool
+mgSQLString::operator!=(const mgSQLString& b) const
+{
+ return strcmp(m_str->original(),b.original());
+}
+
+bool
+mgSQLString::operator!=(const char* b) const
+{
+ return strcmp(m_str->original(),b);
+}
+
+bool
+mgSQLString::operator!=(string b) const
+{
+ return strcmp(m_str->original(),b.c_str());
+}
+
+void
+mgSQLString::operator=(const char* b)
+{
+ delete m_str;
+ Init(b);
+}
+
+void
+mgSQLString::operator=(const mgSQLString& b)
+{
+ delete m_str;
+ Init(b.original());
+}
+
+mgSQLStringImp::mgSQLStringImp()
+{
+ m_quoted = 0;
+}
+
+mgSQLStringImp::~mgSQLStringImp()
+{
+ if (m_quoted)
+ free(m_quoted);
+}
+
+
+char*
+mgSQLStringImp::quoted() const
+{
+ if (!m_quoted)
+ {
+ asprintf(&m_quoted,"'%s'",unquoted());
+ }
+ return m_quoted;
+}
+
+mgQuery::mgQuery(void *db,const char *s,mgQueryNoise noise)
+{
+ Init(db,s,noise);
+}
+
+void
+mgQuery::Init(void *db,const char *s,mgQueryNoise noise)
+{
+#ifdef HAVE_SQLITE
+ m_q = new mgQuerySQLite(db,s,noise);
+#elif HAVE_PG
+ m_q = new mgQueryPG(db,s,noise);
+#else
+ m_q = new mgQueryMySQL(db,s,noise);
+#endif
+}
+
+mgQuery::mgQuery(void *db,string s,mgQueryNoise noise)
+{
+ Init(db,s.c_str(),noise);
+}
+
+mgQuery::~mgQuery()
+{
+ delete m_q;
+}
+
+mgQueryImp::mgQueryImp(void *db,string sql,mgQueryNoise noise)
+{
+ m_db_handle = db;
+ m_sql = sql;
+ m_noise = noise;
+ m_rows = 0;
+ m_columns = 0;
+ m_cursor = 0;
+ m_errormessage=0;
+ m_optsql = optimize(m_sql).c_str();
+}
+
+void
+mgQueryImp::HandleErrors()
+{
+ mgDebug(5,"%X:%d rows: %s",m_db_handle,m_rows,m_optsql);
+ if (m_errormessage && strlen(m_errormessage))
+ switch (m_noise) {
+ case mgQueryNormal:
+ mgError("SQL Error in %s: %d/%s",m_optsql,m_rc,m_errormessage);
+ std::cout<<"ERROR in " << m_optsql << ":" << m_rc << "/" << m_errormessage<<std::endl;
+ break;
+ case mgQueryWarnOnly:
+ mgWarning("SQL Error in %s: %d/%s",m_optsql,m_rc,m_errormessage);
+ std::cout<<"WARNING in " << m_optsql << ":" << m_rc << "/" << m_errormessage<<std::endl;
+ break;
+ case mgQuerySilent:
+ break;
+ }
+}
+
+mgDb::mgDb(bool SeparateThread)
+{
+ m_database_found=false;
+ m_hasfolderfields=false;
+ m_separate_thread=SeparateThread;
+ m_connect_time=0;
+ m_create_time=0;
+}
+
+mgDb::~mgDb()
+{
+}
+
+void *
+mgDb::DbHandle() {
+ ServerConnect();
+ return ImplDbHandle();
+}
+
+string
+optimize (string & spar)
+{
+ string s = spar;
+ string::size_type tmp = s.find (" WHERE");
+ if (tmp != string::npos)
+ s.erase (tmp, 9999);
+ tmp = s.find (" ORDER");
+ if (tmp != string::npos)
+ s.erase (tmp, 9999);
+ string::size_type frompos = s.find (" FROM ") + 6;
+ if (s.substr (frompos).find (",") == string::npos)
+ {
+ string from = s.substr (frompos, 999) + '.';
+ string::size_type tbl;
+ while ((tbl = spar.find (from)) != string::npos)
+ {
+ spar.erase (tbl, from.size ());
+ }
+ }
+ return spar;
+}
+
+bool
+mgDb::SyncStart()
+{
+ if (!Connect())
+ return false;
+ // init random number generator
+ struct timeval tv;
+ struct timezone tz;
+ gettimeofday( &tv, &tz );
+ srandom( tv.tv_usec );
+ CreateFolderFields();
+ return true;
+}
+
+void
+mgDb::Sync(char * const * path_argv)
+{
+ if (!SyncStart())
+ return;
+ extern void showimportcount(unsigned int,bool final=false);
+
+ LoadMapInto("SELECT id,genre from genre",&m_Genres,0);
+ LoadMapInto("SELECT genre,id3genre from genre",&m_GenreIds,0);
+
+ StartTransaction();
+ if (the_setup.DeleteStaleReferences)
+ {
+ int count=0;
+ mgParts all;
+ vector<mgItem*> items;
+ LoadItemsInto(all,items);
+ for (unsigned int idx=0;idx<items.size();idx++)
+ {
+ mgItem* item = items[idx];
+ string fullpath=item->getSourceFile(true,true);
+ if (!item->Valid(true))
+ {
+ char *b;
+ asprintf(&b,"DELETE FROM tracks WHERE id=%ld",item->getItemid());
+ count += Execute(b);
+ free(b);
+ }
+ }
+ mgDebug(1,"Deleted %d entries because the file did not exist",count);
+ }
+
+ unsigned int importcount=0;
+ FTS *fts;
+ FTSENT *ftsent;
+ fts = fts_open( path_argv, FTS_LOGICAL, 0);
+ if (fts)
+ {
+ while ( (ftsent = fts_read(fts)) != NULL)
+ {
+ mode_t mode = ftsent->fts_statp->st_mode;
+ if (mode&S_IFDIR && ftsent->fts_info&FTS_D)
+ mgDebug(1,"Importing from %s",ftsent->fts_path);
+ if (!(mode&S_IFREG))
+ continue;
+ if (SyncFile(ftsent->fts_path))
+ importcount++;
+ if (importcount%1000==0)
+ showimportcount(importcount);
+ }
+ fts_close(fts);
+ }
+ Commit();
+ SyncEnd();
+ showimportcount(importcount,true);
+}
+
+
+string
+mgKeyNormal::id() const
+{
+ if (m_item)
+ return m_item->id();
+ else
+ return "";
+}
+
+bool
+mgKeyNormal::valid() const
+{
+ return m_item && m_item->valid();
+}
+
+string
+mgKeyNormal::value() const
+{
+ if (m_item)
+ return m_item->value();
+ else
+ return "";
+}
+
+
+mgKeyNormal::mgKeyNormal(const mgKeyNormal& k)
+{
+ m_kt = k.m_kt;
+ m_table = k.m_table;
+ m_field = k.m_field;
+ m_item = k.m_item->Clone();
+}
+
+mgKeyNormal::mgKeyNormal(const mgKeyTypes kt, string table, string field)
+{
+ m_kt = kt;
+ m_table = table;
+ m_field = field;
+ m_item = 0;
+}
+
+void
+mgKeyNormal::set(mgListItem* item)
+{
+ m_item=item->Clone();
+}
+
+mgListItem*
+mgKeyNormal::get()
+{
+ return m_item;
+}
+
+mgParts
+mgKeyNormal::Parts(mgDb *db, bool groupby) const
+{
+ mgParts result;
+ result.tables.push_back(table());
+ AddIdClause(db,result,expr(db));
+ if (groupby)
+ result.idfields.push_back(expr(db));
+ return result;
+}
+
+string
+mgKeyNormal::IdClause(mgDb *db,string what,string::size_type start,string::size_type len) const
+{
+ if (len==0)
+ len=string::npos;
+ if (id() == "'NULL'")
+ return what + " is NULL";
+ else if (len==string::npos)
+ return what + "=" + mgSQLString(id()).quoted();
+ else
+ {
+ return "substring("+what + ","+ltos(start+1)+","+ltos(len)+")="
+ + mgSQLString(id().substr(start,len)).quoted();
+ }
+}
+
+void
+mgKeyNormal::AddIdClause(mgDb *db,mgParts &result,string what) const
+{
+ if (valid())
+ result.clauses.push_back(IdClause(db,what));
+}
+
+bool
+mgKey::LoadMap() const
+{
+ if (map_sql().empty())
+ return false;
+ mgDb *db = GenerateDB();
+ db->LoadMapInto(map_sql(), &map_ids[Type()], &map_values[Type()]);
+ delete db;
+ return true;
+}
+
+
+mgKeyMaps KeyMaps;
+bool
+mgKeyMaps::loadvalues (mgKeyTypes kt) const
+{
+ if (map_ids[kt].size()>0)
+ return true;
+ mgKey* k = ktGenerate(kt);
+ bool result = k->LoadMap();
+ delete k;
+ return result;
+}
+
+string
+mgKeyMaps::value(mgKeyTypes kt, string idstr) const
+{
+ if (idstr.empty())
+ return idstr;
+ if (idstr=="NULL")
+ return idstr;
+ if (loadvalues (kt))
+ {
+ map<string,string>& valmap = map_values[kt];
+ map<string,string>::iterator it;
+ it = valmap.find(idstr);
+ if (it!=valmap.end())
+ {
+ string r = it->second;
+ if (!r.empty())
+ return r;
+ }
+ map_ids[kt].clear();
+ loadvalues(kt);
+ it = valmap.find(idstr);
+ if (it!=valmap.end())
+ return valmap[idstr];
+ }
+ return idstr;
+}
+
+string
+mgKeyMaps::id(mgKeyTypes kt, string valstr) const
+{
+ if (loadvalues (kt))
+ {
+ map<string,string>& idmap = map_ids[kt];
+ return idmap[valstr];
+ }
+ return valstr;
+}
+
+
+class mgRefParts : public mgParts {
+ public:
+ mgRefParts(const mgReference* r);
+};
+
+
+strlist& operator+=(strlist&a, strlist b)
+{
+ a.insert(a.end(), b.begin(),b.end());
+ return a;
+}
+
+strlist operator+(strlist&a, strlist&b)
+{
+ strlist result;
+ result.insert(result.end(),a.begin(),a.end());
+ result.insert(result.end(),b.begin(),b.end());
+ return result;
+}
+
+
+string
+sql_list (string prefix,strlist v,string sep,string postfix)
+{
+ string result = "";
+ for (list < string >::iterator it = v.begin (); it != v.end (); ++it)
+ addsep (result, sep, *it);
+ if (!result.empty())
+ {
+ result.insert(0,prefix+" ");
+ result += postfix;
+ }
+ return result;
+}
+
+
+mgParts&
+mgParts::operator+=(mgParts a)
+{
+ valuefields += a.valuefields;
+ idfields += a.idfields;
+ tables += a.tables;
+ clauses += a.clauses;
+ return *this;
+}
+
+mgRefParts::mgRefParts(const mgReference* r)
+{
+ tables.push_back(r->t1());
+ tables.push_back(r->t2());
+ clauses.push_back(r->t1() + '.' + r->f1() + '=' + r->t2() + '.' + r->f2());
+}
+
+void
+mgParts::Dump(string where) const
+{
+ mgDebug(1,"%X:%s:tables:%s",this,where.c_str(),sql_list(" FROM",tables).c_str());
+ mgDebug(1," clauses:%s",sql_list(" WHERE",clauses," AND ").c_str());
+ mgDebug(1," idfields:%s",sql_list("SELECT",idfields).c_str());
+ mgDebug(1," valuefields:%s",sql_list("SELECT",valuefields).c_str());
+}
+
+void
+mgParts::Prepare()
+{
+ tables.sort();
+ tables.unique();
+ strlist::reverse_iterator rit;
+ string prevtable = "";
+ rest.InitReferences();
+ positives.clear();
+ for (rit = tables.rbegin(); rit != tables.rend(); ++rit)
+ {
+ if (!prevtable.empty())
+ {
+ rest.InitReferences();
+ ConnectTables(prevtable,*rit);
+ }
+ prevtable = *rit;
+ }
+ for (unsigned int i = 0 ; i < positives.size(); i++)
+ {
+ *this += mgRefParts(positives[i]);
+ }
+ tables.sort();
+ tables.unique();
+ push_table_to_front("tracks");
+ push_table_to_front("playlistitem");
+ clauses.sort();
+ clauses.unique();
+}
+
+void
+mgParts::push_table_to_front(string table)
+{
+ strlist::iterator it;
+ for (it = tables.begin(); it != tables.end(); ++it)
+ {
+ if (*it==table)
+ {
+ tables.erase(it);
+ tables.push_front(table);
+ break;
+ }
+ }
+}
+
+string
+mgParts::sql_select(bool distinct)
+{
+ if (!special_statement.empty())
+ return special_statement;
+ Prepare();
+ string result;
+ if (distinct)
+ {
+ idfields.push_back("COUNT(*)");
+ result = sql_list("SELECT",idfields);
+ idfields.pop_back();
+ }
+ else
+ {
+ idfields.push_back("1");
+ result = sql_list("SELECT",valuefields+idfields);
+ }
+ if (!result.empty())
+ {
+ result += sql_list(" FROM",tables);
+ result += sql_list(" WHERE",clauses," AND ");
+ if (distinct)
+ {
+ result += sql_list(" GROUP BY",idfields);
+ }
+ }
+ return result;
+}
+
+string
+mgParts::sql_count()
+{
+ Prepare();
+#if defined (HAVE_PG) || defined(HAVE_SQLITE) || MYSQL_VERSION_ID >= 40111
+ string result = sql_list("SELECT COUNT(*) FROM ( SELECT",idfields,",","");
+ if (result.empty())
+ return result;
+ result += sql_list(" FROM",tables);
+ result += sql_list(" WHERE",clauses," AND ");
+ result += sql_list(" GROUP BY",idfields);
+ result += ") AS xx";
+#else
+ string result = sql_list("SELECT COUNT(DISTINCT",idfields,",",")");
+ if (result.empty())
+ return result;
+ result += sql_list(" FROM",tables);
+ result += sql_list(" WHERE",clauses," AND ");
+ optimize(result);
+#endif
+ return result;
+}
+
+mgReference::mgReference(string t1,string f1,string t2,string f2)
+{
+ m_t1 = t1;
+ m_f1 = f1;
+ m_t2 = t2;
+ m_f2 = f2;
+}
+
+void
+mgReferences::InitReferences()
+{
+ // define them such that no circle is possible
+ for (unsigned int idx = 0 ; idx < size() ; idx ++)
+ delete operator[](idx);
+ clear();
+ push_back(new mgReference ("tracks","id","playlistitem","trackid"));
+ push_back(new mgReference ("playlist","id","playlistitem","playlist"));
+ push_back(new mgReference ("tracks","sourceid","album","cddbid"));
+ push_back(new mgReference ("tracks","lang","language","id"));
+}
+
+unsigned int
+mgReferences::CountTable(string table) const
+{
+ unsigned int result = 0;
+ for (unsigned int i=0 ; i<size(); i++ )
+ {
+ mgReference* r = operator[](i);
+ if (table==r->t1() || table==r->t2())
+ result++;
+ }
+ return result;
+}
+
+bool
+mgReference::Equal(string table1, string table2) const
+{
+ return ((t1()==table1) && (t2()==table2))
+ || ((t1()==table2) && (t2()==table1));
+}
+
+void
+mgParts::ConnectTables(string table1, string table2)
+{
+ // same table?
+ if (table1 == table2)
+ return;
+
+ // backend specific:
+ if (table1=="genre")
+ {
+ ConnectTables("tracks",table2);
+ return;
+ }
+ if (table2=="genre")
+ {
+ ConnectTables("tracks",table1);
+ return;
+ }
+ // do not connect aliases. See sql_delete_from_collection
+ if (table1.find(" as ")!=string::npos) return;
+ if (table2.find(" as ")!=string::npos) return;
+ if (table1.find(" AS ")!=string::npos) return;
+ if (table2.find(" AS ")!=string::npos) return;
+
+ // now the generic part:
+ for (unsigned int i=0 ; i<rest.size(); i++ )
+ {
+ mgReference* r = rest[i];
+ if (r->Equal(table1,table2))
+ {
+ rest.erase(rest.begin()+i);
+ positives.push_back(r);
+ return;
+ }
+ }
+again:
+ for (unsigned int i=0 ; i<rest.size(); i++ )
+ {
+ mgReference* r = rest[i];
+ unsigned int ct1=rest.CountTable(r->t1());
+ unsigned int ct2=rest.CountTable(r->t2());
+ if (ct1==1 || ct2==1)
+ {
+ rest.erase(rest.begin()+i);
+ if (ct1==1 && ct2==1)
+ {
+ if (r->Equal(table1,table2))
+ {
+ positives.push_back(r);
+ return;
+ }
+ else
+ {
+ delete r;
+ continue;
+ }
+ }
+ else if (ct1==1)
+ {
+ if (r->t1()==table1)
+ {
+ positives.push_back(r);
+ ConnectTables(r->t2(),table2);
+ }
+ else if (r->t1()==table2)
+ {
+ positives.push_back(r);
+ ConnectTables(table1,r->t2());
+ }
+ else
+ {
+ delete r;
+ goto again;
+ }
+ }
+ else
+ {
+ if (r->t2()==table1)
+ {
+ positives.push_back(r);
+ ConnectTables(r->t1(),table2);
+ }
+ else if (r->t2()==table2)
+ {
+ positives.push_back(r);
+ ConnectTables(table1,r->t1());
+ }
+ else
+ {
+ delete r;
+ goto again;
+ }
+ }
+ }
+ }
+}
+
+
+mgParts::mgParts()
+{
+ special_statement="";
+ orderByCount = false;
+}
+
+mgParts::~mgParts()
+{
+}
+
+unsigned long
+mgDb::exec_count( const string sql)
+{
+ unsigned long result = 0;
+ if (Connect())
+ result = atol (get_col0 ( sql).c_str ());
+ return result;
+}
+
+
+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;
+};
+
+void mgDb::FillTables()
+{
+#include "mg_tables.h"
+ int len = sizeof( genres ) / sizeof( genres_t );
+ StartTransaction();
+ Execute("INSERT INTO genre (id,genre) VALUES('NULL','No Genre')");
+ 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");
+ sprintf(b,"INSERT INTO genre (id,id3genre,genre) VALUES ('%s',%s,%s)",
+ genres[i].id,id3genre,mgSQLString(genres[i].name).quoted());
+ Execute(b);
+ }
+ len = sizeof( languages ) / sizeof( lang_t );
+ Execute("INSERT INTO language (id,language) VALUES('NULL','Instrumental')");
+ for( int i=0; i < len; i ++ )
+ {
+ char b[600];
+ sprintf(b,"INSERT INTO language (id,language) VALUES('%s',%s)",
+ languages[i].id,
+ mgSQLString(languages[i].name).quoted());
+ Execute(b);
+ }
+ len = sizeof( musictypes ) / sizeof( musictypes_t );
+ for( int i=0; i < len; i ++ )
+ {
+ char b[600];
+ sprintf(b,"INSERT INTO musictype (musictype) VALUES('%s')",
+ musictypes[i].name);
+ Execute(b);
+ }
+ len = sizeof( sources ) / sizeof( sources_t );
+ for( int i=0; i < len; i ++ )
+ {
+ char b[600];
+ sprintf(b,"INSERT INTO source (source) VALUES('%s')",
+ sources[i].name);
+ Execute(b);
+ }
+ Commit();
+}
+
+mgSQLString
+mgDb::Build_cddbid(const mgSQLString& artist) const
+{
+ char *s;
+ asprintf(&s,"%ld-%.9s",random(),artist.original());
+ return mgSQLString(s);
+}
+
+mgSQLString
+mgDb::getAlbum(const char *filename,const mgSQLString& c_album,
+ const mgSQLString& c_artist)
+{
+ char *b;
+ asprintf(&b,"SELECT cddbid FROM album"
+ " WHERE title=%s AND artist=%s",c_album.quoted(),c_artist.quoted());
+ mgSQLString result(get_col0(b));
+ free(b);
+ if (result=="NULL")
+ {
+ char *directory = strdup(filename);
+ char *slash=strrchr(directory,'/');
+ if (slash)
+ *slash=0;
+ mgSQLString c_directory(directory);
+ free(directory);
+ char *where;
+ asprintf(&where,"WHERE tracks.sourceid=album.cddbid "
+ "AND %s=%s "
+ "AND album.title=%s",
+ Directory().c_str(),c_directory.quoted(),
+ c_album.quoted());
+ // how many artists will the album have after adding this one?
+ asprintf(&b,"SELECT distinct album.artist FROM album, tracks %s ",where);
+ mgQuery q(DbHandle(),b);
+ free(b);
+ long new_album_artists = q.Rows();
+ mgSQLString buf("");
+ if (new_album_artists==1)
+ {
+ buf=mgSQLString(q.Next()[0]);
+ if (buf==c_artist)
+ new_album_artists++;
+ }
+ else
+ buf="";
+ if (new_album_artists>1 && strcmp(buf.original(),"Various Artists"))
+ // is the album multi artist and not yet marked as such?
+ {
+ asprintf(&b,"SELECT album.cddbid FROM album, tracks %s",where);
+ result=mgSQLString(get_col0(b));
+ free(b);
+ asprintf(&b,"UPDATE album SET artist='Various Artists' WHERE cddbid=%s",result.quoted());
+ Execute(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
+ result=Build_cddbid(c_artist);
+ char *b;
+ asprintf(&b,"INSERT INTO album (title,artist,cddbid) "
+ "VALUES(%s,%s,%s)",
+ c_album.quoted(),c_artist.quoted(),result.quoted());
+ Execute(b);
+ free(b);
+ }
+ free(where);
+ }
+ return result;
+}
+
+TagLib::String
+mgDb::getId3v2Tag(TagLib::ID3v2::Tag *id3v2tags,const char *name) const
+{
+ TagLib::String result;
+ TagLib::ID3v2::FrameList l = id3v2tags->frameListMap()[name];
+ if (!l.isEmpty())
+ result = l.front()->toString();
+ return result;
+}
+
+void
+mgDb::get_tags(TagLib::ID3v2::Tag *tags)
+{
+ if (!tags)
+ return;
+ m_TLAN = getId3v2Tag(tags,"TLAN");
+ m_TCON = getId3v2Tag(tags,"TCON");
+}
+
+void
+mgDb::get_ID3v2_Tags(const char *filename)
+{
+ if (!strcasecmp(extension(filename),"flac"))
+ {
+ TagLib::FLAC::File f(filename);
+ get_tags(f.ID3v2Tag());
+ }
+ else if (!strcasecmp(extension(filename),"mp3"))
+ {
+ TagLib::MPEG::File f(filename);
+ get_tags(f.ID3v2Tag());
+ }
+}
+
+void
+mgDb::DefineGenre(const string genre)
+{
+ mgQuery q1(DbHandle(),"SELECT id FROM genre WHERE id ='z'" );
+ if (q1.Rows()==0)
+ {
+ Execute("INSERT INTO genre (id,genre) VALUES('z','Extra')");
+ for (char c='a';c<='z';c++)
+ {
+ char g[20];
+ strcpy(g,"Extra");
+ if (c!='a')
+ sprintf(strchr(g,0)," %c",c);
+ char *b;
+ asprintf(&b,"INSERT INTO genre (id,genre) VALUES('z%c','%s')",c,g);
+ Execute(b);
+ free(b);
+ }
+ }
+ mgQuery q(DbHandle(),"SELECT id FROM genre WHERE id LIKE 'z__'" );
+ char **r;
+ char *newid=0;
+ while ((r = q.Next()))
+ {
+ newid = r[0];
+ }
+ if (!newid)
+ newid="zaa";
+ else
+ {
+ newid[2]++;
+ if (newid[2]>'z')
+ {
+ newid[1]++;
+ if (newid[1]>'z')
+ return;
+ newid[2]='a';
+ }
+ }
+ char *b;
+ mgSQLString c_genre(genre);
+ asprintf(&b,"INSERT INTO genre (id,genre) VALUES('%s',%s)",newid,c_genre.quoted());
+ Execute(b);
+ free(b);
+ m_Genres[genre]=newid;
+ mgDebug(1,"Added new genre %s",genre.c_str());
+}
+
+mgSQLString
+mgDb::getGenre1(TagLib::FileRef& f)
+{
+ string genre1 = f.tag()->genre().toCString();
+ if (genre1.empty())
+ {
+ genre1 = m_TCON.toCString();
+ const char *tcon=genre1.c_str();
+ char *rparen=strchr(tcon,')');
+ if (tcon[0]=='(' && rparen)
+ {
+ *rparen=0;
+ genre1 = m_GenreIds[tcon+1];
+ }
+ }
+ if (genre1.empty())
+ return mgSQLString("NULL");
+ if (m_Genres[genre1]=="")
+ DefineGenre(genre1);
+ return m_Genres[genre1];
+}
+
+bool
+mgDb::SyncFile(const char *filename)
+{
+ char *ext = extension(filename);
+ if (strcasecmp(ext,"flac")
+ && strcasecmp(ext,"wav")
+ && strcasecmp(ext,"ogg")
+ && strcasecmp(ext,"mp3"))
+ return false;
+
+ char sql[7000];
+ if (!strncmp(filename,"./",2)) // strip leading ./
+ filename += 2;
+ const char *cfilename=filename;
+ if (isdigit(filename[0]) && isdigit(filename[1]) && filename[2]=='/' && !strchr(filename+3,'/'))
+ cfilename=cfilename+3;
+ if (strlen(cfilename)>255)
+ {
+ mgWarning("Length of file exceeds database field capacity: %s", filename);
+ return false;
+ }
+ TagLib::FileRef f( filename) ;
+
+ if( f.isNull() || !f.tag() )
+ {
+ if( !strcasecmp( ext, "wav" ) )
+ {
+ mgDebug(2,"Importing %s",filename);
+ char *folders[4];
+ char *fbuf=SeparateFolders(filename,folders,4);
+ mgSQLString c_folder1(folders[0]);
+ mgSQLString c_folder2(folders[1]);
+ mgSQLString c_folder3(folders[2]);
+ mgSQLString c_folder4(folders[3]);
+ free(fbuf);
+
+ mgSQLString c_artist("Unknown");
+ mgSQLString c_title("Unknown");
+ mgSQLString c_album("Unassigned");
+ mgSQLString c_lang("");
+ mgSQLString c_genre1("");
+ mgSQLString c_cddbid( getAlbum(filename, c_album, c_artist) );
+ mgSQLString c_mp3file(cfilename);
+ sprintf(sql,"SELECT id from tracks WHERE mp3file=%s",c_mp3file.quoted());
+
+ string id = get_col0(sql);
+ if (id!="NULL")
+ {
+ 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.quoted(),c_title.quoted(),f.tag()->year(),c_cddbid.quoted(),
+ f.tag()->track(), 0, 0, 0,
+ 2, c_genre1.quoted(), c_lang.quoted(), atol(id.c_str() ) );
+ }
+ else
+ {
+ sprintf( sql,"INSERT INTO tracks "
+ "(artist,title,year,sourceid,"
+ "tracknb,mp3file,length,bitrate,samplerate,"
+ "channels,genre1,genre2,lang,folder1,folder2,"
+ "folder3,folder4) "
+ "VALUES (%s,%s,%u,%s,"
+ "%u,%s,%d,%d,%d,"
+ "%d,%s,'',%s,%s,%s,%s,%s)",
+ c_artist.quoted(),c_title.quoted(), 0, c_cddbid.quoted(),
+ f.tag()->track(), c_mp3file.quoted(), 0,
+ 0, 0,
+ 2, c_genre1.quoted(), c_lang.quoted(),
+ c_folder1.quoted(),c_folder2.quoted(),
+ c_folder3.quoted(),c_folder4.quoted());
+ }
+ Execute(sql);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ mgDebug(2,"Importing %s",filename);
+ TagLib::AudioProperties *ap = f.audioProperties();
+ get_ID3v2_Tags(filename);
+ char *folders[4];
+ char *fbuf=SeparateFolders(filename,folders,4);
+ mgSQLString c_folder1(folders[0]);
+ mgSQLString c_folder2(folders[1]);
+ mgSQLString c_folder3(folders[2]);
+ mgSQLString c_folder4(folders[3]);
+ free(fbuf);
+ mgSQLString c_artist(f.tag()->artist());
+ mgSQLString c_title(f.tag()->title());
+ mgSQLString c_album(f.tag()->album());
+ if (strlen(c_album.original())==0)
+ c_album = "Unassigned";
+ mgSQLString c_lang(m_TLAN);
+ mgSQLString c_genre1(getGenre1(f));
+ mgSQLString c_cddbid(getAlbum(filename,c_album,c_artist));
+ mgSQLString c_mp3file(cfilename);
+ sprintf(sql,"SELECT id from tracks WHERE mp3file=%s",c_mp3file.quoted());
+ string id = get_col0(sql);
+ if (id!="NULL")
+ {
+ 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.quoted(),c_title.quoted(),f.tag()->year(),c_cddbid.quoted(),
+ f.tag()->track(),ap->length(),ap->bitrate(),ap->sampleRate(),
+ ap->channels(),c_genre1.quoted(),c_lang.quoted(),atol(id.c_str()));
+ }
+ else
+ {
+ sprintf(sql,"INSERT INTO tracks "
+ "(artist,title,year,sourceid,"
+ "tracknb,mp3file,length,bitrate,samplerate,"
+ "channels,genre1,genre2,lang,folder1,folder2,"
+ "folder3,folder4) "
+ "VALUES (%s,%s,%u,%s,"
+ "%u,%s,%d,%d,%d,"
+ "%d,%s,'',%s,%s,%s,%s,%s)",
+ c_artist.quoted(),c_title.quoted(),f.tag()->year(),c_cddbid.quoted(),
+ f.tag()->track(),c_mp3file.quoted(),ap->length(),
+ ap->bitrate(),ap->sampleRate(),
+ ap->channels(),c_genre1.quoted(),c_lang.quoted(),
+ c_folder1.quoted(),c_folder2.quoted(),
+ c_folder3.quoted(),c_folder4.quoted());
+ }
+ Execute(sql);
+ return true;
+}
+
+string
+mgDb::get_col0( const string sql)
+{
+ mgQuery q(DbHandle(),sql);
+ char **r = q.Next();
+ if (r)
+ return r[0];
+ else
+ return "NULL";
+}
+
+int
+mgDb::Execute(const string sql)
+{
+ if (sql.empty())
+ return 0;
+ if (!Connect())
+ return 0;
+ mgQuery q(DbHandle(),sql);
+ return q.Rows();
+}
+
+void
+mgDb::LoadMapInto(string sql,map<string,string>*idmap,map<string,string>*valmap)
+{
+ if (!valmap && !idmap)
+ return;
+ if (!Connect())
+ return;
+ mgQuery q(DbHandle(),sql);
+ char **row;
+ while ((row = q.Next()))
+ if (row[0] && row[1])
+ {
+ if (valmap) (*valmap)[row[0]] = row[1];
+ if (idmap) (*idmap)[row[1]] = row[0];
+ }
+}
+
+string
+mgDb::LoadItemsInto(mgParts& what,vector<mgItem*>& items)
+{
+ if (!Connect())
+ return "";
+ what.idfields.clear();
+ what.valuefields.clear();
+ what.idfields.push_back("tracks.id");
+ what.idfields.push_back("tracks.title");
+ what.idfields.push_back("tracks.mp3file");
+ what.idfields.push_back("tracks.artist");
+ what.idfields.push_back("album.title");
+ what.idfields.push_back("tracks.genre1");
+ what.idfields.push_back("tracks.genre2");
+ what.idfields.push_back("tracks.bitrate");
+ what.idfields.push_back("tracks.year");
+ what.idfields.push_back("tracks.rating");
+ what.idfields.push_back("tracks.length");
+ what.idfields.push_back("tracks.samplerate");
+ what.idfields.push_back("tracks.channels");
+ what.idfields.push_back("tracks.lang");
+ what.idfields.push_back("tracks.tracknb");
+ what.idfields.push_back("album.coverimg");
+ what.tables.push_back("tracks");
+ what.tables.push_back("album");
+ string result = what.sql_select(false);
+ for (unsigned int idx=0;idx<items.size();idx++)
+ delete items[idx];
+ items.clear ();
+ mgQuery q(DbHandle(),result);
+ char **row;
+ while ((row = q.Next()))
+ items.push_back (new mgItemGd (row));
+ return result;
+}
+
+
+string
+mgDb::LoadValuesInto(mgParts& what,mgKeyTypes tp,vector<mgListItem*>& listitems,bool distinct)
+{
+ if (!Connect())
+ return "";
+ string result = what.sql_select(distinct);
+ listitems.clear ();
+ mgQuery q(DbHandle(),result);
+ if (q.Rows())
+ assert(q.Columns()>=2);
+ char **row;
+ while ((row = q.Next()))
+ {
+ if (!row[0]) continue;
+ string r0 = row[0];
+ mgListItem* n = new mgListItem;
+ long count=1;
+ if (q.Columns()>1)
+ count = atol(row[q.Columns()-1]);
+ if (q.Columns()==3)
+ {
+ if (!row[1])
+ {
+ delete n;
+ continue;
+ }
+ n->set(row[0],row[1],count);
+ }
+ else
+ n->set(KeyMaps.value(tp,r0),r0,count);
+ listitems.push_back(n);
+ }
+ return result;
+}
+
+int
+mgDb::AddToCollection( const string Name, const vector<mgItem*>&items,mgParts *what)
+{
+ if (Name.empty())
+ return 0;
+ if (!Connect())
+ return 0;
+ CreateCollection(Name);
+ string listid = KeyMaps.id(keyGdCollection,Name);
+ if (listid.empty())
+ return 0;
+ StartTransaction();
+ // insert all tracks:
+ int result = 0;
+ for (unsigned int i = 0; i < items.size(); i++)
+ result += Execute("INSERT INTO playlistitem VALUES( " + listid + ","
+ + ltos (items[i]->getItemid ()) +")");
+ Commit();
+ return result;
+}
+
+int
+mgDb::RemoveFromCollection (const string Name, const vector<mgItem*>&items,mgParts* what)
+{
+ if (Name.empty())
+ return 0;
+ if (!Connect())
+ return 0;
+ string listid = KeyMaps.id(keyGdCollection,Name);
+ if (listid.empty())
+ return 0;
+ StartTransaction();
+ // remove all tracks:
+ int result = 0;
+ for (unsigned int i = 0; i < items.size(); i++)
+ result += Execute("DELETE FROM playlistitem WHERE playlist="+listid+
+ " AND trackid = " + ltos (items[i]->getItemid ()));
+ Commit();
+ return result;
+}
+
+bool
+mgDb::DeleteCollection (const string Name)
+{
+ if (!Connect()) return false;
+ ClearCollection(Name);
+ return Execute (string("DELETE FROM playlist WHERE title=") + mgSQLString (Name).quoted()) == 1;
+}
+
+void
+mgDb::ClearCollection (const string Name)
+{
+ if (!Connect()) return;
+ string listid = KeyMaps.id(keyGdCollection,Name);
+ Execute (string("DELETE FROM playlistitem WHERE playlist=")+mgSQLString(listid).quoted());
+}
+
+bool
+mgDb::CreateCollection (const string Name)
+{
+ if (!Connect()) return false;
+ string name = mgSQLString(Name).quoted();
+ if (exec_count("SELECT count(title) FROM playlist WHERE title = " + name)>0)
+ return false;
+ Execute ("INSERT INTO playlist (title,author,created) VALUES(" + name + ",'VDR',"+Now()+")");
+ return true;
+}
+
+class mgKeyGdTrack : public mgKeyNormal {
+ public:
+ mgKeyGdTrack() : mgKeyNormal(keyGdTrack,"tracks","tracknb") {};
+ mgParts Parts(mgDb *db,bool groupby=false) const;
+ mgSortBy SortBy() const { return mgSortByIdNum; }
+};
+
+class mgKeyGdAlbum : public mgKeyNormal {
+ public:
+ mgKeyGdAlbum() : mgKeyNormal(keyGdAlbum,"album","title") {};
+ mgParts Parts(mgDb *db,bool groupby=false) const;
+};
+
+mgParts
+mgKeyGdAlbum::Parts(mgDb *db,bool groupby) const
+{
+ mgParts result = mgKeyNormal::Parts(db,groupby);
+ result.tables.push_back("tracks");
+ return result;
+}
+
+class mgKeyGdFolder : public mgKeyNormal {
+ public:
+ mgKeyGdFolder(mgKeyTypes kt,const char *fname)
+ : mgKeyNormal(kt,"tracks",fname) { m_enabled=-1;};
+ bool Enabled(mgDb *db);
+ private:
+ int m_enabled;
+};
+
+
+bool
+mgKeyGdFolder::Enabled(mgDb *db)
+{
+ if (m_enabled<0)
+ m_enabled = db->FieldExists("tracks", m_field);
+ return (m_enabled==1);
+}
+
+class mgKeyGdGenres : public mgKeyNormal {
+ public:
+ mgKeyGdGenres() : mgKeyNormal(keyGdGenres,"tracks","genre1") {};
+ mgKeyGdGenres(mgKeyTypes kt) : mgKeyNormal(kt,"tracks","genre1") {};
+ mgParts Parts(mgDb *db,bool groupby=false) const;
+ protected:
+ string map_sql() const;
+ virtual unsigned int genrelevel() const { return 4; }
+ private:
+ string GenreClauses(mgDb *db,bool groupby) const;
+};
+
+class mgKeyGdGenre1 : public mgKeyGdGenres
+{
+ public:
+ mgKeyGdGenre1() : mgKeyGdGenres(keyGdGenre1) {}
+ unsigned int genrelevel() const { return 1; }
+};
+
+class mgKeyGdGenre2 : public mgKeyGdGenres
+{
+ public:
+ mgKeyGdGenre2() : mgKeyGdGenres(keyGdGenre2) {}
+ unsigned int genrelevel() const { return 2; }
+};
+
+class mgKeyGdGenre3 : public mgKeyGdGenres
+{
+ public:
+ mgKeyGdGenre3() : mgKeyGdGenres(keyGdGenre3) {}
+ unsigned int genrelevel() const { return 3; }
+};
+
+string
+mgKeyGdGenres::map_sql() const
+{
+ if (genrelevel()==4)
+ return "SELECT id,genre FROM genre";
+ else
+ return string("SELECT id,genre FROM genre WHERE LENGTH(id)<="+ltos(genrelevel()));
+}
+
+string
+mgKeyGdGenres::GenreClauses(mgDb *db,bool groupby) const
+{
+ strlist g1;
+ strlist g2;
+
+ if (groupby)
+ if (genrelevel()==4)
+ {
+ g1.push_back("tracks.genre1=genre.id");
+ g2.push_back("tracks.genre2=genre.id");
+ }
+ else
+ {
+ g1.push_back("substring(tracks.genre1,1,"+ltos(genrelevel())+")=genre.id");
+ g2.push_back("substring(tracks.genre2,1,"+ltos(genrelevel())+")=genre.id");
+ }
+
+ if (valid())
+ {
+ unsigned int len=genrelevel();
+ if (len==4) len=0;
+ g1.push_back(IdClause(db,"tracks.genre1",0,genrelevel()));
+ g2.push_back(IdClause(db,"tracks.genre2",0,genrelevel()));
+ }
+
+ if (db->NeedGenre2())
+ {
+ string o1=sql_list("(",g1," AND ",")");
+ if (o1.empty())
+ return "";
+ string o2=sql_list("(",g2," AND ",")");
+ return string("(") + o1 + " OR " + o2 + string(")");
+ }
+ else
+ return sql_list("",g1," AND ");
+}
+
+
+mgParts
+mgKeyGdGenres::Parts(mgDb *db,bool groupby) const
+{
+ mgParts result;
+ result.clauses.push_back(GenreClauses(db,groupby));
+ result.tables.push_back("tracks");
+ if (groupby)
+ {
+ result.valuefields.push_back("genre.genre");
+ if (genrelevel()==4)
+ result.idfields.push_back("genre.id");
+ else
+ result.idfields.push_back("substring(genre.id,1,"+ltos(genrelevel())+")");
+ result.tables.push_back("genre");
+ }
+ return result;
+}
+
+
+class mgKeyGdLanguage : public mgKeyNormal {
+ public:
+ mgKeyGdLanguage() : mgKeyNormal(keyGdLanguage,"tracks","lang") {};
+ mgParts Parts(mgDb *db,bool groupby=false) const;
+ protected:
+ string map_sql() const { return "SELECT id,language FROM language"; }
+};
+
+class mgKeyGdCollection: public mgKeyNormal {
+ public:
+ mgKeyGdCollection() : mgKeyNormal(keyGdCollection,"playlist","id") {};
+ mgParts Parts(mgDb *db,bool groupby=false) const;
+ protected:
+ string map_sql() const { return "SELECT id,title FROM playlist"; }
+};
+class mgKeyGdCollectionItem : public mgKeyNormal {
+ public:
+ mgKeyGdCollectionItem() : mgKeyNormal(keyGdCollectionItem,"playlistitem","trackid") {};
+ mgParts Parts(mgDb *db,bool groupby=false) const;
+ mgSortBy SortBy() const { return mgSortNone; }
+};
+
+class mgKeyDecade : public mgKeyNormal {
+ public:
+ mgKeyDecade() : mgKeyNormal(keyGdDecade,"tracks","year") {}
+ string expr(mgDb* db) const { return db->DecadeExpr(); }
+};
+
+mgKey*
+ktGenerate(const mgKeyTypes kt)
+{
+ mgKey* result = 0;
+ switch (kt)
+ {
+ case keyGdGenres: result = new mgKeyGdGenres;break;
+ case keyGdGenre1: result = new mgKeyGdGenre1;break;
+ case keyGdGenre2: result = new mgKeyGdGenre2;break;
+ case keyGdGenre3: result = new mgKeyGdGenre3;break;
+ case keyGdFolder1:result = new mgKeyGdFolder(keyGdFolder1,"folder1");break;
+ case keyGdFolder2:result = new mgKeyGdFolder(keyGdFolder2,"folder2");break;
+ case keyGdFolder3:result = new mgKeyGdFolder(keyGdFolder3,"folder3");break;
+ case keyGdFolder4:result = new mgKeyGdFolder(keyGdFolder4,"folder4");break;
+ case keyGdArtist: result = new mgKeyNormal(kt,"tracks","artist");break;
+ case keyGdArtistABC: result = new mgKeyABC(kt,"tracks","artist");break;
+ case keyGdTitle: result = new mgKeyNormal(kt,"tracks","title");break;
+ case keyGdTitleABC: result = new mgKeyABC(kt,"tracks","title");break;
+ case keyGdTrack: result = new mgKeyGdTrack;break;
+ case keyGdDecade: result = new mgKeyDecade;break;
+ case keyGdAlbum: result = new mgKeyGdAlbum;break;
+ case keyGdCreated: result = new mgKeyDate(kt,"tracks","created");break;
+ case keyGdModified: result = new mgKeyDate(kt,"tracks","modified");break;
+ case keyGdCollection: result = new mgKeyGdCollection;break;
+ case keyGdCollectionItem: result = new mgKeyGdCollectionItem;break;
+ case keyGdLanguage: result = new mgKeyGdLanguage;break;
+ case keyGdRating: result = new mgKeyNormal(kt,"tracks","rating");break;
+ case keyGdYear: result = new mgKeyNormal(kt,"tracks","year");break;
+ case keyGdUnique: result = new mgKeyNormal(kt,"tracks","id");break;
+ default: result = 0; break;
+ }
+ return result;
+}
+
+mgParts
+mgKeyGdTrack::Parts(mgDb *db,bool groupby) const
+{
+ mgParts result;
+ result.tables.push_back("tracks");
+ AddIdClause(db,result,"tracks.tracknb");
+ if (groupby)
+ {
+ result.valuefields.push_back("tracks.title");
+ result.idfields.push_back("tracks.tracknb");
+ }
+ return result;
+}
+
+mgParts
+mgKeyGdLanguage::Parts(mgDb *db,bool groupby) const
+{
+ mgParts result;
+ AddIdClause(db,result,"tracks.lang");
+ result.tables.push_back("tracks");
+ if (groupby)
+ {
+ result.valuefields.push_back("language.language");
+ result.idfields.push_back("tracks.lang");
+ result.tables.push_back("language");
+ }
+ return result;
+}
+
+mgParts
+mgKeyGdCollection::Parts(mgDb *db,bool groupby) const
+{
+ mgParts result;
+ if (groupby)
+ {
+ result.tables.push_back("playlist");
+ AddIdClause(db,result,"playlist.id");
+ result.valuefields.push_back("playlist.title");
+ result.idfields.push_back("playlist.id");
+ }
+ else
+ {
+ result.tables.push_back("playlistitem");
+ AddIdClause(db,result,"playlistitem.playlist");
+ }
+ return result;
+}
+
+mgParts
+mgKeyGdCollectionItem::Parts(mgDb *db,bool groupby) const
+{
+ mgParts result;
+ result.tables.push_back("playlistitem");
+ if (groupby)
+ {
+ result.tables.push_back("tracks");
+ result.valuefields.push_back("tracks.title");
+ result.idfields.push_back("tracks.id");
+ }
+ return result;
+}
diff --git a/mg_db.h b/mg_db.h
new file mode 100644
index 0000000..73ea605
--- /dev/null
+++ b/mg_db.h
@@ -0,0 +1,313 @@
+/*!
+ * \file mg_db.h
+ * \brief A generic capsule around database access
+ *
+ * \version $Revision: 1.2 $
+ * \date $Date: 2005-04-13 17:42:54 +0100 (Thu, 13 Apr 2005) $
+ * \author Wolfgang Rohdewald
+ * \author Responsible author: $Author: wolfgang61 $
+ */
+
+#ifndef __MG_DB_H
+#define __MG_DB_H
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <string>
+#include <list>
+#include <vector>
+#include <map>
+#include <tag.h>
+#include <id3v2tag.h>
+#include <fileref.h>
+
+using namespace std;
+
+class mgItem;
+
+#include "mg_listitem.h"
+#include "mg_keytypes.h"
+
+typedef list<string> strlist;
+
+strlist& operator+=(strlist&a, strlist b);
+
+
+string sql_list (string prefix,strlist v,string sep=",",string postfix="");
+
+class mgSQLStringImp {
+ public:
+ mgSQLStringImp();
+ virtual ~mgSQLStringImp();
+ char *quoted() const;
+ virtual char *unquoted() const = 0 ;
+ const char *original() const { return m_original; }
+ protected:
+ const char *m_original;
+ private:
+ mutable char *m_quoted;
+};
+
+class mgSQLString {
+ public:
+ mgSQLString(const char*s);
+ mgSQLString(string s);
+ mgSQLString(TagLib::String s);
+ ~mgSQLString();
+ char *quoted() const;
+ char *unquoted() const;
+ const char *original() const;
+ void operator=(const mgSQLString& b);
+ bool operator==(const mgSQLString& b) const;
+ bool operator==(const char* b) const;
+ bool operator==(string b) const;
+ bool operator!=(const mgSQLString& b) const;
+ bool operator!=(const char* b) const;
+ bool operator!=(string b) const;
+ void operator=(const char* b);
+ private:
+ void Init(const char* s);
+ mgSQLStringImp* m_str;
+ char* m_original;
+};
+
+enum mgQueryNoise {mgQueryNormal, mgQueryWarnOnly, mgQuerySilent};
+
+class mgQueryImp {
+ public:
+ mgQueryImp(void *db,string sql,mgQueryNoise noise);
+ virtual ~mgQueryImp() {};
+ virtual char ** Next() = 0;
+ int Rows() { return m_rows; }
+ int Columns() { return m_columns; }
+ string ErrorMessage() { if (!m_errormessage) return "";else return m_errormessage; }
+ protected:
+ void HandleErrors();
+ string m_sql;
+ int m_rows;
+ int m_columns;
+ int m_rc;
+ const char* m_errormessage;
+ int m_cursor;
+ const char* m_optsql;
+ mgQueryNoise m_noise;
+ void *m_db_handle;
+};
+
+class mgQuery {
+ public:
+ mgQuery(void *db,const char*s,mgQueryNoise noise = mgQueryNormal);
+ mgQuery(void *db,string s,mgQueryNoise noise = mgQueryNormal);
+ ~mgQuery();
+ int Rows() { return m_q->Rows(); }
+ int Columns() { return m_q->Columns(); }
+ char ** Next() { return m_q->Next(); }
+ string ErrorMessage() { return m_q->ErrorMessage(); }
+ private:
+ void Init(void *db,const char*s,mgQueryNoise noise);
+ mgQueryImp* m_q;
+};
+
+class mgReference {
+ public:
+ mgReference(string t1,string f1,string t2,string f2);
+ string t1() const { return m_t1; }
+ string t2() const { return m_t2; }
+ string f1() const { return m_f1; }
+ string f2() const { return m_f2; }
+ bool Equal(string table1, string table2) const;
+ private:
+ string m_t1;
+ string m_t2;
+ string m_f1;
+ string m_f2;
+};
+
+class mgReferences : public vector<mgReference*> {
+public:
+ void InitReferences();
+ unsigned int CountTable(string table) const;
+};
+
+
+class mgParts {
+public:
+ mgParts();
+ ~mgParts();
+ strlist valuefields; // if idfield and valuefield are identical, define idfield only
+ strlist idfields;
+ strlist tables;
+ strlist clauses;
+ mgParts& operator+=(mgParts a);
+ void Prepare();
+ string sql_count();
+ string sql_select(bool distinct);
+ bool empty() const { return tables.size()==0;}
+ string special_statement;
+ bool orderByCount;
+ void Dump(string where) const;
+private:
+ mgReferences rest;
+ mgReferences positives;
+ void ConnectTables(string c1, string c2);
+ void push_table_to_front(string table);
+};
+
+/*!
+ * \brief an abstract database class
+ *
+ */
+class mgDb {
+ public:
+ mgDb (bool SeparateThread=false);
+ virtual ~mgDb ();
+ /*! \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(const string sql);
+ virtual bool ServerConnect() { return true; }
+ virtual bool Connect() = 0;
+ bool HasFolderFields() const { return m_hasfolderfields;}
+ virtual bool Create() = 0;
+ virtual int AddToCollection( const string Name,const vector<mgItem*>&items,mgParts* what=0);
+ virtual int RemoveFromCollection( const string Name,const vector<mgItem*>&items,mgParts* what=0);
+ virtual bool DeleteCollection( const string Name);
+ virtual void ClearCollection( const string Name);
+ virtual bool CreateCollection( const string Name);
+
+ void Sync(char * const * path_argv);
+ virtual bool FieldExists(string table, string field)=0;
+ void LoadMapInto(string sql,map<string,string>*idmap,map<string,string>*valmap);
+ string LoadItemsInto(mgParts& what,vector<mgItem*>& items);
+ string LoadValuesInto(mgParts& what,mgKeyTypes tp,vector<mgListItem*>& listitems,bool groupby);
+ virtual bool NeedGenre2() = 0;
+ virtual bool Threadsafe() { return false; }
+ int Execute(const string sql);
+ virtual const char* Options() const =0;
+ virtual const char* HelpText() const =0;
+ void* DbHandle();
+ virtual const char* DecadeExpr()=0;
+ virtual string Now() const =0;
+ virtual string Directory() const =0;
+ protected:
+ int m_rows;
+ int m_cols;
+ virtual void SyncEnd() {}
+ bool SyncFile(const char *filename);
+ bool m_database_found;
+ bool m_hasfolderfields;
+ bool m_separate_thread;
+ time_t m_connect_time;
+ time_t m_create_time;
+ string get_col0(const string sql);
+ void FillTables();
+ virtual void StartTransaction() {};
+ virtual void Commit() {};
+ virtual bool SyncStart();
+ virtual void CreateFolderFields() {};
+ virtual void* ImplDbHandle() const = 0;
+ private:
+ TagLib::String m_TLAN;
+ TagLib::String m_TCON;
+ TagLib::String getId3v2Tag(TagLib::ID3v2::Tag *id3v2tags,const char *name) const;
+ void get_ID3v2_Tags(const char *filename);
+ void get_tags(TagLib::ID3v2::Tag *tags);
+ void DefineGenre(const string genre);
+ mgSQLString getGenre1(TagLib::FileRef& f);
+ mgSQLString Build_cddbid(const mgSQLString& artist) const;
+ mgSQLString getAlbum(const char *filename,const mgSQLString& c_album,
+ const mgSQLString& c_artist);
+ map<string,string> m_Genres;
+ map<string,string> m_GenreIds;
+
+};
+
+class mgKey {
+ public:
+ virtual ~mgKey() {};
+ virtual mgParts Parts(mgDb *db,bool groupby=false) const = 0;
+ virtual string id() const = 0;
+ virtual bool valid() const = 0;
+ virtual string value () const = 0;
+ //!\brief translate field into user friendly string
+ virtual void set(mgListItem* item) = 0;
+ virtual mgListItem* get() = 0;
+ virtual mgKeyTypes Type() const = 0;
+ virtual mgSortBy SortBy() const { return mgSortByValue; }
+ virtual bool Enabled(mgDb *db) { return true; }
+ virtual bool LoadMap() const;
+ protected:
+ virtual string map_sql() const { return ""; }
+};
+
+class mgKeyNormal : public mgKey {
+ public:
+ mgKeyNormal(const mgKeyNormal& k);
+ mgKeyNormal(const mgKeyTypes kt, string table, string field);
+ virtual mgParts Parts(mgDb *db,bool groupby=false) const;
+ string value() const;
+ string id() const;
+ bool valid() const;
+ void set(mgListItem* item);
+ mgListItem* get();
+ mgKeyTypes Type() const { return m_kt; }
+ virtual string table() const { return m_table; }
+ protected:
+ virtual string expr(mgDb *db) const { return m_table + "." + m_field; }
+ string IdClause(mgDb *db,string what,string::size_type start=0,string::size_type len=string::npos) const;
+ void AddIdClause(mgDb *db,mgParts &result,string what) const;
+ mgListItem *m_item;
+ string m_field;
+ private:
+ mgKeyTypes m_kt;
+ string m_table;
+};
+
+class mgKeyABC : public mgKeyNormal {
+ public:
+ mgKeyABC(const mgKeyNormal& k) : mgKeyNormal(k) {}
+ mgKeyABC(const mgKeyTypes kt, string table, string field) : mgKeyNormal(kt,table,field) {}
+ virtual string expr(mgDb*db) const { return "substring("+mgKeyNormal::expr(db)+",1,1)"; }
+ protected:
+ //void AddIdClause(mgDb *db,mgParts &result,string what) const;
+};
+
+class mgKeyDate : public mgKeyNormal {
+ public:
+ mgKeyDate(mgKeyTypes kt,string table, string field) : mgKeyNormal(kt,table,field) {}
+};
+mgKey*
+ktGenerate(const mgKeyTypes kt);
+
+mgDb* GenerateDB(bool SeparateThread=false);
+
+/*! \brief if the SQL command works on only 1 table, remove all table
+* qualifiers. Example: SELECT X.title FROM X becomes SELECT title
+* FROM X
+* \param spar the sql command. It will be edited in place
+* \return the new sql command is also returned
+*/
+extern string optimize(string& spar);
+class mgKeyMaps {
+ public:
+ string value(mgKeyTypes kt, string idstr) const;
+ string id(mgKeyTypes kt, string valstr) const;
+ private:
+ bool loadvalues (mgKeyTypes kt) const;
+};
+
+extern mgKeyMaps KeyMaps;
+
+class mgDbServer {
+ public:
+ mgDbServer() {};
+ mgDb* EscapeDb() const { return m_escape_db; }
+ protected:
+ mgDb* m_escape_db;
+};
+
+extern mgDbServer* DbServer;
+
+#endif
diff --git a/mg_db_gd_mysql.c b/mg_db_gd_mysql.c
new file mode 100644
index 0000000..5836d78
--- /dev/null
+++ b/mg_db_gd_mysql.c
@@ -0,0 +1,668 @@
+/*!
+ * \file mg_db_gd_mysql.c
+ * \brief A capsule around database access
+ *
+ * \version $Revision: 1.2 $
+ * \date $Date: 2005-04-13 17:42:54 +0100 (Thu, 13 Apr 2005) $
+ * \author Wolfgang Rohdewald * \author Responsible author: $Author: wolfgang61 $ */
+
+#include <string>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <time.h>
+#include <assert.h>
+
+#include "mg_setup.h"
+#include "mg_item_gd.h"
+#include "mg_db_gd_mysql.h"
+
+using namespace std;
+
+mgSQLStringMySQL::~mgSQLStringMySQL()
+{
+ if (m_unquoted)
+ free(m_unquoted);
+}
+
+mgSQLStringMySQL::mgSQLStringMySQL(const char*s)
+{
+ m_unquoted = 0;
+ m_original = s;
+}
+
+char*
+mgSQLStringMySQL::unquoted() const
+{
+ if (!m_unquoted)
+ {
+ int buflen=2*strlen(m_original)+3;
+ m_unquoted = (char *) malloc( buflen);
+ mgDb* esc = DbServer->EscapeDb();
+ if (esc)
+ mysql_real_escape_string( (MYSQL*)esc->DbHandle(),
+ m_unquoted, m_original, strlen(m_original) );
+ else
+ strcpy(m_unquoted,m_original);
+ }
+ return m_unquoted;
+}
+
+
+mgQueryMySQL::mgQueryMySQL(void* db,string sql,mgQueryNoise noise)
+ : mgQueryImp(db,sql,noise)
+{
+ m_db = (MYSQL*)m_db_handle;
+ m_table = 0;
+ if ((m_rc=mysql_query(m_db,m_optsql)))
+ m_errormessage = mysql_error (m_db);
+ else
+ {
+ m_table = mysql_store_result (m_db);
+ m_rows = mysql_affected_rows(m_db);
+ if (m_table)
+ m_columns = mysql_num_fields(m_table);
+ }
+ HandleErrors();
+}
+
+mgQueryMySQL::~mgQueryMySQL()
+{
+ mysql_free_result (m_table);
+}
+
+char **
+mgQueryMySQL::Next()
+{
+ return mysql_fetch_row(m_table);
+}
+
+const char*
+mgDbGd::Options() const
+{
+#if !defined(HAVE_ONLY_SERVER) && MYSQL_VERSION_ID < 40111
+ return "";
+#else
+ return "hspuw";
+#endif
+}
+
+const char*
+mgDbGd::HelpText() const
+{
+#if !defined(HAVE_ONLY_SERVER) && MYSQL_VERSION_ID < 40111
+ return "";
+#else
+ return
+ " -h HHHH, --host=HHHH specify database host (default is embedded or localhost)\n"
+ " if the specified host is localhost, sockets will\n"
+ " be used if possible.\n"
+ " Otherwise the -s parameter will be ignored\n"
+ " -s SSSS --socket=PATH specify database socket\n"
+ " -p PPPP, --port=PPPP specify port of database server (default is )\n"
+ " -u UUUU, --user=UUUU specify database user (default is )\n"
+ " -w WWWW, --password=WWWW specify database password (default is empty)\n";
+#endif
+}
+
+mgDb* GenerateDB(bool SeparateThread)
+{
+ // \todo should return different backends according to the_setup.Variant
+ if (!DbServer)
+ DbServer = new mgDbServerMySQL;
+ return new mgDbGd(SeparateThread);
+}
+
+static bool needGenre2;
+static bool needGenre2_set=false;
+
+bool UsingEmbeddedMySQL();
+
+mgDbGd::mgDbGd(bool SeparateThread)
+{
+ m_db = 0;
+ if (m_separate_thread)
+ {
+ if (Threadsafe())
+ mysql_thread_init();
+ else
+ mgError("Your Mysql version is not thread safe");
+ }
+}
+
+mgDbGd::~mgDbGd()
+{
+ if (m_db)
+ mysql_close (m_db);
+ m_db = 0;
+#if MYSQL_VERSION_ID >=400000
+ if (m_separate_thread)
+ mysql_thread_end();
+#endif
+}
+
+bool
+mgDbGd::Threadsafe()
+{
+#if defined THREAD_SAFE_CLIENT && MYSQL_VERSION_ID >=400000
+ // 3.23 does define THREAD_SAFE_CLIENT but has no mysql_thread_init.
+ // So we assume we should better not assume threading to be safe
+ return true;
+#else
+ return false;
+#endif
+}
+
+#ifndef HAVE_ONLY_SERVER
+static char *mysql_embedded_args[] =
+{
+ "muggle",
+ 0, // placeholder for --datadir=
+ "--key_buffer_size=32M"
+};
+
+static void
+wrong_embedded_mysql_for_external_server(int version)
+{
+ mgError("You are using the embedded mysql library. For accessing external servers "
+ "you need mysql 040111 but you have only %06d", version);
+ abort();
+}
+#endif
+
+mgDbServerMySQL::mgDbServerMySQL()
+{
+#ifndef HAVE_ONLY_SERVER
+ static char *mysql_embedded_groups[] =
+ {
+ "embedded",
+ "server",
+ "muggle_SERVER",
+ 0
+ };
+ int argv_size;
+ if (UsingEmbeddedMySQL())
+ {
+ argv_size = sizeof(mysql_embedded_args) / sizeof(char *);
+ struct stat stbuf;
+ if (stat(the_setup.DbDatadir,&stbuf))
+ mkdir(the_setup.DbDatadir,0755);
+ if (stat(the_setup.DbDatadir,&stbuf))
+ {
+ mgError("Cannot access mysqldata directory %s: errno=%d",
+ the_setup.DbDatadir,errno);
+ abort();
+ }
+ asprintf(&mysql_embedded_args[1],"--datadir=%s",the_setup.DbDatadir);
+ mgDebug(1,"calling mysql_server_init for embedded in %s",the_setup.DbDatadir);
+ }
+ else
+ {
+#if MYSQL_VERSION_ID < 40111
+ // compile time check
+ wrong_embedded_mysql_for_external_server(MYSQL_VERSION_ID);
+#endif
+#if MYSQL_VERSION_ID >= 40101
+ // runtime check
+ if (mysql_get_client_version()<40111) // this function was added for embedded library in MySQL 4.1.1
+ wrong_embedded_mysql_for_external_server(mysql_get_client_version());
+#endif
+ mgDebug(1,"calling mysql_server_init for external server");
+ argv_size = -1;
+ }
+ if (mysql_server_init(argv_size, mysql_embedded_args, mysql_embedded_groups))
+ {
+ mgError("mysql_server_init failed");
+ abort();
+ }
+#endif
+ m_escape_db = new mgDbGd;
+}
+
+mgDbServerMySQL::~mgDbServerMySQL()
+{
+ delete m_escape_db;
+ m_escape_db=0;
+#ifndef HAVE_ONLY_SERVER
+ mgDebug(3,"calling mysql_server_end");
+ mysql_server_end();
+#endif
+}
+
+
+
+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"
+};
+
+void
+mgDbGd::StartTransaction()
+{
+ Execute("START TRANSACTION");
+}
+
+void
+mgDbGd::Commit()
+{
+ Execute("COMMIT");
+}
+
+bool
+mgDbGd::Create()
+{
+ if (!ServerConnect())
+ return false;
+ // create database and tables
+ mgDebug(1,"Dropping and recreating database %s",the_setup.DbName);
+ char buffer[500];
+ sprintf(buffer,"DROP DATABASE IF EXISTS %s",the_setup.DbName);
+ if (strlen(buffer)>400)
+ mgError("name of database too long: %s",the_setup.DbName);
+ mgQuery q(m_db,buffer);
+ if (!q.ErrorMessage().empty())
+ return false;
+ sprintf(buffer,"CREATE DATABASE %s",the_setup.DbName);
+ mgQuery q1(m_db,buffer);
+ if (!q1.ErrorMessage().empty())
+ return false;
+ if (!UsingEmbeddedMySQL())
+ sprintf(buffer,"grant all privileges on %s.* to vdr@localhost",
+ the_setup.DbName);
+ Execute(buffer);
+ // ignore error. If we can create the data base, we can do everything
+ // with it anyway.
+
+ if (mysql_select_db(m_db,the_setup.DbName))
+ mgError("mysql_select_db(%s) failed with %s",mysql_error(m_db));
+
+ int len = sizeof( db_cmds ) / sizeof( char* );
+ for( int i=0; i < len; i ++ )
+ {
+ mgQuery q(m_db, db_cmds[i],mgQueryWarnOnly);
+ if (!q.ErrorMessage().empty())
+ {
+ sprintf(buffer,"DROP DATABASE IF EXISTS %s",the_setup.DbName);
+ Execute(buffer);
+ return false;
+ }
+ }
+ m_database_found=true;
+ FillTables();
+ return true;
+}
+
+
+bool
+mgDbGd::ServerConnect ()
+{
+ if (m_db)
+ return true;
+ if (time(0)<m_connect_time+10)
+ return false;
+ m_connect_time=time(0);
+ m_db = mysql_init (0);
+ if (!m_db)
+ return false;
+ if (UsingEmbeddedMySQL())
+ {
+#ifndef HAVE_ONLY_SERVER
+ if (!mysql_real_connect(m_db, 0, 0, 0, 0, 0, 0, 0))
+ mgWarning("Failed to connect to embedded mysql in %s:%s ",
+ the_setup.DbDatadir,mysql_error(m_db));
+ else
+ mgDebug(1,"Connected to embedded mysql in %s",
+ the_setup.DbDatadir);
+#endif
+ }
+ else
+ {
+ if (the_setup.NoHost() || !strcmp(the_setup.DbHost,"localhost"))
+ mgDebug(1,"Using socket %s for connecting to local system as user %s.",
+ the_setup.DbSocket, the_setup.DbUser);
+ else
+ mgDebug(1,"Using TCP for connecting to server %s as user %s.",
+ the_setup.DbHost, the_setup.DbUser);
+ if (!mysql_real_connect( m_db,
+ the_setup.DbHost, the_setup.DbUser, the_setup.DbPass, 0,
+ the_setup.DbPort, the_setup.DbSocket, 0 ) != 0 )
+ {
+ 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;
+ }
+ }
+ return m_db!=0;
+}
+
+
+bool
+UsingEmbeddedMySQL()
+{
+#ifdef HAVE_ONLY_SERVER
+ return false;
+#else
+ return the_setup.NoHost();
+#endif
+}
+
+bool
+mgDbGd::Connect ()
+{
+ if (m_database_found)
+ return true;
+ if (!ServerConnect())
+ return false;
+ if (time(0)<m_create_time+10)
+ return false;
+ m_create_time=time(0);
+ m_database_found = mysql_select_db(m_db,the_setup.DbName)==0;
+ if (m_database_found)
+ return true;
+ extern bool create_question();
+ extern bool import();
+ if (create_question())
+ {
+ if (Create())
+ {
+ import();
+ return true;
+ }
+ }
+ mgWarning(mysql_error(m_db));
+ return false;
+}
+
+bool
+mgDbGd::NeedGenre2()
+{
+ if (!needGenre2_set && Connect())
+ {
+ needGenre2_set=true;
+ needGenre2=exec_count("SELECT COUNT(DISTINCT genre2) FROM tracks")>1;
+ }
+ return needGenre2;
+}
+
+void
+mgDbGd::CreateFolderFields()
+{
+ if (HasFolderFields())
+ return;
+ mgQuery q(m_db, "DESCRIBE tracks folder1");
+ m_hasfolderfields = q.Rows()>0;
+ if (!m_hasfolderfields)
+ {
+ mgQuery q(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)");
+ m_hasfolderfields = q.ErrorMessage().empty();
+
+ }
+}
+
+int
+mgDbGd::AddToCollection( const string Name,const vector<mgItem*>&items, mgParts* what)
+{
+ if (!Connect()) return 0;
+ CreateCollection(Name);
+ string listid = mgSQLString (get_col0
+ (string("SELECT id FROM playlist WHERE title=")
+ + mgSQLString(Name).quoted())).quoted();
+ unsigned int tracksize = items.size();
+ if (tracksize==0)
+ return 0;
+
+ // this code is rather complicated but works in a multi user
+ // environment:
+
+ // insert a unique trackid:
+ string trackid = ltos(thread_id()+1000000);
+ Execute("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(get_col0(sql).c_str()) - tracksize + 1;
+
+ // replace the place holder trackid by the correct value:
+ Execute("UPDATE playlistitem SET trackid="+ltos(items[tracksize-1]->getItemid())+
+ " WHERE playlist="+listid+" AND trackid="+trackid);
+
+ // insert all other tracks:
+ const char *sql_prefix = "INSERT INTO playlistitem VALUES ";
+ sql = "";
+ for (unsigned int i = 0; i < tracksize-1; i++)
+ {
+ string item = "(" + listid + "," + ltos (first + i) + "," +
+ ltos (items[i]->getItemid ()) + ")";
+ comma(sql, item);
+ if ((i%100)==99)
+ {
+ Execute (sql_prefix+sql);
+ sql = "";
+ }
+ }
+ if (!sql.empty()) Execute (sql_prefix+sql);
+ return tracksize;
+}
+
+int
+mgDbGd::RemoveFromCollection (const string Name, const vector<mgItem*>&items, mgParts* what)
+{
+ if (Name.empty())
+ return 0;
+ if (!Connect()) return 0;
+ string pid = KeyMaps.id(keyGdCollection,Name);
+ if (pid.empty())
+ return 0;
+ what->Prepare();
+ what->tables.push_front("playlistitem as del");
+ what->clauses.push_back("del.playlist="+pid);
+ bool usesTracks = false;
+ for (list < string >::iterator it = what->tables.begin (); it != what->tables.end (); ++it)
+ if (*it == "tracks")
+ {
+ usesTracks = true;
+ break;
+ }
+ if (usesTracks)
+ what->clauses.push_back("del.trackid=tracks.id");
+ else
+ what->clauses.push_back("del.trackid=playlistitem.trackid");
+ string sql = "DELETE playlistitem";
+ sql += sql_list(" FROM",what->tables);
+ sql += sql_list(" WHERE",what->clauses," AND ");
+ return Execute (sql);
+}
+
+bool
+mgDbGd::FieldExists(string table, string field)
+{
+ if (!Connect())
+ return false;
+ char *b;
+ asprintf(&b,"DESCRIBE %s %s",table.c_str(),field.c_str());
+ mgQuery q(m_db,b);
+ free(b);
+ if (q.Next())
+ return q.Rows() == 1;
+ else
+ return false;
+}
+
+const char*
+mgDbGd::DecadeExpr()
+{
+ return "substring(10 * floor(tracks.year/10),3)";
+}
+
diff --git a/mg_db_gd_mysql.h b/mg_db_gd_mysql.h
new file mode 100644
index 0000000..ba8f08c
--- /dev/null
+++ b/mg_db_gd_mysql.h
@@ -0,0 +1,82 @@
+/*!
+ * \file mg_db_gd_mysql.h
+ * \brief A capsule around giantdisc database access
+ *
+ * \version $Revision: 1.2 $
+ * \date $Date: 2005-04-13 17:42:54 +0100 (Thu, 13 Apr 2005) $
+ * \author Wolfgang Rohdewald
+ * \author Responsible author: $Author: wolfgang61 $
+ */
+
+#ifndef __MG_DB_GD_H
+#define __MG_DB_GD_H
+
+#include <string>
+#include <tag.h>
+#include <map>
+#include <mysql/mysql.h>
+
+using namespace std;
+
+#include "mg_db.h"
+
+class mgDbGd : public mgDb {
+ public:
+ mgDbGd (bool SeparateThread=false);
+ ~mgDbGd();
+ bool ServerConnect();
+ bool Connect();
+ bool Create();
+ int AddToCollection( const string Name,const vector<mgItem*>&items,mgParts* what);
+ int RemoveFromCollection( const string Name,const vector<mgItem*>&items,mgParts* what);
+
+ bool NeedGenre2();
+ long thread_id() { return mysql_thread_id(m_db); }
+ bool FieldExists(string table, string field);
+ void ServerEnd();
+ bool Threadsafe();
+ const char* HelpText() const;
+ const char *Options() const;
+ const char *DecadeExpr();
+ string Now() const { return "CURRENT_TIMESTAMP"; }
+ string Directory() const { return "substring(tracks.mp3file,1,length(tracks.mp3file)"
+ "-instr(reverse(tracks.mp3file),'/'))"; }
+ protected:
+ void SyncFile(const char *filename);
+ void StartTransaction();
+ void Commit();
+ void *ImplDbHandle() const { return (void*)m_db; }
+ private:
+ MYSQL *m_db;
+ void CreateFolderFields();
+ MYSQL_RES* Query( const string sql);
+ bool sql_query(string sql);
+
+};
+
+class mgDbServerMySQL : public mgDbServer {
+ public:
+ mgDbServerMySQL();
+ ~mgDbServerMySQL();
+};
+
+class mgSQLStringMySQL : public mgSQLStringImp {
+ public:
+ mgSQLStringMySQL(const char* s);
+ ~mgSQLStringMySQL();
+ char *unquoted() const;
+ private:
+ mutable char* m_unquoted;
+};
+
+class mgQueryMySQL : public mgQueryImp {
+ public:
+ mgQueryMySQL(void* db,string sql,mgQueryNoise noise);
+ ~mgQueryMySQL();
+ char ** Next();
+ private:
+ MYSQL_RES *m_table;
+ int m_rc;
+ MYSQL *m_db;
+};
+#endif
diff --git a/mg_db_gd_pg.c b/mg_db_gd_pg.c
new file mode 100644
index 0000000..d315c6c
--- /dev/null
+++ b/mg_db_gd_pg.c
@@ -0,0 +1,351 @@
+/*!
+ * \file mg_db_gd_pg.c
+ * \brief A capsule around postgresql database access
+ *
+ * \version $Revision: 1.2 $
+ * \date $Date: 2005-04-13 17:42:54 +0100 (Thu, 13 Apr 2005) $
+ * \author Wolfgang Rohdewald
+ * \author Responsible author: $Author: wolfgang61 $
+ */
+
+#include <string>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <time.h>
+#include <assert.h>
+
+
+#include "mg_setup.h"
+#include "mg_item_gd.h"
+#include "mg_db_gd_pg.h"
+
+#include <pg_config.h>
+
+using namespace std;
+
+mgQueryPG::mgQueryPG(void* db,string sql,mgQueryNoise noise)
+ : mgQueryImp(db,sql,noise)
+{
+ m_db = (PGconn*)m_db_handle;
+ m_cursor = 0;
+ m_table = PQexec(m_db,m_optsql);
+ switch ((m_rc=PQresultStatus(m_table))) {
+ case PGRES_COMMAND_OK:
+ m_rows = atol(PQcmdTuples(m_table));
+ break;;
+ case PGRES_TUPLES_OK:
+ m_rows = PQntuples(m_table);
+ m_columns = PQnfields(m_table);
+ break;
+ default:
+ m_errormessage = PQresultErrorMessage (m_table);
+ break;
+ }
+ HandleErrors();
+}
+
+mgQueryPG::~mgQueryPG()
+{
+ PQclear(m_table);
+}
+
+char **
+mgQueryPG::Next()
+{
+ if (m_cursor>=Rows())
+ return 0;
+ assert(Columns()<100);
+ memset(m_rowpointers,0,sizeof(m_rowpointers));
+ for (int idx=0;idx<Columns();idx++)
+ if (!PQgetisnull(m_table,m_cursor,idx))
+ m_rowpointers[idx] = PQgetvalue(m_table,m_cursor,idx);
+ m_cursor++;
+ return m_rowpointers;
+}
+
+mgSQLStringPG::~mgSQLStringPG()
+{
+ if (m_unquoted)
+ free(m_unquoted);
+}
+
+mgSQLStringPG::mgSQLStringPG(const char*s)
+{
+ m_unquoted = 0;
+ m_original = s;
+}
+
+char*
+mgSQLStringPG::unquoted() const
+{
+ if (!m_unquoted)
+ {
+ int buflen=2*strlen(m_original)+5;
+ m_unquoted = (char *) malloc( buflen);
+ PQescapeString(m_unquoted,m_original,strlen(m_original));
+ }
+ return m_unquoted;
+}
+
+const char*
+mgDbGd::Options() const
+{
+ return "hspuw";
+}
+
+const char*
+mgDbGd::HelpText() const
+{
+ return
+ " -h HHHH, --host=HHHH specify database host (default is localhost)\n"
+ " -s SSSS --socket=PATH specify database socket\n"
+ " -p PPPP, --port=PPPP specify port of database server (default is )\n"
+ " -u UUUU, --user=UUUU specify database user (default is )\n"
+ " -w WWWW, --password=WWWW specify database password (default is empty)\n";
+}
+
+mgDb* GenerateDB(bool SeparateThread)
+{
+ // \todo should return different backends according to the_setup.Variant
+ return new mgDbGd(SeparateThread);
+}
+
+static bool needGenre2;
+static bool needGenre2_set=false;
+
+mgDbGd::mgDbGd(bool SeparateThread)
+{
+ m_db = 0;
+ if (m_separate_thread)
+ if (!Threadsafe())
+ mgError("Your database library is not thread safe");
+}
+
+mgDbGd::~mgDbGd()
+{
+ PQfinish (m_db);
+ m_db = 0;
+}
+
+bool
+mgDbGd::Threadsafe()
+{
+ return ENABLE_THREAD_SAFETY;
+}
+
+static char *db_cmds[] =
+{
+ "CREATE TABLE album ( "
+ "artist varchar(255) default NULL, "
+ "title varchar(255) default NULL, "
+ "cddbid varchar(20) default '', "
+ "coverimg varchar(255) default NULL, "
+ "covertxt text, "
+ "modified date default NULL, "
+ "genre varchar(10) default NULL, "
+ "PRIMARY KEY (cddbid))",
+ "CREATE INDEX alb_artist ON album (artist)",
+ "CREATE INDEX alb_title ON album (title)",
+ "CREATE TABLE genre ("
+ "id varchar(10) NOT NULL default '', "
+ "id3genre smallint default NULL, "
+ "genre varchar(255) default NULL, "
+ "freq int default NULL, "
+ "PRIMARY KEY (id))",
+ "CREATE TABLE language ("
+ "id varchar(4) NOT NULL default '', "
+ "language varchar(40) default NULL, "
+ "freq int default NULL, "
+ "PRIMARY KEY (id))",
+ "CREATE TABLE musictype ("
+ "musictype varchar(40) default NULL, "
+ "id serial, "
+ "PRIMARY KEY (id)) ",
+ "CREATE TABLE playlist ( "
+ "title varchar(255) default NULL, "
+ "author varchar(255) default NULL, "
+ "note varchar(255) default NULL, "
+ "created timestamp default NULL, "
+ "id serial, "
+ "PRIMARY KEY (id))",
+ "CREATE TABLE playlistitem ( "
+ "playlist int NOT NULL,"
+ "trackid int NOT NULL) WITH OIDS",
+ "CREATE TABLE source ( "
+ "source varchar(40) default NULL, "
+ "id serial, "
+ "PRIMARY KEY (id)) ",
+ "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 default NULL, "
+ "lang varchar(4) default NULL, "
+ "type smallint default NULL, "
+ "rating smallint default NULL, "
+ "length smallint default NULL, "
+ "source smallint default NULL, "
+ "sourceid varchar(20) default NULL, "
+ "tracknb smallint default NULL, "
+ "mp3file varchar(255) default NULL, "
+ "condition smallint default NULL, "
+ "voladjust smallint default '0', "
+ "lengthfrm int default '0', "
+ "startfrm int default '0', "
+ "bpm smallint default '0', "
+ "lyrics text, "
+ "bitrate varchar(10) default NULL, "
+ "created date default NULL, "
+ "modified date default NULL, "
+ "backup smallint default NULL, "
+ "samplerate int default NULL, "
+ "channels smallint default NULL, "
+ "id serial, "
+ "folder1 varchar(255), "
+ "folder2 varchar(255), "
+ "folder3 varchar(255), "
+ "folder4 varchar(255), "
+ "PRIMARY KEY (id))",
+ "CREATE INDEX tracks_title ON tracks (title)",
+ "CREATE INDEX tracks_sourceid ON tracks (sourceid)",
+ "CREATE INDEX tracks_mp3file ON tracks (mp3file)",
+ "CREATE INDEX tracks_genre1 ON tracks (genre1)",
+ "CREATE INDEX tracks_genre2 ON tracks (genre2)",
+ "CREATE INDEX tracks_year ON tracks (year)",
+ "CREATE INDEX tracks_lang ON tracks (lang)",
+ "CREATE INDEX tracks_artist ON tracks (artist)",
+ "CREATE INDEX tracks_rating ON tracks (rating)",
+ "CREATE INDEX tracks_folder1 ON tracks (folder1)",
+ "CREATE INDEX tracks_folder2 ON tracks (folder2)",
+ "CREATE INDEX tracks_folder3 ON tracks (folder3)",
+ "CREATE INDEX tracks_folder4 ON tracks (folder4)",
+};
+
+void
+mgDbGd::StartTransaction()
+{
+ Execute("BEGIN TRANSACTION");
+}
+
+void
+mgDbGd::Commit()
+{
+ Execute("COMMIT");
+}
+
+
+bool
+mgDbGd::Create()
+{
+ if (!Connect())
+ return false;
+ return myCreate();
+}
+
+bool
+mgDbGd::myCreate()
+{
+ // create database and tables
+ int len = sizeof( db_cmds ) / sizeof( char* );
+ for( int i=0; i < len; i ++ )
+ {
+ mgQuery q(m_db,db_cmds[i],mgQueryWarnOnly);
+ if (!q.ErrorMessage().empty())
+ return false;
+ }
+ m_database_found=true;
+ FillTables();
+ return true;
+}
+
+bool
+mgDbGd::ServerConnect ()
+{
+ return true;
+}
+
+
+bool
+mgDbGd::Connect ()
+{
+ if (m_database_found)
+ return true;
+ char conninfo[500];
+ char port[20];
+ char host[200];
+ if (the_setup.DbPort>0)
+ sprintf(port," port = %d ",the_setup.DbPort);
+ else
+ port[0]=0;
+ if (notempty(the_setup.DbHost))
+ snprintf(host,199," host = %s ",the_setup.DbHost);
+ else if (notempty(the_setup.DbSocket))
+ snprintf(host,199," host = %s ",the_setup.DbSocket);
+ else
+ host[0]=0;
+ snprintf(conninfo,499,"%s %s dbname = %s user = %s ",
+ host,port,the_setup.DbName,the_setup.DbUser);
+ m_db = PQconnectdb(conninfo);
+ if (PQstatus(m_db) != CONNECTION_OK)
+ {
+ mgWarning("Failed to connect to postgres server using %s:%s",conninfo,PQerrorMessage(m_db));
+ return false;
+ }
+ m_database_found = true; // otherwise we get into a recursion
+ m_database_found = exec_count("SELECT COUNT(*) FROM information_schema.tables WHERE table_name='album'");
+ if (m_database_found)
+ return true;
+ if (time(0)<m_create_time+10)
+ return false;
+ m_create_time=time(0);
+ extern bool create_question();
+ extern bool import();
+ if (create_question())
+ {
+ m_database_found = true;
+ if (myCreate())
+ {
+ import();
+ return true;
+ }
+ }
+ m_database_found = false;
+ mgWarning(PQerrorMessage(m_db));
+ return false;
+}
+
+bool
+mgDbGd::NeedGenre2()
+{
+ if (!needGenre2_set && Connect())
+ {
+ needGenre2_set=true;
+ needGenre2=exec_count("SELECT COUNT(DISTINCT genre2) FROM tracks")>1;
+ }
+ return needGenre2;
+}
+
+
+bool
+mgDbGd::FieldExists(string table, string field)
+{
+ if (!Connect())
+ return false;
+ char *b;
+ asprintf(&b,"SELECT COUNT(*) FROM information_schema.columns WHERE table_name='album' AND column_name='%s'",field.c_str());
+ bool result = exec_count(b)==1;
+ free(b);
+ return result;
+}
+
+const char*
+mgDbGd::DecadeExpr()
+{
+ return "substring(10 * floor(tracks.year/10),3)";
+}
+
diff --git a/mg_db_gd_pg.h b/mg_db_gd_pg.h
new file mode 100644
index 0000000..0eb3f0e
--- /dev/null
+++ b/mg_db_gd_pg.h
@@ -0,0 +1,70 @@
+/*!
+ * \file mg_db_gd_mysql.h
+ * \brief A capsule around giantdisc database access
+ *
+ * \version $Revision: 1.2 $
+ * \date $Date: 2005-04-13 17:42:54 +0100 (Thu, 13 Apr 2005) $
+ * \author Wolfgang Rohdewald
+ * \author Responsible author: $Author: wolfgang61 $
+ */
+
+#ifndef __MG_DB_GD_H
+#define __MG_DB_GD_H
+
+#include <string>
+#include <tag.h>
+#include <map>
+#include <libpq-fe.h>
+
+using namespace std;
+
+#include "mg_db.h"
+
+class mgSQLStringPG : public mgSQLStringImp {
+ public:
+ mgSQLStringPG(const char* s);
+ ~mgSQLStringPG();
+ char *unquoted() const;
+ private:
+ mutable char* m_unquoted;
+};
+
+class mgQueryPG : public mgQueryImp {
+ public:
+ mgQueryPG(void* db,string sql,mgQueryNoise noise);
+ ~mgQueryPG();
+ char ** Next();
+ private:
+ PGresult *m_table;
+ PGconn *m_db;
+ char *m_rowpointers[100];
+};
+
+class mgDbGd : public mgDb {
+ public:
+ mgDbGd (bool SeparateThread=false);
+ ~mgDbGd();
+ bool ServerConnect();
+ bool Connect();
+ bool Create();
+
+ bool NeedGenre2();
+ long thread_id() { return -1; }
+ bool FieldExists(string table, string field);
+ bool Threadsafe();
+ const char* Options() const;
+ const char* HelpText() const;
+ const char *DecadeExpr();
+ string Now() const { return "CURRENT_TIMESTAMP";}
+ string Directory() const { return "substring(tracks.mp3file from '.*/(.*)')"; }
+ protected:
+ void SyncFile(const char *filename);
+ void StartTransaction();
+ void Commit();
+ void *ImplDbHandle() const { return (void*)m_db;}
+ private:
+ bool myCreate();
+ PGconn *m_db;
+
+};
+#endif
diff --git a/mg_db_gd_sqlite.c b/mg_db_gd_sqlite.c
new file mode 100644
index 0000000..8407b27
--- /dev/null
+++ b/mg_db_gd_sqlite.c
@@ -0,0 +1,372 @@
+/*!
+ * \file mg_db_gd_sqlite.c
+ * \brief A capsule around database access
+ *
+ * \version $Revision: 1.2 $
+ * \date $Date: 2005-04-13 17:42:54 +0100 (Thu, 13 Apr 2005) $
+ * \author Wolfgang Rohdewald
+ * \author Responsible author: $Author: wolfgang61 $
+ */
+
+#include <string>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <time.h>
+#include <assert.h>
+
+
+#include "mg_setup.h"
+#include "mg_item_gd.h"
+#include "mg_db_gd_sqlite.h"
+
+using namespace std;
+
+mgQuerySQLite::mgQuerySQLite(void* db,string sql,mgQueryNoise noise)
+ : mgQueryImp(db,sql,noise)
+{
+ sqlite3* m_db = (sqlite3*)m_db_handle;
+ m_table = 0;
+ char *p;
+ if (!strncmp(m_optsql,"SELECT ",7))
+ m_rc = sqlite3_get_table(m_db,m_optsql,&m_table,&m_rows,&m_columns,&p);
+ else
+ {
+ m_rc = sqlite3_exec(m_db,m_optsql,0,0,&p);
+ if (m_rc==SQLITE_OK)
+ m_rows = sqlite3_changes(m_db);
+ }
+ m_errormessage= (const char*)p;
+ HandleErrors();
+}
+
+mgQuerySQLite::~mgQuerySQLite()
+{
+ sqlite3_free_table(m_table);
+}
+
+char **
+mgQuerySQLite::Next()
+{
+ if (m_cursor>=m_rows)
+ return 0;
+ m_cursor++;
+ return &m_table[m_columns*m_cursor]; // skip header row
+}
+
+mgSQLStringSQLite::~mgSQLStringSQLite()
+{
+ if (m_unquoted)
+ sqlite3_free(m_unquoted);
+}
+
+mgSQLStringSQLite::mgSQLStringSQLite(const char*s)
+{
+ m_unquoted = 0;
+ m_original = s;
+}
+
+char*
+mgSQLStringSQLite::unquoted() const
+{
+ if (!m_unquoted)
+ m_unquoted = sqlite3_mprintf("%q",m_original);
+ return m_unquoted;
+}
+
+const char*
+mgDbGd::Options() const
+{
+ return "";
+}
+
+const char*
+mgDbGd::HelpText() const
+{
+ return "";
+}
+
+mgDb* GenerateDB(bool SeparateThread)
+{
+ // \todo should return different backends according to the_setup.Variant
+ return new mgDbGd(SeparateThread);
+}
+
+
+mgDbGd::mgDbGd(bool SeparateThread)
+{
+ m_db = 0;
+ if (m_separate_thread)
+ if (!Threadsafe())
+ mgError("Your database library is not thread safe");
+}
+
+mgDbGd::~mgDbGd()
+{
+ sqlite3_close (m_db);
+ m_db = 0;
+}
+
+bool
+mgDbGd::Threadsafe()
+{
+ return false;
+}
+
+static char *db_cmds[] =
+{
+ "drop table 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))",
+ "CREATE INDEX idx_album_artist ON album (artist)",
+ "CREATE INDEX idx_album_title ON album (title)",
+ "drop table 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))",
+ "drop table language",
+ "CREATE TABLE language ("
+ "id varchar(4) NOT NULL default '', "
+ "language varchar(40) default NULL, "
+ "freq int(11) default NULL, "
+ "PRIMARY KEY (id))",
+ "drop table musictype;",
+ "CREATE TABLE musictype ("
+ "musictype varchar(40) default NULL, "
+ "id integer PRIMARY KEY autoincrement)",
+ "drop table 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 integer PRIMARY KEY autoincrement)",
+"drop table playlistitem",
+ "CREATE TABLE playlistitem ( "
+ "playlist integer NOT NULL, "
+ "trackid int(11) NOT NULL)",
+ "CREATE INDEX playlistitem_1 on playlistitem (playlist)",
+ "drop table 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))",
+ "drop table source",
+ "CREATE TABLE source ( "
+ "source varchar(40) default NULL, "
+ "id integer PRIMARY KEY autoincrement)",
+ "drop table tracks",
+ "CREATE TABLE tracks ( "
+ "id integer PRIMARY KEY autoincrement, "
+ "artist varchar(255) default NULL, "
+ "title varchar(255) default NULL, "
+ "genre1 varchar(10) default NULL, "
+ "genre2 varchar(10) default NULL, "
+ "year smallint(5) default NULL, "
+ "lang varchar(4) default NULL, "
+ "type tinyint(3) default NULL, "
+ "rating tinyint(3) default NULL, "
+ "length smallint(5) default NULL, "
+ "source tinyint(3) default NULL, "
+ "sourceid varchar(20) default NULL, "
+ "tracknb tinyint(3) default NULL, "
+ "mp3file varchar(255) default NULL, "
+ "condition tinyint(3) 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) default NULL, "
+ "samplerate int(7) default NULL, "
+ "channels tinyint(3) default NULL, "
+ "folder1 varchar(255), "
+ "folder2 varchar(255), "
+ "folder3 varchar(255), "
+ "folder4 varchar(255)); ",
+ "CREATE INDEX tracks_title ON tracks (title)",
+ "CREATE INDEX tracks_sourceid ON tracks (sourceid)",
+ "CREATE INDEX tracks_mp3file ON tracks (mp3file)",
+ "CREATE INDEX tracks_genre1 ON tracks (genre1)",
+ "CREATE INDEX tracks_year ON tracks (year)",
+ "CREATE INDEX tracks_lang ON tracks (lang)",
+ "CREATE INDEX tracks_artist ON tracks (artist)",
+ "CREATE INDEX tracks_rating ON tracks (rating)",
+ "CREATE INDEX tracks_folder1 ON tracks (folder1)",
+ "CREATE INDEX tracks_folder2 ON tracks (folder2)",
+ "CREATE INDEX tracks_folder3 ON tracks (folder3)",
+ "CREATE INDEX tracks_folder4 ON tracks (folder4)",
+};
+
+void
+mgDbGd::StartTransaction()
+{
+ Execute("BEGIN TRANSACTION");
+}
+
+void
+mgDbGd::Commit()
+{
+ Execute("COMMIT");
+}
+
+bool
+mgDbGd::Create()
+{
+ // create database and tables
+ int len = sizeof( db_cmds ) / sizeof( char* );
+ for( int i=0; i < len; i ++ )
+ {
+ mgQuery q(m_db,db_cmds[i],mgQuerySilent);
+ if (!q.ErrorMessage().empty())
+ if (strncmp(db_cmds[i],"drop ",5))
+ {
+ mgWarning("%20s: %s",db_cmds[i],q.ErrorMessage().c_str());
+ return false;
+ }
+ }
+ m_database_found=true;
+ FillTables();
+ return true;
+}
+
+void
+mgDirectory(sqlite3_context *context, int argc, sqlite3_value **argv)
+{
+ assert(argc==1);
+ char *buf=strdup((char*)sqlite3_value_text(argv[0]));
+ char *slash=strrchr(buf,'/');
+ if (!slash)
+ slash=buf;
+ *slash=0;
+ sqlite3_result_text(context,buf,strlen(buf),free);
+}
+
+void
+mgDecade(sqlite3_context *context, int argc, sqlite3_value **argv)
+{
+ assert(argc==1);
+ unsigned int year=sqlite3_value_int(argv[0]);
+ char *buf;
+ asprintf(&buf,"%02d",(year-year%10)%100);
+ sqlite3_result_text(context,buf,2,free);
+}
+
+void
+mgSubstring(sqlite3_context *context, int argc, sqlite3_value **argv)
+{
+ assert(argc==3);
+ char*full=(char*)sqlite3_value_text(argv[0]);
+ unsigned int opos=sqlite3_value_int(argv[1]);
+ unsigned int pos=opos;
+ unsigned int olen=sqlite3_value_int(argv[2]);
+ unsigned int len=olen;
+ if (pos>strlen(full))
+ pos=strlen(full);
+ if (len==0)
+ len=99999;
+ if (len>strlen(full+pos-1))
+ len=strlen(full+pos-1);
+ char *buf=strndup(full+pos-1,len);
+ sqlite3_result_text(context,buf,len,free);
+}
+
+bool
+mgDbGd::Connect ()
+{
+ if (m_database_found)
+ return true;
+ if (time(0)<m_create_time+10)
+ return false;
+ m_create_time=time(0);
+ char *s=sqlite3_mprintf("%s/%s.sqlite",the_setup.DbDatadir,the_setup.DbName);
+ mgDebug(1,"%X opening data base %s",m_db,s);
+ int rc = sqlite3_open(s,&m_db);
+ m_database_found = rc==SQLITE_OK;
+ if (!m_database_found)
+ {
+ mgWarning("Cannot open/create SQLite database %s:%d/%s",
+ s,rc,sqlite3_errmsg(m_db));
+ sqlite3_free(s);
+ return false;
+ }
+ sqlite3_free(s);
+ rc = sqlite3_create_function(m_db,"mgDirectory",1,SQLITE_UTF8,
+ 0,&mgDirectory,0,0);
+ if (rc!=SQLITE_OK)
+ {
+ mgWarning("Cannot define mgDirectory:%d/%s",rc,sqlite3_errmsg);
+ return false;
+ }
+ rc = sqlite3_create_function(m_db,"substring",3,SQLITE_UTF8,
+ 0,&mgSubstring,0,0);
+ if (rc!=SQLITE_OK)
+ {
+ mgWarning("Cannot define mgSubstring:%d/%s",rc,sqlite3_errmsg);
+ return false;
+ }
+ rc = sqlite3_create_function(m_db,"decade",1,SQLITE_UTF8,
+ 0,&mgDecade,0,0);
+ if (rc!=SQLITE_OK)
+ {
+ mgWarning("Cannot define decade:%d/%s",rc,sqlite3_errmsg);
+ return false;
+ }
+ if (!FieldExists("tracks","id"))
+ {
+ extern bool create_question();
+ if (!create_question())
+ return false;
+ if (!Create())
+ return false;
+ extern bool import();
+ import();
+ }
+ return true;
+}
+
+bool
+mgDbGd::NeedGenre2()
+{
+// we do not support genre2 because queries like
+// SELECT title FROM tracks WHERE (genre1='m' or genre2='m')
+// are very slow with sqlite
+ return false;
+}
+
+bool
+mgDbGd::FieldExists(string table, string field)
+{
+ if (!Connect())
+ return false;
+ char *b;
+ asprintf(&b,"SELECT %s FROM %s LIMIT 1",field.c_str(),table.c_str());
+ mgQuery q(m_db,b,mgQuerySilent);
+ free(b);
+ return q.ErrorMessage().empty();
+}
+
+const char*
+mgDbGd::DecadeExpr()
+{
+ return "Decade(tracks.year)";
+}
+
diff --git a/mg_db_gd_sqlite.h b/mg_db_gd_sqlite.h
new file mode 100644
index 0000000..17de670
--- /dev/null
+++ b/mg_db_gd_sqlite.h
@@ -0,0 +1,67 @@
+/*!
+ * \file mg_db_gd_sqlite.h
+ * \brief A capsule around giantdisc database access
+ *
+ * \version $Revision: 1.2 $
+ * \date $Date: 2005-04-13 17:42:54 +0100 (Thu, 13 Apr 2005) $
+ * \author Wolfgang Rohdewald
+ * \author Responsible author: $Author: wolfgang61 $
+ */
+
+#ifndef __MG_DB_GD_H
+#define __MG_DB_GD_H
+
+#include <string>
+#include <tag.h>
+#include <map>
+#include <sqlite3.h>
+
+using namespace std;
+
+#include "mg_db.h"
+
+class mgSQLStringSQLite : public mgSQLStringImp {
+ public:
+ mgSQLStringSQLite(const char* s);
+ ~mgSQLStringSQLite();
+ char *unquoted() const;
+ private:
+ mutable char* m_unquoted;
+};
+
+class mgQuerySQLite : public mgQueryImp {
+ public:
+ mgQuerySQLite(void *db,string sql,mgQueryNoise noise);
+ ~mgQuerySQLite();
+ char ** Next();
+ private:
+ char **m_table;
+ int m_rc;
+};
+
+class mgDbGd : public mgDb {
+ public:
+ mgDbGd (bool SeparateThread=false);
+ ~mgDbGd();
+ bool Connect();
+ bool Create();
+
+ bool NeedGenre2();
+ long thread_id() { return -1; }
+ bool FieldExists(string table, string field);
+ bool Threadsafe();
+ const char* Options() const;
+ const char* HelpText() const;
+ void *ImplDbHandle() const { return m_db; }
+ const char *DecadeExpr();
+ string Now() const { return "strftime('%s','now')"; }
+ string Directory() const { return "mgDirectory(mp3file)"; }
+ protected:
+ void StartTransaction();
+ void Commit();
+ private:
+ sqlite3 *m_db;
+};
+
+
+#endif
diff --git a/mg_item.c b/mg_item.c
new file mode 100644
index 0000000..36cdffe
--- /dev/null
+++ b/mg_item.c
@@ -0,0 +1,114 @@
+/*!
+ * \file mg_item.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 <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "mg_item.h"
+#include "mg_tools.h"
+#include "mg_setup.h"
+
+bool
+mgItem::Valid(bool Silent) const
+{
+ if (!m_validated)
+ {
+ getSourceFile(true,Silent); // sets m_valid as a side effect
+ m_validated=true;
+ }
+ return m_valid;
+}
+
+bool
+mgItem::readable(string filename) const
+{
+ errno=0;
+ string fullname;
+ fullname = the_setup.ToplevelDir + filename;
+ int fd = open(fullname.c_str(),O_RDONLY);
+ if (fd<0)
+ return false;
+ char buf[20];
+ errno=0;
+ int i=read(fd,buf,10);
+ int err = errno;
+ close(fd);
+ if (i!=10)
+ {
+ errno = 123456;
+ return false;
+ }
+ else
+ errno = err;
+ close(fd);
+ errno = 0;
+ return true;
+}
+
+void
+mgItem::analyze_failure(string filename) const
+{
+ readable(filename); // sets errno
+ int err = errno;
+ int nsize = filename.size();
+ char *p;
+ if (err==123456)
+ p="File too short";
+ else
+ p = strerror(err);
+ extern void showmessage(int duration,const char*,...);
+ if (nsize<20)
+ showmessage(0,"%s not readable, errno=%d",filename.c_str(),err);
+ else
+ showmessage(0,"%s..%s not readable, errno=%d",
+ filename.substr(0,15).c_str(),filename.substr(nsize-15).c_str(),err);
+ mgWarning ("cannot read %s: %s", filename.c_str (),p);
+}
+
+mgItem::mgItem()
+{
+ m_valid = false;
+ m_validated = false;
+ m_itemid = -1;
+ m_year = 0;
+ m_rating = 0;
+ m_duration = 0;
+}
+
+mgItem*
+mgItem::Clone ()
+{
+ if (!this)
+ return 0;
+ mgItem* result = new mgItem();
+ result->InitFrom(this);
+ return result;
+}
+
+void
+mgItem::InitFrom(const mgItem* c)
+{
+ m_sel = c->m_sel;
+ m_valid = c->m_valid;
+ m_validated = c->m_validated;
+ m_itemid = c->m_itemid;
+ m_title = c->m_title;
+ m_realfile = c->m_realfile;
+ m_genre_id = c->m_genre_id;
+ m_genre = c->m_genre;
+ m_language = c->m_language;
+ m_language_id = c->m_language_id;
+ m_year = c->m_year;
+ m_rating = c->m_rating;
+ m_duration = c->m_duration;
+}
+
diff --git a/mg_item.h b/mg_item.h
new file mode 100644
index 0000000..4d5bf2d
--- /dev/null
+++ b/mg_item.h
@@ -0,0 +1,71 @@
+/* \file mg_item.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_ITEM_H
+#define _MG_ITEM_H
+#include <string>
+
+using namespace std;
+
+#include "mg_listitem.h"
+#include "mg_keytypes.h"
+#include "mg_selection.h"
+
+class mgSelection;
+
+//! \brief represents a content item
+class mgItem
+{
+ public:
+//! \brief copy constructor
+// mgItem (const mgItem* c);
+ mgItem();
+ virtual mgItem* Clone();
+ virtual ~mgItem() {};
+ bool Valid(bool Silent=false) const;
+ virtual long getItemid() const { return m_itemid; }
+ virtual mgListItem* getKeyItem(mgKeyTypes kt) const { return new mgListItem; }
+//! \brief returns filename
+ virtual string getSourceFile (bool AbsolutePath=true,bool Silent=false) const { return m_realfile; }
+//! \brief returns title
+ string getTitle () const { return m_title; }
+//! \brief returns the name of the language
+ string getLanguage () const { return m_language; }
+//! \brief returns year
+ int getYear () const { return m_year; }
+//! \brief returns rating
+ int getRating () const { return m_rating; }
+//! \brief returns duration
+ int getDuration () const { return m_duration; }
+ virtual string getImagePath (bool AbsolutePath=true) const { return ""; }
+ void setSelection(const mgSelection* sel) { m_sel=sel; }
+ const mgSelection* getSelection() const { return m_sel; }
+
+ protected:
+ mutable bool m_valid;
+ mutable bool m_validated;
+ long m_itemid;
+ string m_title;
+ string m_realfile;
+ int m_year;
+ int m_rating;
+ int m_duration;
+ string m_genre_id;
+ string m_genre;
+ string m_language_id;
+ string m_language;
+ bool readable(string filename) const;
+ void analyze_failure(string filename) const;
+ void InitFrom(const mgItem* c);
+ const mgSelection* m_sel;
+};
+
+
+#endif
diff --git a/mg_item_gd.c b/mg_item_gd.c
new file mode 100644
index 0000000..817d9ab
--- /dev/null
+++ b/mg_item_gd.c
@@ -0,0 +1,355 @@
+/*!
+ * \file mg_item_gd.c
+ * \brief An interface to GiantDisc data items
+ *
+ * \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 <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "mg_item_gd.h"
+#include "mg_setup.h"
+#include "mg_tools.h"
+#include "mg_db.h"
+
+// this one last because of swap() redefinition:
+#include <tools.h>
+
+static bool gd_music_dir_exists[100];
+static bool gd_music_dirs_scanned=false;
+
+mgListItem*
+mgItemGd::getKeyItem(mgKeyTypes kt) const
+{
+ string val;
+ string id;
+ long number=0;
+ bool val_is_number=false;
+ bool id_is_number=false;
+ if (m_itemid>=0)
+ {
+ switch (kt) {
+ case keyGdGenres:
+ case keyGdGenre1:
+ case keyGdGenre2:
+ case keyGdGenre3: val = getGenre();id=m_genre_id;break;
+ case keyGdArtist: val = id = getArtist();break;
+ case keyGdArtistABC: val = id = getArtist()[0];break;
+ case keyGdAlbum: val = id = getAlbum();break;
+ case keyGdYear: val = id = string(ltos(getYear()));break;
+ case keyGdDecade: val = id = string(ltos(int((getYear() % 100) / 10) * 10));break;
+ case keyGdTitle: val = id = getTitle();break;
+ case keyGdTitleABC: val = id = getTitle()[0];break;
+ case keyGdTrack: val = getTitle();id_is_number=true;number=getTrack();break;
+ case keyGdLanguage: val = getLanguage();id=m_language_id ; break;
+ case keyGdRating: id_is_number=val_is_number=true;number=getRating();break;
+ case keyGdFolder1:
+ case keyGdFolder2:
+ case keyGdFolder3:
+ case keyGdFolder4:
+ {
+ char *folders[4];
+ char *fbuf=SeparateFolders(m_mp3file.c_str(),folders,4);
+ val = id = folders[int(kt)-int(keyGdFolder1)];
+ free(fbuf);
+ break;
+ }
+ default: return new mgListItem;
+ }
+ }
+ if (val_is_number)
+ {
+ char valbuf[30];
+ sprintf(valbuf,"%.5ld",number);
+ val=valbuf;
+ }
+ if (id_is_number)
+ {
+ char idbuf[30];
+ sprintf(idbuf,"%.5ld",number);
+ id=idbuf;
+ }
+ return new mgListItem(val,id);
+}
+
+
+string mgItemGd::getGenre () const
+{
+ string result="";
+ if (m_genre!="NULL")
+ result = m_genre;
+ if (m_genre2!="NULL" && m_genre2.size()>0)
+ {
+ if (!result.empty())
+ result += "/";
+ result += m_genre2;
+ }
+ return result;
+}
+
+
+string mgItemGd::getBitrate () const
+{
+ return m_bitrate;
+}
+
+
+string mgItemGd::getArtist () const
+{
+ return m_artist;
+}
+
+
+string mgItemGd::getAlbum () const
+{
+ return m_albumtitle;
+}
+
+
+int mgItemGd::getSampleRate () const
+{
+ return m_samplerate;
+}
+
+
+int mgItemGd::getChannels () const
+{
+ return m_channels;
+}
+
+int mgItemGd::getTrack () const
+{
+ return m_tracknb;
+}
+
+mgItemGd::mgItemGd(const mgItemGd* c)
+{
+ m_covercount = 0;
+ InitFrom(c);
+}
+
+mgItemGd*
+mgItemGd::Clone ()
+{
+ if (!this)
+ return 0;
+ else
+ return new mgItemGd(this);
+}
+
+void
+mgItemGd::InitFrom(const mgItemGd* c)
+{
+ mgItem::InitFrom(c);
+ m_mp3file = c->m_mp3file;
+ m_artist = c->m_artist;
+ m_albumtitle = c->m_albumtitle;
+ m_genre2_id = c->m_genre2_id;
+ m_genre2 = c->m_genre2;
+ m_bitrate = c->m_bitrate;
+ m_samplerate = c->m_samplerate;
+ m_channels = c->m_channels;
+ m_tracknb = c->m_tracknb;
+}
+
+
+string
+mgItemGd::getSourceFile(bool AbsolutePath,bool Silent) const
+{
+ string result;
+ string tld = the_setup.ToplevelDir;
+ if (AbsolutePath)
+ {
+ result = getSourceFile(false,Silent);
+ if (!result.empty())
+ result = tld + result;
+ return result;
+ }
+ int access_errno=0;
+ result = m_mp3file;
+ if (m_validated && !m_valid)
+ return m_mp3file;
+ if (!readable(result))
+ {
+ access_errno=errno;
+ result="";
+ if (!gd_music_dirs_scanned)
+ {
+ for (unsigned int i =0 ; i < 100 ; i++)
+ {
+ struct stat stbuf;
+ char *dir;
+ asprintf(&dir,"%s%02d",tld.c_str(),i);
+ gd_music_dir_exists[i]=!stat(dir,&stbuf);
+ free(dir);
+ }
+ gd_music_dirs_scanned=true;
+ }
+ for (unsigned int i =0 ; i < 100 ; i++)
+ {
+ if (!gd_music_dir_exists[i])
+ continue;
+ char *file;
+ asprintf(&file,"%02d/%s",i,m_mp3file.c_str());
+ if (readable(file))
+ {
+ m_mp3file = file;
+ result = m_mp3file;
+ }
+ free(file);
+ if (!result.empty())
+ break;
+ }
+ }
+ m_validated=true;
+ if (result.empty())
+ {
+ if (!Silent)
+ analyze_failure(m_mp3file);
+ m_valid = false;
+ return m_mp3file;
+ }
+ return result;
+}
+
+string
+mgItemGd::getImagePath(bool AbsolutePath) const
+{
+ string result;
+ if (AbsolutePath)
+ {
+ result = getImagePath(false);
+ if (!result.empty())
+ result = string(the_setup.ToplevelDir) + result;
+ return result;
+ }
+ if (!m_coverimg.empty())
+ {
+ result = m_coverimg;
+ if (!readable(result))
+ {
+ analyze_failure(result);
+ m_coverimg="";
+ }
+ else
+ return result;
+ }
+ result = getSourceFile(false,true);
+ const char* jpg = ".jpg";
+ string::size_type dot = result.rfind('.');
+ if (dot==string::npos)
+ result += jpg;
+ else
+ result.replace(dot,999,jpg);
+ if (readable(result))
+ return result;
+#ifdef DIAS
+ while (true)
+ {
+ m_covercount++;
+ result = getSourceFile(false,true);
+ dot = result.rfind('.');
+ if (dot==string::npos)
+ result += '.' + m_covercount + jpg;
+ else
+ result.replace(dot,999,'.' + m_covercount + jpg);
+ if (readable(result))
+ return result;
+ if (m_covercount == 1)
+ break;
+ m_covercount = 0;
+ }
+#endif
+ result = getSourceFile(false,true);
+ while (true)
+ {
+ string::size_type slash = result.rfind('/');
+ if (slash == string::npos)
+ {
+ if (readable("cover.jpg"))
+ return "cover.jpg";
+ break;
+ }
+ result.replace(slash,999,"");
+ if (readable(result+"/cover.jpg"))
+ return result+"/cover.jpg";
+ }
+ return "";
+}
+
+mgItemGd::mgItemGd (char **row)
+{
+ m_valid = true;
+ m_validated = false;
+ m_covercount = 0;
+ m_itemid = 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] && row[5][0])
+ m_genre_id = row[5];
+ else
+ m_genre_id = "NULL";
+ m_genre = KeyMaps.value(keyGdGenres,m_genre_id);
+ if (row[6] && row[6][0])
+ m_genre2_id = row[6];
+ else
+ m_genre2_id = "NULL";
+ m_genre2 = KeyMaps.value(keyGdGenres,m_genre2_id);
+ 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;
+ if (row[13])
+ m_language_id = row[13];
+ else
+ m_language_id = "NULL";
+ if (row[14])
+ m_tracknb = atol(row[14]);
+ else
+ m_tracknb = 0;
+ if (row[15])
+ m_coverimg = row[14];
+ else
+ m_coverimg = "";
+ m_language = KeyMaps.value(keyGdLanguage,m_language_id);
+};
+
diff --git a/mg_item_gd.h b/mg_item_gd.h
new file mode 100644
index 0000000..1eba25b
--- /dev/null
+++ b/mg_item_gd.h
@@ -0,0 +1,78 @@
+/*!
+ * \file mg_item.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_ITEM_GD_H
+#define _MG_ITEM_GD_H
+#include <stdlib.h>
+#include <string>
+
+using namespace std;
+
+#include "mg_tools.h"
+#include "mg_item.h"
+
+//! \brief represents a GiantDisc content item
+class mgItemGd : public mgItem
+{
+ public:
+ mgListItem* getKeyItem(mgKeyTypes kt) const;
+
+ //! \brief copy constructor
+ mgItemGd(const mgItemGd* c);
+
+ //! \brief construct an item from an SQL row
+ mgItemGd (char **row);
+
+ mgItemGd* Clone();
+
+//! \brief returns filename
+ string getSourceFile (bool AbsolutePath=true,bool Silent=false) const;
+
+//! \brief returns artist
+ string getArtist () const;
+
+//! \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 samplerate
+ int getSampleRate () const;
+
+//! \brief returns # of channels
+ int getChannels () const;
+
+//! \brief returns tracknb
+ int getTrack () const;
+
+ string getImagePath(bool AbsolutePath=true) const;
+
+ protected:
+ void InitFrom(const mgItemGd* c);
+ private:
+ mutable string m_mp3file;
+ string m_artist;
+ string m_albumtitle;
+ string m_genre2_id;
+ string m_genre2;
+ string m_bitrate;
+ mutable string m_coverimg;
+ int m_samplerate;
+ int m_channels;
+ int m_tracknb;
+ mutable int m_covercount;
+};
+
+#endif
diff --git a/mg_keytypes.h b/mg_keytypes.h
new file mode 100644
index 0000000..1971cfa
--- /dev/null
+++ b/mg_keytypes.h
@@ -0,0 +1,51 @@
+/*! \file mg_keytypes.h
+ * \ingroup muggle
+ * \brief key type definitions for all backends
+ *
+ * \version $Revision: 1.4 $
+ * \author Wolfgang Rohdewald
+ * \author file owner: $Author: wr61 $
+ *
+ */
+
+/* makes sure we don't use the same declarations twice */
+#ifndef _MUGGLE_KEYTYPES_H
+#define _MUGGLE_KEYTYPES_H
+
+enum mgKeyTypes {
+ keyGdGenre1=1, // the genre types must have exactly this order!
+ keyGdGenre2,
+ keyGdGenre3,
+ keyGdGenres,
+ keyGdDecade,
+ keyGdYear,
+ keyGdArtist,
+ keyGdAlbum,
+ keyGdTitle,
+ keyGdTrack,
+ keyGdLanguage,
+ keyGdRating,
+ keyGdFolder1,
+ keyGdFolder2,
+ keyGdFolder3,
+ keyGdFolder4,
+ keyGdCreated,
+ keyGdModified,
+ keyGdArtistABC,
+ keyGdTitleABC,
+ keyGdCollection,
+ keyGdCollectionItem,
+ keyGdUnique
+ // here come other backends: keyXXTitle = 50, (reserve)
+};
+const mgKeyTypes mgGdKeyTypesLow = keyGdGenre1;
+const mgKeyTypes mgGdKeyTypesHigh = keyGdUnique;
+
+enum mgSortBy {
+ mgSortNone,
+ mgSortByValue,
+ mgSortById,
+ mgSortByIdNum
+};
+
+#endif
diff --git a/mg_listitem.c b/mg_listitem.c
new file mode 100644
index 0000000..0f32263
--- /dev/null
+++ b/mg_listitem.c
@@ -0,0 +1,114 @@
+/*!
+ * \file mg_listitem.c
+ * \brief An item as delivered by mgSelection
+ *
+ * \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_listitem.h"
+#include <assert.h>
+
+mgListItem::mgListItem()
+{
+ m_valid=false;
+ m_count=0;
+}
+
+mgListItem::mgListItem(const mgListItem* from)
+{
+ assert(from);
+ m_valid=from->m_valid;
+ m_value=from->m_value;
+ m_id=from->m_id;
+ m_count=from->m_count;
+}
+
+mgListItem::mgListItem(string v,string i,unsigned int c)
+{
+ set(v,i,c);
+}
+
+mgListItem*
+mgListItem::Clone()
+{
+ if (!this)
+ return 0;
+ else
+ return new mgListItem(this);
+}
+
+void
+mgListItem::set(string v,string i,unsigned int c)
+{
+ assert(this);
+ m_valid=true;
+ m_value=v;
+ m_id=i;
+ m_count=c;
+}
+
+void
+mgListItem::operator=(const mgListItem& from)
+{
+ assert(this);
+ m_valid=from.m_valid;
+ m_value=from.m_value;
+ m_id=from.m_id;
+ m_count=from.m_count;
+}
+
+void
+mgListItem::operator=(const mgListItem* from)
+{
+ assert(this);
+ m_valid=from->valid();
+ m_value=from->value();
+ m_id=from->id();
+ m_count=from->count();
+}
+
+bool
+mgListItem::operator==(const mgListItem& other) const
+{
+ if (!this)
+ return false;
+ return m_value == other.m_value
+ && m_id == other.m_id;
+}
+
+string
+mgListItem::value() const
+{
+ if (!this)
+ return "";
+ else
+ return m_value;
+}
+
+string
+mgListItem::id() const
+{
+ if (!this)
+ return "";
+ else
+ return m_id;
+}
+
+unsigned int
+mgListItem::count() const
+{
+ if (!this)
+ return 0;
+ else
+ return m_count;
+}
+
+bool
+mgListItem::valid() const
+{
+ return this && m_valid;
+}
diff --git a/mg_listitem.h b/mg_listitem.h
new file mode 100644
index 0000000..ada27d1
--- /dev/null
+++ b/mg_listitem.h
@@ -0,0 +1,41 @@
+/*!
+ * \file mg_listitem.h
+ * \brief an item as delivered by mgSelection
+ * \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_LISTITEM_H
+#define _MG_LISTITEM_H
+#include <stdlib.h>
+#include <string>
+
+using namespace std;
+
+
+class mgListItem
+{
+ public:
+ mgListItem();
+ mgListItem(string v,string i,unsigned int c=0);
+ mgListItem* Clone();
+ mgListItem(const mgListItem *from);
+ void set(string v,string i,unsigned int c=0);
+ void operator=(const mgListItem& from);
+ void operator=(const mgListItem* from);
+ bool operator==(const mgListItem& other) const;
+ string value() const;
+ string id() const;
+ unsigned int count() const;
+ bool valid() const;
+ private:
+ bool m_valid;
+ string m_value;
+ string m_id;
+ unsigned int m_count;
+};
+
+#endif
diff --git a/mg_mysql.c b/mg_mysql.c
deleted file mode 100644
index c26f724..0000000
--- a/mg_mysql.c
+++ /dev/null
@@ -1,590 +0,0 @@
-/*! \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 <stdio.h>
-#include <stdarg.h>
-#include <errno.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <time.h>
-
-#include "mg_setup.h"
-
-bool needGenre2;
-static bool needGenre2_set;
-bool UsingEmbedded();
-
-class mysqlhandle_t {
- public:
- mysqlhandle_t();
- ~mysqlhandle_t();
-};
-
-
-static char *datadir;
-
-static char *embedded_args[] =
-{
- "muggle",
- "--datadir=/tmp", // stupid default
- "--key_buffer_size=32M"
-};
-
-#ifndef HAVE_ONLY_SERVER
-static char *embedded_groups[] =
-{
- "embedded",
- "server",
- "muggle_SERVER",
- 0
-};
-#endif
-
-void
-set_datadir(char *dir)
-{
- mgDebug(1,"setting datadir to %s",dir);
- struct stat stbuf;
- datadir=strdup(dir);
- asprintf(&embedded_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);
- }
-}
-
-
-mysqlhandle_t::mysqlhandle_t()
-{
-#ifndef HAVE_ONLY_SERVER
- int argv_size;
- if (UsingEmbedded())
- {
- mgDebug(1,"calling mysql_server_init for embedded");
- argv_size = sizeof(embedded_args) / sizeof(char *);
- }
- else
- {
- if (strcmp(MYSQLCLIENTVERSION,"4.1.11")<0)
- mgError("You have embedded mysql. For accessing external servers "
- "you need mysql 4.1.11 but you have only %s", MYSQLCLIENTVERSION);
- mgDebug(1,"calling mysql_server_init for external");
- argv_size = -1;
- }
- if (mysql_server_init(argv_size, embedded_args, embedded_groups))
- mgDebug(3,"mysql_server_init failed");
-#endif
-}
-
-mysqlhandle_t::~mysqlhandle_t()
-{
-#ifndef HAVE_ONLY_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;"
-};
-
-bool
-mgmySql::sql_query(const char *sql)
-{
- return mysql_query(m_db,sql);
-}
-
-
-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 (sql_query (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);
- char buffer[500];
- sprintf(buffer,"DROP DATABASE IF EXISTS %s",the_setup.DbName);
- if (strlen(buffer)>400)
- mgError("name of database too long: %s",the_setup.DbName);
- if (sql_query(buffer))
- {
- mgWarning("Cannot drop %s:%s",the_setup.DbName,
- mysql_error (m_db));
- return;
- }
- sprintf(buffer,"CREATE DATABASE %s",the_setup.DbName);
- if (sql_query(buffer))
- {
- mgWarning("Cannot create %s:%s",the_setup.DbName,mysql_error (m_db));
- return;
- }
-
- if (!UsingEmbedded())
- sprintf(buffer,"grant all privileges on %s.* to vdr@localhost",
- the_setup.DbName);
- sql_query(buffer);
- // ignore error. If we can create the data base, we can do everything
- // with it anyway.
-
- if (mysql_select_db(m_db,the_setup.DbName))
- mgError("mysql_select_db(%s) failed with %s",mysql_error(m_db));
-
- int len = sizeof( db_cmds ) / sizeof( char* );
- for( int i=0; i < len; i ++ )
- {
- if (sql_query (db_cmds[i]))
- {
- mgWarning("%20s: %s",db_cmds[i],mysql_error (m_db));
- sprintf(buffer,"DROP DATABASE IF EXISTS %s",the_setup.DbName);
- sql_query(buffer); // clean up
- return;
- }
- }
- m_database_found=true;
- 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;
-}
-
-
-bool
-UsingEmbedded()
-{
-#ifdef HAVE_ONLY_SERVER
- return false;
-#else
- return the_setup.NoHost();
-#endif
-}
-
-void
-mgmySql::Connect ()
-{
- assert(!m_db);
- m_db = mysql_init (0);
- if (!m_db)
- return;
- if (UsingEmbedded())
- {
- 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);
- }
- else
- {
- if (the_setup.NoHost() || !strcmp(the_setup.DbHost,"localhost"))
- mgDebug(1,"Using socket %s for connecting to local system as user %s.",
- the_setup.DbSocket, the_setup.DbUser);
- else
- mgDebug(1,"Using TCP for connecting to server %s as user %s.",
- the_setup.DbHost, the_setup.DbUser);
- if (!mysql_real_connect( m_db,
- the_setup.DbHost, the_setup.DbUser, the_setup.DbPass, 0,
- the_setup.DbPort, the_setup.DbSocket, 0 ) != 0 )
- {
- 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;
- }
- }
- if (m_db)
- {
- m_database_found = mysql_select_db(m_db,the_setup.DbName)==0;
- {
- if (Connected())
- mgDebug(1,"Selected database %s",the_setup.DbName);
- else
- if (!createtime)
- mgWarning("%s:%s",the_setup.DbName,mysql_error(m_db));
- }
- }
- if (!needGenre2_set && Connected())
- {
- needGenre2_set=true;
- needGenre2=exec_count("SELECT COUNT(DISTINCT genre2) from tracks")>1;
- }
- return;
-}
-
-
-void
-mgmySql::CreateFolderFields()
-{
- if (!Connected())
- return;
- if (HasFolderFields())
- return;
- sql_query("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 = !sql_query(
- "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
deleted file mode 100644
index 9ade129..0000000
--- a/mg_mysql.h
+++ /dev/null
@@ -1,76 +0,0 @@
-/*!
- * \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;}
- //! \brief create database and tables
- void Create();
- void FillTables();
- void CreateFolderFields();
- private:
- MYSQL *m_db;
- bool m_database_found;
- bool m_hasfolderfields;
- bool sql_query(const char *query);
- void Connect();
-};
-
-#endif
diff --git a/mg_order.c b/mg_order.c
deleted file mode 100644
index 4c44f28..0000000
--- a/mg_order.c
+++ /dev/null
@@ -1,1254 +0,0 @@
-#include "mg_order.h"
-#include "mg_tools.h"
-#include "i18n.h"
-#include <stdio.h>
-#include <assert.h>
-
-static map <mgKeyTypes, map<string,string> > map_values;
-static map <mgKeyTypes, map<string,string> > map_ids;
-
-mgKeyMaps KeyMaps;
-
-bool iskeyGenre(mgKeyTypes kt)
-{
- return kt>=keyGenre1 && kt <= keyGenres;
-}
-
-class mgRefParts : public mgParts {
- public:
- mgRefParts(const mgReference& r);
-};
-
-
-strlist& operator+=(strlist&a, strlist b)
-{
- a.insert(a.end(), b.begin(),b.end());
- return a;
-}
-
-
-/*! \brief if the SQL command works on only 1 table, remove all table
-* qualifiers. Example: SELECT tracks.title FROM tracks becomes SELECT title
-* FROM tracks
-* \param spar the sql command. It will be edited in place
-* \return the new sql command is also returned
-*/
-static string
-optimize (string & spar)
-{
- string s = spar;
- string::size_type tmp = s.find (" WHERE");
- if (tmp != string::npos)
- s.erase (tmp, 9999);
- tmp = s.find (" ORDER");
- if (tmp != string::npos)
- s.erase (tmp, 9999);
- string::size_type frompos = s.find (" FROM ") + 6;
- if (s.substr (frompos).find (",") == string::npos)
- {
- string from = s.substr (frompos, 999) + '.';
- string::size_type track;
- while ((track = spar.find (from)) != string::npos)
- {
- spar.erase (track, from.size ());
- }
- }
- return spar;
-}
-
-string&
-addsep (string & s, string sep, string n)
-{
- if (!n.empty ())
- {
- if (!s.empty ())
- s.append (sep);
- s.append (n);
- }
- return s;
-}
-
-
-static string
-sql_list (string prefix,strlist v,string sep=",",string postfix="")
-{
- string result = "";
- for (list < string >::iterator it = v.begin (); it != v.end (); ++it)
- {
- addsep (result, sep, *it);
- }
- if (!result.empty())
- {
- result.insert(0," "+prefix+" ");
- result += postfix;
- }
- return result;
-}
-
-//! \brief converts long to string
-string
-itos (int i)
-{
- stringstream s;
- s << i;
- return s.str ();
-}
-
-//! \brief convert long to string
-string
-ltos (long l)
-{
- stringstream s;
- s << l;
- return s.str ();
-}
-
-
-class mgKeyNormal : public mgKey {
- public:
- mgKeyNormal(const mgKeyNormal& k);
- mgKeyNormal(const mgKeyTypes kt, string table, string field);
- virtual mgParts Parts(mgmySql &db,bool orderby=false) const;
- string value() const;
- string id() const;
- bool valid() const;
- void set(mgListItem& item);
- mgListItem& get();
- mgKeyTypes Type() const { return m_kt; }
- virtual string expr() const { return m_table + "." + m_field; }
- virtual string table() const { return m_table; }
- protected:
- string IdClause(mgmySql &db,string what,string::size_type start=0,string::size_type len=string::npos) const;
- void AddIdClause(mgmySql &db,mgParts &result,string what) const;
- mgListItem m_item;
- string m_field;
- private:
- mgKeyTypes m_kt;
- string m_table;
-};
-
-class mgKeyABC : public mgKeyNormal {
- public:
- mgKeyABC(const mgKeyNormal& k) : mgKeyNormal(k) {}
- mgKeyABC(const mgKeyTypes kt, string table, string field) : mgKeyNormal(kt,table,field) {}
- virtual string expr() const { return "substring("+mgKeyNormal::expr()+",1,1)"; }
- protected:
- //void AddIdClause(mgmySql &db,mgParts &result,string what) const;
-};
-
-class mgKeyDate : public mgKeyNormal {
- public:
- mgKeyDate(mgKeyTypes kt,string table, string field) : mgKeyNormal(kt,table,field) {}
-};
-
-class mgKeyTrack : public mgKeyNormal {
- public:
- mgKeyTrack() : mgKeyNormal(keyTrack,"tracks","tracknb") {};
- mgParts Parts(mgmySql &db,bool orderby=false) const;
-};
-
-class mgKeyAlbum : public mgKeyNormal {
- public:
- mgKeyAlbum() : mgKeyNormal(keyAlbum,"album","title") {};
- mgParts Parts(mgmySql &db,bool orderby=false) const;
-};
-
-mgParts
-mgKeyAlbum::Parts(mgmySql &db,bool orderby) const
-{
- mgParts result = mgKeyNormal::Parts(db,orderby);
- result.tables.push_back("tracks");
- return result;
-}
-
-class mgKeyFolder : public mgKeyNormal {
- public:
- mgKeyFolder(mgKeyTypes kt,const char *fname)
- : mgKeyNormal(kt,"tracks",fname) { m_enabled=-1;};
- bool Enabled(mgmySql &db);
- private:
- int m_enabled;
-};
-
-class mgKeyFolder1 : public mgKeyFolder {
- public:
- mgKeyFolder1() : mgKeyFolder(keyFolder1,"folder1") {};
-};
-class mgKeyFolder2 : public mgKeyFolder {
- public:
- mgKeyFolder2() : mgKeyFolder(keyFolder2,"folder2") {};
-};
-class mgKeyFolder3 : public mgKeyFolder {
- public:
- mgKeyFolder3() : mgKeyFolder(keyFolder3,"folder3") {};
-};
-class mgKeyFolder4 : public mgKeyFolder {
- public:
- mgKeyFolder4() : mgKeyFolder(keyFolder4,"folder4") {};
-};
-
-bool
-mgKeyFolder::Enabled(mgmySql &db)
-{
- if (m_enabled<0)
- {
- if (!db.Connected())
- return false;
- char *b;
- asprintf(&b,"DESCRIBE tracks %s",m_field.c_str());
- MYSQL_RES * rows = db.exec_sql (b);
- free(b);
- if (rows)
- {
- m_enabled = mysql_num_rows(rows);
- mysql_free_result (rows);
- }
- }
- return (m_enabled==1);
-}
-
-class mgKeyGenres : public mgKeyNormal {
- public:
- mgKeyGenres() : mgKeyNormal(keyGenres,"tracks","genre1") {};
- mgKeyGenres(mgKeyTypes kt) : mgKeyNormal(kt,"tracks","genre1") {};
- mgParts Parts(mgmySql &db,bool orderby=false) const;
- protected:
- string map_idfield() const { return "id"; }
- string map_valuefield() const { return "genre"; }
- string map_table() const { return "genre"; }
- virtual unsigned int genrelevel() const { return 4; }
- private:
- string GenreClauses(mgmySql &db,bool orderby) const;
-};
-
-class mgKeyGenre1 : public mgKeyGenres
-{
- public:
- mgKeyGenre1() : mgKeyGenres(keyGenre1) {}
- unsigned int genrelevel() const { return 1; }
-};
-
-class mgKeyGenre2 : public mgKeyGenres
-{
- public:
- mgKeyGenre2() : mgKeyGenres(keyGenre2) {}
- unsigned int genrelevel() const { return 2; }
-};
-
-class mgKeyGenre3 : public mgKeyGenres
-{
- public:
- mgKeyGenre3() : mgKeyGenres(keyGenre3) {}
- unsigned int genrelevel() const { return 3; }
-};
-
-string
-mgKeyGenres::GenreClauses(mgmySql &db,bool orderby) const
-{
- strlist g1;
- strlist g2;
-
- if (orderby)
- if (genrelevel()==4)
- {
- g1.push_back("tracks.genre1=genre.id");
- g2.push_back("tracks.genre2=genre.id");
- }
- else
- {
- g1.push_back("substring(tracks.genre1,1,"+ltos(genrelevel())+")=genre.id");
- g2.push_back("substring(tracks.genre2,1,"+ltos(genrelevel())+")=genre.id");
- }
-
- if (valid())
- {
- unsigned int len=genrelevel();
- if (len==4) len=0;
- g1.push_back(IdClause(db,"tracks.genre1",0,genrelevel()));
- g2.push_back(IdClause(db,"tracks.genre2",0,genrelevel()));
- }
-
- extern bool needGenre2;
- if (needGenre2)
- {
- string o1=sql_list("(",g1," AND ",")");
- if (o1.empty())
- return "";
- string o2=sql_list("(",g2," AND ",")");
- return string("(") + o1 + " OR " + o2 + string(")");
- }
- else
- return sql_list("",g1," AND ");
-}
-
-
-mgParts
-mgKeyGenres::Parts(mgmySql &db,bool orderby) const
-{
- mgParts result;
- result.clauses.push_back(GenreClauses(db,orderby));
- result.tables.push_back("tracks");
- if (orderby)
- {
- result.fields.push_back("genre.genre");
- result.fields.push_back("genre.id");
- result.tables.push_back("genre");
- result.orders.push_back("genre.genre");
- }
- return result;
-}
-
-
-class mgKeyLanguage : public mgKeyNormal {
- public:
- mgKeyLanguage() : mgKeyNormal(keyLanguage,"tracks","lang") {};
- mgParts Parts(mgmySql &db,bool orderby=false) const;
- protected:
- string map_idfield() const { return "id"; }
- string map_valuefield() const { return "language"; }
- string map_table() const { return "language"; }
-};
-
-class mgKeyCollection: public mgKeyNormal {
- public:
- mgKeyCollection() : mgKeyNormal(keyCollection,"playlist","id") {};
- mgParts Parts(mgmySql &db,bool orderby=false) const;
- protected:
- string map_idfield() const { return "id"; }
- string map_valuefield() const { return "title"; }
- string map_table() const { return "playlist"; }
-};
-class mgKeyCollectionItem : public mgKeyNormal {
- public:
- mgKeyCollectionItem() : mgKeyNormal(keyCollectionItem,"playlistitem","tracknumber") {};
- mgParts Parts(mgmySql &db,bool orderby=false) const;
-};
-
-class mgKeyDecade : public mgKeyNormal {
- public:
- mgKeyDecade() : mgKeyNormal(keyDecade,"tracks","year") {}
- string expr() const { return "substring(convert(10 * floor(tracks.year/10), char),3)"; }
-};
-
-string
-mgKeyNormal::id() const
-{
- return m_item.id();
-}
-
-bool
-mgKeyNormal::valid() const
-{
- return m_item.valid();
-}
-
-string
-mgKeyNormal::value() const
-{
- return m_item.value();
-}
-
-
-mgKeyNormal::mgKeyNormal(const mgKeyNormal& k)
-{
- m_kt = k.m_kt;
- m_table = k.m_table;
- m_field = k.m_field;
- m_item = k.m_item;
-}
-
-mgKeyNormal::mgKeyNormal(const mgKeyTypes kt, string table, string field)
-{
- m_kt = kt;
- m_table = table;
- m_field = field;
-}
-
-void
-mgKeyNormal::set(mgListItem& item)
-{
- m_item=item;
-}
-
-mgListItem&
-mgKeyNormal::get()
-{
- return m_item;
-}
-
-mgParts::mgParts()
-{
- m_sql_select="";
- orderByCount = false;
-}
-
-mgParts::~mgParts()
-{
-}
-
-mgParts
-mgKeyNormal::Parts(mgmySql &db, bool orderby) const
-{
- mgParts result;
- result.tables.push_back(table());
- AddIdClause(db,result,expr());
- if (orderby)
- {
- result.fields.push_back(expr());
- result.orders.push_back(expr());
- }
- return result;
-}
-
-string
-mgKeyNormal::IdClause(mgmySql &db,string what,string::size_type start,string::size_type len) const
-{
- if (len==0)
- len=string::npos;
- if (id() == "'NULL'")
- return what + " is NULL";
- else if (len==string::npos)
- return what + "=" + db.sql_string(id());
- else
- {
- return "substring("+what + ","+ltos(start+1)+","+ltos(len)+")="
- + db.sql_string(id().substr(start,len));
- }
-}
-
-void
-mgKeyNormal::AddIdClause(mgmySql &db,mgParts &result,string what) const
-{
- if (valid())
- result.clauses.push_back(IdClause(db,what));
-}
-
-mgParts
-mgKeyTrack::Parts(mgmySql &db,bool orderby) const
-{
- mgParts result;
- result.tables.push_back("tracks");
- AddIdClause(db,result,"tracks.title");
- if (orderby)
- {
- // if you change tracks.title, please also
- // change mgContentItem::getKeyItem()
- result.fields.push_back("tracks.title");
- result.orders.push_back("tracks.tracknb");
- }
- return result;
-}
-
-mgParts
-mgKeyLanguage::Parts(mgmySql &db,bool orderby) const
-{
- mgParts result;
- AddIdClause(db,result,"tracks.lang");
- result.tables.push_back("tracks");
- if (orderby)
- {
- result.fields.push_back("language.language");
- result.fields.push_back("tracks.lang");
- result.tables.push_back("language");
- result.orders.push_back("language.language");
- }
- return result;
-}
-
-mgParts
-mgKeyCollection::Parts(mgmySql &db,bool orderby) const
-{
- mgParts result;
- if (orderby)
- {
- result.tables.push_back("playlist");
- AddIdClause(db,result,"playlist.id");
- result.fields.push_back("playlist.title");
- result.fields.push_back("playlist.id");
- result.orders.push_back("playlist.title");
- }
- else
- {
- result.tables.push_back("playlistitem");
- AddIdClause(db,result,"playlistitem.playlist");
- }
- return result;
-}
-
-mgParts
-mgKeyCollectionItem::Parts(mgmySql &db,bool orderby) const
-{
- mgParts result;
- result.tables.push_back("playlistitem");
- AddIdClause(db,result,"playlistitem.tracknumber");
- if (orderby)
- {
- // tracks nur hier, fuer sql_delete_from_coll wollen wir es nicht
- result.tables.push_back("tracks");
- result.fields.push_back("tracks.title");
- result.fields.push_back("playlistitem.tracknumber");
- result.orders.push_back("playlistitem.tracknumber");
- }
- return result;
-}
-
-mgParts&
-mgParts::operator+=(mgParts a)
-{
- fields += a.fields;
- tables += a.tables;
- clauses += a.clauses;
- orders += a.orders;
- return *this;
-}
-
-mgRefParts::mgRefParts(const mgReference& r)
-{
- tables.push_back(r.t1());
- tables.push_back(r.t2());
- clauses.push_back(r.t1() + '.' + r.f1() + '=' + r.t2() + '.' + r.f2());
-}
-
-void
-mgParts::Prepare()
-{
- tables.sort();
- tables.unique();
- strlist::reverse_iterator it;
- string prevtable = "";
- for (it = tables.rbegin(); it != tables.rend(); ++it)
- {
- if (!prevtable.empty())
- *this += ref.Connect(prevtable,*it);
- prevtable = *it;
- }
- tables.sort();
- tables.unique();
- clauses.sort();
- clauses.unique();
- orders.unique();
-}
-
-string
-mgParts::sql_select(bool distinct)
-{
- if (!m_sql_select.empty())
- return m_sql_select;
- Prepare();
- string result;
- if (distinct)
- {
- fields.push_back("COUNT(*) AS mgcount");
- result = sql_list("SELECT",fields);
- fields.pop_back();
- }
- else
- result = sql_list("SELECT",fields);
- if (result.empty())
- return result;
- result += sql_list("FROM",tables);
- result += sql_list("WHERE",clauses," AND ");
- if (distinct)
- {
- result += sql_list("GROUP BY",fields);
- if (orderByCount)
- orders.insert(orders.begin(),"mgcount desc");
- }
- result += sql_list("ORDER BY",orders);
- optimize(result);
- return result;
-}
-
-string
-mgParts::sql_count()
-{
- Prepare();
- string result = sql_list("SELECT COUNT(DISTINCT",fields,",",")");
- if (result.empty())
- return result;
- result += sql_list("FROM",tables);
- result += sql_list("WHERE",clauses," AND ");
- optimize(result);
- return result;
-}
-
-bool
-mgParts::UsesTracks()
-{
- for (list < string >::iterator it = tables.begin (); it != tables.end (); ++it)
- if (*it == "tracks") return true;
- return false;
-}
-
-string
-mgParts::sql_delete_from_collection(string pid)
-{
- if (pid.empty())
- return "";
- Prepare();
- // del nach vorne, weil DELETE playlistitem die erste Table nimmt,
- // die passt, egal ob alias oder nicht.
- tables.push_front("playlistitem as del");
- clauses.push_back("del.playlist="+pid);
- // todo geht so nicht fuer andere selections
- if (UsesTracks())
- clauses.push_back("del.trackid=tracks.id");
- else
- clauses.push_back("del.trackid=playlistitem.trackid");
- string result = "DELETE playlistitem";
- result += sql_list(" FROM",tables);
- result += sql_list(" WHERE",clauses," AND ");
- optimize(result);
- return result;
-}
-
-string
-mgParts::sql_update(strlist new_values)
-{
- Prepare();
- assert(fields.size()==new_values.size());
- string result = sql_list("UPDATE",fields);
- result += sql_list(" FROM",tables);
- result += sql_list(" WHERE",clauses," AND ");
- result += sql_list("VALUES(",new_values,",",")");
- optimize(result);
- return result;
-}
-
-mgReference::mgReference(string t1,string f1,string t2,string f2)
-{
- m_t1 = t1;
- m_f1 = f1;
- m_t2 = t2;
- m_f2 = f2;
-}
-
-mgOrder::mgOrder()
-{
- clear();
- setKey (keyArtist);
- setKey (keyAlbum);
- setKey (keyTrack);
- m_orderByCount = false;
-}
-
-mgOrder::~mgOrder()
-{
- truncate(0);
-}
-
-mgKey*
-mgOrder::Key(unsigned int idx) const
-{
- return Keys[idx];
-}
-
-mgKey*&
-mgOrder::operator[](unsigned int idx)
-{
- assert(idx<size());
- return Keys[idx];
-}
-
-bool
-operator==(const mgOrder& a, const mgOrder &b)
-{
- bool result = a.size()==b.size();
- if (result)
- for (unsigned int i=0; i<a.size();i++)
- {
- result &= a.Key(i)->Type()==b.Key(i)->Type();
- if (!result) break;
- }
- return result;
-}
-
-const mgOrder&
-mgOrder::operator=(const mgOrder& from)
-{
- clear();
- InitFrom(from);
- return *this;
-}
-
-mgOrder::mgOrder(const mgOrder &from)
-{
- InitFrom(from);
-}
-
-void
-mgOrder::InitFrom(const mgOrder &from)
-{
- for (unsigned int i = 0; i < from.size();i++)
- {
- mgKey *k = ktGenerate(from.getKeyType(i));
- k->set(from.getKeyItem(i));
- Keys.push_back(k);
- }
- m_orderByCount=from.m_orderByCount;
-}
-
-string
-mgOrder::Name()
-{
- string result="";
- for (unsigned int idx=0;idx<size();idx++)
- {
- if (!result.empty()) result += ":";
- result += ktName(Keys[idx]->Type());
- }
- return result;
-}
-
-void
-mgOrder::setKey (const mgKeyTypes kt)
-{
- mgKey *newkey = ktGenerate(kt);
- if (newkey)
- Keys.push_back(newkey);
-}
-
-mgOrder::mgOrder(mgValmap& nv,char *prefix)
-{
- char *idx;
- asprintf(&idx,"%s.OrderByCount",prefix);
- m_orderByCount = nv.getbool(idx);
- free(idx);
- clear();
- for (unsigned int i = 0; i < 999 ; i++)
- {
- asprintf(&idx,"%s.Keys.%u.Type",prefix,i);
- unsigned int v = nv.getuint(idx);
- free(idx);
- if (v==0) break;
- setKey (mgKeyTypes(v) );
- }
- if (size()>0)
- clean();
-}
-
-void
-mgOrder::DumpState(mgValmap& nv, char *prefix) const
-{
- char n[100];
- sprintf(n,"%s.OrderByCount",prefix);
- nv.put(n,m_orderByCount);
- for (unsigned int i=0;i<size();i++)
- {
- sprintf(n,"%s.Keys.%d.Type",prefix,i);
- nv.put(n,int(Key(i)->Type()));
- }
-}
-
-mgOrder::mgOrder(vector<mgKeyTypes> kt)
-{
- m_orderByCount = false;
- setKeys(kt);
-}
-
-void
-mgOrder::setKeys(vector<mgKeyTypes> kt)
-{
- clear();
- for (unsigned int i=0;i<kt.size();i++)
- setKey(kt[i]);
- clean();
-}
-
-
-mgKeyTypes
-mgOrder::getKeyType(unsigned int idx) const
-{
- assert(idx<Keys.size());
- return Keys[idx]->Type();
-}
-
-mgListItem&
-mgOrder::getKeyItem(unsigned int idx) const
-{
- assert(idx<Keys.size());
- return Keys[idx]->get();
-}
-
-void
-mgOrder::truncate(unsigned int i)
-{
- while (size()>i)
- {
- delete Keys.back();
- Keys.pop_back();
- }
-}
-
-void
-mgOrder::clear()
-{
- truncate(0);
-}
-
-void
-mgOrder::clean()
-{
- // remove double entries:
- keyvector::iterator i;
- keyvector::iterator j;
- bool collection_found = false;
- bool collitem_found = false;
- bool album_found = false;
- bool tracknb_found = false;
- bool title_found = false;
- bool is_unique = false;
- for (i = Keys.begin () ; i != Keys.end (); ++i)
- {
- mgKeyNormal* k = dynamic_cast<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)
- || (collection_found && collitem_found);
- if (is_unique)
- {
- for (j = i+1 ; j !=Keys.end(); ++j)
- delete *j;
- Keys.erase(i+1,Keys.end ());
- break;
- }
- if (k->Type()==keyYear)
- {
- for (j = i+1 ; j != Keys.end(); ++j)
- if ((*j)->Type() == keyDecade)
- {
- delete *j;
- Keys.erase(j);
- break;
- }
- }
-cleanagain:
- for (j = i+1 ; j != Keys.end(); ++j)
- if ((*i)->Type() == (*j)->Type())
- {
- delete *j;
- Keys.erase(j);
- goto cleanagain;
- }
- }
- if (!is_unique)
- {
- if (!album_found)
- Keys.push_back(ktGenerate(keyAlbum));
- if (!title_found)
- Keys.push_back(ktGenerate(keyTitle));
- }
-}
-
-bool
-mgOrder::isCollectionOrder() const
-{
- return (size()==2
- && (Keys[0]->Type()==keyCollection)
- && (Keys[1]->Type()==keyCollectionItem));
-}
-
-mgParts
-mgOrder::Parts(mgmySql &db,unsigned int level,bool orderby) const
-{
- mgParts result;
- result.orderByCount = m_orderByCount;
- if (level==0 && isCollectionOrder())
- {
- // sql command contributed by jarny
- result.m_sql_select = string("select playlist.title,playlist.id, "
- "count(*) * (playlistitem.playlist is not null) from playlist "
- "left join playlistitem on playlist.id = playlistitem.playlist "
- "group by playlist.title");
- return result;
- }
- for (unsigned int i=0;i<=level;i++)
- {
- if (i==Keys.size()) break;
- mgKeyNormal *k = dynamic_cast<mgKeyNormal*>(Keys[i]);
- mgKeyTypes kt = k->Type();
- if (iskeyGenre(kt))
- {
- for (unsigned int j=i+1;j<=level;j++)
- {
- if (j>=Keys.size())
- break;
- mgKeyNormal *kn = dynamic_cast<mgKeyNormal*>(Keys[j]);
- if (kn)
- {
- mgKeyTypes knt = kn->Type();
- if (iskeyGenre(knt) && knt>kt && !kn->id().empty())
- goto next;
- }
- }
- }
- result += k->Parts(db,orderby && (i==level));
-next:
- continue;
- }
- return result;
-}
-
-string
-mgOrder::GetContent(mgmySql &db,unsigned int level,vector < mgContentItem > &content) const
-{
- mgParts p = Parts(db,level);
- p.fields.clear();
- p.fields.push_back("tracks.id");
- p.fields.push_back("tracks.title");
- p.fields.push_back("tracks.mp3file");
- p.fields.push_back("tracks.artist");
- p.fields.push_back("album.title");
- p.fields.push_back("tracks.genre1");
- p.fields.push_back("tracks.genre2");
- p.fields.push_back("tracks.bitrate");
- p.fields.push_back("tracks.year");
- p.fields.push_back("tracks.rating");
- p.fields.push_back("tracks.length");
- p.fields.push_back("tracks.samplerate");
- p.fields.push_back("tracks.channels");
- p.fields.push_back("tracks.lang");
- p.tables.push_back("tracks");
- p.tables.push_back("album");
- for (unsigned int i = level; i<size(); i++)
- p += Key(i)->Parts(db,true);
- string result = p.sql_select(false);
- content.clear ();
- MYSQL_RES *rows = db.exec_sql (result);
- if (rows)
- {
- MYSQL_ROW row;
- while ((row = mysql_fetch_row (rows)) != 0)
- content.push_back (mgContentItem (row));
- mysql_free_result (rows);
- }
- return result;
-}
-
-
-//! \brief right now thread locking should not be needed here
-mgReferences::mgReferences()
-{
- push_back(mgReference ("tracks","id","playlistitem","trackid"));
- push_back(mgReference ("playlist","id","playlistitem","playlist"));
- push_back(mgReference ("tracks","sourceid","album","cddbid"));
- push_back(mgReference ("tracks","lang","language","id"));
-}
-
-bool
-mgReferences::Equal(unsigned int i,string table1, string table2) const
-{
- const mgReference& r = operator[](i);
- string s1 = r.t1();
- string s2 = r.t2();
- return ((s1==table1) && (s2==table2))
- || ((s1==table2) && (s2==table1));
-}
-
-mgParts
-mgReferences::FindConnectionBetween(string table1, string table2) const
-{
- for (unsigned int i=0 ; i<size(); i++ )
- if (Equal(i,table1,table2))
- return mgRefParts(operator[](i));
- return mgParts();
-}
-
-mgParts
-mgReferences::ConnectToTracks(string table) const
-{
- mgParts result;
- if (table=="tracks")
- return result;
- result += FindConnectionBetween(table,"tracks");
- if (result.empty())
- {
- result += FindConnectionBetween(table,"playlistitem");
- if (!result.empty())
- {
- result += FindConnectionBetween("playlistitem","tracks");
- }
- else
- assert(false);
- }
- return result;
-}
-
-mgParts
-mgReferences::Connect(string table1, string table2) const
-{
- mgParts result;
- // same table?
- if (table1 == table2) return result;
- if (table1=="genre") return ConnectToTracks(table2);
- if (table2=="genre") return ConnectToTracks(table1);
- // do not connect aliases. See sql_delete_from_collection
- if (table1.find(" as ")!=string::npos) return result;
- if (table2.find(" as ")!=string::npos) return result;
- if (table1.find(" AS ")!=string::npos) return result;
- if (table2.find(" AS ")!=string::npos) return result;
- // direct connection?
- result += FindConnectionBetween(table1,table2);
- if (result.empty())
- {
- // indirect connection? try connecting via tracks
- result += ConnectToTracks(table1);
- result += ConnectToTracks(table2);
- }
- return result;
-}
-
-
-mgKey*
-ktGenerate(const mgKeyTypes kt)
-{
- mgKey* result = 0;
- switch (kt)
- {
- case keyGenres: result = new mgKeyGenres;break;
- case keyGenre1: result = new mgKeyGenre1;break;
- case keyGenre2: result = new mgKeyGenre2;break;
- case keyGenre3: result = new mgKeyGenre3;break;
- case keyFolder1:result = new mgKeyFolder1;break;
- case keyFolder2:result = new mgKeyFolder2;break;
- case keyFolder3:result = new mgKeyFolder3;break;
- case keyFolder4:result = new mgKeyFolder4;break;
- case keyArtist: result = new mgKeyNormal(kt,"tracks","artist");break;
- case keyArtistABC: result = new mgKeyABC(kt,"tracks","artist");break;
- case keyTitle: result = new mgKeyNormal(kt,"tracks","title");break;
- case keyTitleABC: result = new mgKeyABC(kt,"tracks","title");break;
- case keyTrack: result = new mgKeyTrack;break;
- case keyDecade: result = new mgKeyDecade;break;
- case keyAlbum: result = new mgKeyAlbum;break;
- case keyCreated: result = new mgKeyDate(kt,"tracks","created");break;
- case keyModified: result = new mgKeyDate(kt,"tracks","modified");break;
- case keyCollection: result = new mgKeyCollection;break;
- case keyCollectionItem: result = new mgKeyCollectionItem;break;
- case keyLanguage: result = new mgKeyLanguage;break;
- case keyRating: result = new mgKeyNormal(kt,"tracks","rating");break;
- case keyYear: result = new mgKeyNormal(kt,"tracks","year");break;
- }
- return result;
-}
-
-const char * const
-ktName(const mgKeyTypes kt)
-{
- const char * result = "";
- switch (kt)
- {
- case keyGenres: result = "Genre";break;
- case keyGenre1: result = "Genre1";break;
- case keyGenre2: result = "Genre2";break;
- case keyGenre3: result = "Genre3";break;
- case keyFolder1: result = "Folder1";break;
- case keyFolder2: result = "Folder2";break;
- case keyFolder3: result = "Folder3";break;
- case keyFolder4: result = "Folder4";break;
- case keyArtist: result = "Artist";break;
- case keyArtistABC: result = "ArtistABC";break;
- case keyTitle: result = "Title";break;
- case keyTitleABC: result = "TitleABC";break;
- case keyTrack: result = "Track";break;
- case keyDecade: result = "Decade";break;
- case keyAlbum: result = "Album";break;
- case keyCreated: result = "Created";break;
- case keyModified: result = "Modified";break;
- case keyCollection: result = "Collection";break;
- case keyCollectionItem: result = "Collection item";break;
- case keyLanguage: result = "Language";break;
- case keyRating: result = "Rating";break;
- case keyYear: result = "Year";break;
- }
- return tr(result);
-}
-
-mgKeyTypes
-ktValue(const char * name)
-{
- for (int kt=int(mgKeyTypesLow);kt<=int(mgKeyTypesHigh);kt++)
- if (!strcmp(name,ktName(mgKeyTypes(kt))))
- return mgKeyTypes(kt);
- mgError("ktValue(%s): unknown name",name);
- return mgKeyTypes(0);
-}
-
-static vector<int> keycounts;
-
-unsigned int
-mgOrder::keycount(mgKeyTypes kt) const
-{
- if (keycounts.size()==0)
- {
- for (unsigned int ki=int(mgKeyTypesLow);ki<=int(mgKeyTypesHigh);ki++)
- {
- keycounts.push_back(-1);
- }
- }
- int& count = keycounts[int(kt-mgKeyTypesLow)];
- if (count==-1)
- {
- mgKey* k = ktGenerate(kt);
- struct mgmySql db;
- if (k->Enabled(db))
- count = db.exec_count(k->Parts(db,true).sql_count());
- else
- count = 0;
- delete k;
- }
- return count;
-}
-
-
-bool
-mgOrder::UsedBefore(const mgKeyTypes kt,unsigned int level) const
-{
- if (level>=size())
- level = size() -1;
- for (unsigned int lx = 0; lx < level; lx++)
- if (getKeyType(lx)==kt)
- return true;
- return false;
-}
-
-vector <const char *>
-mgOrder::Choices(unsigned int level, unsigned int *current) const
-{
- vector<const char*> result;
- if (level>size())
- {
- *current = 0;
- return result;
- }
- for (unsigned int ki=int(mgKeyTypesLow);ki<=int(mgKeyTypesHigh);ki++)
- {
- mgKeyTypes kt = mgKeyTypes(ki);
- if (kt==getKeyType(level))
- {
- *current = result.size();
- result.push_back(ktName(kt));
- continue;
- }
- if (UsedBefore(kt,level))
- continue;
- if (kt==keyDecade && UsedBefore(keyYear,level))
- continue;
- if (kt==keyGenre1)
- {
- if (UsedBefore(keyGenre2,level)) continue;
- if (UsedBefore(keyGenre3,level)) continue;
- if (UsedBefore(keyGenres,level)) continue;
- }
- if (kt==keyGenre2)
- {
- if (UsedBefore(keyGenre3,level)) continue;
- if (UsedBefore(keyGenres,level)) continue;
- }
- if (kt==keyGenre3)
- {
- if (UsedBefore(keyGenres,level)) continue;
- }
- if (kt==keyFolder1)
- {
- if (UsedBefore(keyFolder2,level)) continue;
- if (UsedBefore(keyFolder3,level)) continue;
- if (UsedBefore(keyFolder4,level)) continue;
- }
- if (kt==keyFolder2)
- {
- if (UsedBefore(keyFolder3,level)) continue;
- if (UsedBefore(keyFolder4,level)) continue;
- }
- if (kt==keyFolder3)
- {
- if (UsedBefore(keyFolder4,level)) continue;
- }
- if (kt==keyCollection || kt==keyCollectionItem)
- result.push_back(ktName(kt));
- else if (keycount(kt)>1)
- result.push_back(ktName(kt));
- }
- return result;
-}
-
-bool
-mgKey::LoadMap() const
-{
- map<string,string>& idmap = map_ids[Type()];
- if (map_idfield().empty())
- {
- return false;
- }
- mgmySql db;
- map<string,string>& valmap = map_values[Type()];
- char *b;
- asprintf(&b,"select %s,%s from %s;",map_idfield().c_str(),map_valuefield().c_str(),map_table().c_str());
- MYSQL_RES *rows = db.exec_sql (string(b));
- free(b);
- if (rows)
- {
- MYSQL_ROW row;
- while ((row = mysql_fetch_row (rows)) != 0)
- {
- if (row[0] && row[1])
- {
- valmap[row[0]] = row[1];
- idmap[row[1]] = row[0];
- }
- }
- mysql_free_result (rows);
- }
- return true;
-}
-
-bool
-mgKeyMaps::loadvalues (mgKeyTypes kt) const
-{
- if (map_ids.count(kt)>0)
- return true;
- mgKey* k = ktGenerate(kt);
- bool result = k->LoadMap();
- delete k;
- return result;
-}
-
-string
-mgKeyMaps::value(mgKeyTypes kt, string idstr) const
-{
- if (loadvalues (kt))
- {
- map<string,string>& valmap = map_values[kt];
- map<string,string>::iterator it;
- it = valmap.find(idstr);
- if (it!=valmap.end())
- {
- string r = it->second;
- if (!r.empty())
- return r;
- }
- map_ids[kt].clear();
- loadvalues(kt);
- it = valmap.find(idstr);
- if (it!=valmap.end())
- return valmap[idstr];
- }
- return idstr;
-}
-
-string
-mgKeyMaps::id(mgKeyTypes kt, string valstr) const
-{
- if (loadvalues (kt))
- {
- map<string,string>& idmap = map_ids[kt];
- string v = idmap[valstr];
- if (kt==keyGenre1) v=v.substr(0,1);
- if (kt==keyGenre2) v=v.substr(0,2);
- if (kt==keyGenre3) v=v.substr(0,3);
- return v;
- }
- return valstr;
-}
diff --git a/mg_order.h b/mg_order.h
deleted file mode 100644
index 41d110b..0000000
--- a/mg_order.h
+++ /dev/null
@@ -1,162 +0,0 @@
-#ifndef _MG_SQL_H
-#define _MG_SQL_H
-#include <stdlib.h>
-#include <typeinfo>
-#include <string>
-#include <list>
-#include <vector>
-#include <map>
-#include <sstream>
-using namespace std;
-#include "mg_valmap.h"
-#include "mg_mysql.h"
-#include "mg_content.h"
-#include "mg_tools.h"
-
-using namespace std;
-
-typedef list<string> strlist;
-
-strlist& operator+=(strlist&a, strlist b);
-
-//! \brief adds string n to string s, using string sep to separate them
-string& addsep (string & s, string sep, string n);
-
-bool iskeyGenre(mgKeyTypes kt);
-
-class mgParts;
-
-class mgReference {
- public:
- mgReference(string t1,string f1,string t2,string f2);
- string t1() const { return m_t1; }
- string t2() const { return m_t2; }
- string f1() const { return m_f1; }
- string f2() const { return m_f2; }
- private:
- string m_t1;
- string m_t2;
- string m_f1;
- string m_f2;
-};
-
-class mgReferences : public vector<mgReference> {
-public:
- // \todo memory leak for vector ref?
- mgReferences();
- mgParts Connect(string c1, string c2) const;
-private:
- bool Equal(unsigned int i,string table1, string table2) const;
- mgParts FindConnectionBetween(string table1, string table2) const;
- mgParts ConnectToTracks(string table) const;
-};
-
-class mgKeyMaps {
- public:
- string value(mgKeyTypes kt, string idstr) const;
- string id(mgKeyTypes kt, string valstr) const;
- private:
- bool loadvalues (mgKeyTypes kt) const;
-};
-
-extern mgKeyMaps KeyMaps;
-
-class mgKey {
- public:
- virtual ~mgKey() {};
- virtual mgParts Parts(mgmySql &db,bool orderby=false) const = 0;
- virtual string id() const = 0;
- virtual bool valid() const = 0;
- virtual string value () const = 0;
- //!\brief translate field into user friendly string
- virtual void set(mgListItem& item) = 0;
- virtual mgListItem& get() = 0;
- virtual mgKeyTypes Type() const = 0;
- virtual bool Enabled(mgmySql &db) { return true; }
- virtual bool LoadMap() const;
- protected:
- virtual string map_idfield() const { return ""; }
- virtual string map_valuefield() const { return ""; }
- virtual string map_table() const { return ""; }
-};
-
-
-mgKey*
-ktGenerate(const mgKeyTypes kt);
-
-const char * const ktName(const mgKeyTypes kt);
-mgKeyTypes ktValue(const char * name);
-
-typedef vector<mgKey*> keyvector;
-
-class mgParts {
-public:
- mgParts();
- ~mgParts();
- strlist fields;
- strlist tables;
- strlist clauses;
- strlist groupby;
- strlist orders;
- mgParts& operator+=(mgParts a);
- void Prepare();
- string sql_count();
- string sql_select(bool distinct=true);
- string sql_delete_from_collection(string pid);
- string sql_update(strlist new_values);
- bool empty() const { return tables.size()==0;}
- string m_sql_select;
- bool orderByCount;
-private:
- bool UsesTracks();
- mgReferences ref;
-};
-
-//! \brief converts long to string
-string itos (int i);
-
-//! \brief convert long to string
-string ltos (long l);
-
-
-const unsigned int MaxKeys = 20;
-
-class mgOrder {
-public:
- mgOrder();
- mgOrder(const mgOrder &from);
- mgOrder(mgValmap& nv, char *prefix);
- mgOrder(vector<mgKeyTypes> kt);
- ~mgOrder();
- void InitFrom(const mgOrder &from);
- 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();
- mgKey* Key(unsigned int idx) const;
- mgKeyTypes getKeyType(unsigned int idx) const;
- mgListItem& getKeyItem(unsigned int idx) const;
- void setKeys(vector<mgKeyTypes> kt);
- string Name();
- void setOrderByCount(bool orderbycount) { m_orderByCount = orderbycount;}
- bool getOrderByCount() { return m_orderByCount; }
- string GetContent(mgmySql &db,unsigned int level,vector < mgContentItem > &content) const;
- vector <const char*> Choices(unsigned int level, unsigned int *current) const;
-private:
- bool m_orderByCount;
- bool isCollectionOrder() const;
- keyvector Keys;
- void setKey ( const mgKeyTypes kt);
- void clean();
- unsigned int keycount(mgKeyTypes kt) const;
- bool UsedBefore(const mgKeyTypes kt,unsigned int level) const;
-};
-
-bool operator==(const mgOrder& a,const mgOrder&b); //! \brief compares only the order, not the current key values
-
-
-#endif
diff --git a/mg_sel_gd.c b/mg_sel_gd.c
new file mode 100644
index 0000000..483bf91
--- /dev/null
+++ b/mg_sel_gd.c
@@ -0,0 +1,354 @@
+/*!
+ * \file mg_sel_gd.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 <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <assert.h>
+
+
+#include "mg_sel_gd.h"
+# include "mg_db.h"
+#include "i18n.h"
+
+mgSelectionGd::mgSelectionGd(const mgSelection *s)
+{
+ InitFrom(s);
+}
+
+mgSelectionGd::mgSelectionGd(const bool fall_through)
+ : mgSelection(fall_through)
+{
+}
+
+void mgSelectionGd::InitSelection() {
+ mgSelection::InitSelection();
+ m_db = GenerateDB();
+}
+
+
+static bool iskeyGdGenre(mgKeyTypes kt)
+{
+ return kt>=keyGdGenre1 && kt <= keyGdGenres;
+}
+
+bool
+mgSelectionGd::DeduceKeyValue(mgKeyTypes new_kt,const mgSelection *s,
+ vector<mgListItem>& items)
+{
+ if (!s)
+ return false;
+ for (unsigned int i=0;i<s->ordersize();i++)
+ {
+ mgKeyTypes old_kt = s->getKeyType(i);
+ if (old_kt>new_kt
+ && iskeyGdGenre(old_kt)
+ && iskeyGdGenre(new_kt))
+ {
+ string selid=KeyMaps.id(new_kt,
+ KeyMaps.value(new_kt,s->getKeyItem(i)->id()));
+ items.push_back(mgListItem(
+ KeyMaps.value(new_kt,selid),selid));
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+mgSelectionGd::clean()
+{
+ mgSelection::clean();
+ keyvector::iterator i;
+ keyvector::iterator j;
+ bool collection_found = false;
+ bool collitem_found = false;
+ bool album_found = false;
+ bool tracknb_found = false;
+ bool title_found = false;
+ bool is_sufficiently_unique = false;
+ for (i = Keys.begin () ; i != Keys.end (); ++i)
+ {
+ mgKey* k = *i;
+ if (k->Type()==keyGdUnique)
+ {
+ truncate(i-Keys.begin());
+ break;
+ }
+ collection_found |= (k->Type()==keyGdCollection);
+ collitem_found |= (k->Type()==keyGdCollectionItem);
+ album_found |= (k->Type()==keyGdAlbum);
+ tracknb_found |= (k->Type()==keyGdTrack);
+ title_found |= (k->Type()==keyGdTitle);
+ is_sufficiently_unique = tracknb_found || (album_found && title_found)
+ || (collection_found && collitem_found);
+ if (is_sufficiently_unique)
+ {
+ truncate (i+1-Keys.begin());
+ break;
+ }
+ if (k->Type()==keyGdYear)
+ {
+ for (j = i+1 ; j != Keys.end(); ++j)
+ if ((*j)->Type() == keyGdDecade)
+ {
+ delete *j;
+ Keys.erase(j);
+ break;
+ }
+ }
+ }
+ if (!is_sufficiently_unique)
+ {
+ if (!album_found)
+ Keys.push_back(ktGenerate(keyGdAlbum));
+ if (!title_found)
+ Keys.push_back(ktGenerate(keyGdTitle));
+ }
+ Keys.push_back(ktGenerate(keyGdUnique)); // make sure we ALWAYS have a unique key
+}
+
+
+vector <const char *>
+mgSelectionGd::Choices(unsigned int level, unsigned int *current) const
+{
+ vector<const char*> result;
+ if (level>ordersize())
+ {
+ *current = 0;
+ return result;
+ }
+ for (unsigned int ki=(unsigned int)(ktLow());ki<=(unsigned int)(ktHigh());ki++)
+ {
+ mgKeyTypes kt = mgKeyTypes(ki);
+ if (kt == keyGdUnique)
+ continue;
+ if (kt==getKeyType(level))
+ {
+ *current = result.size();
+ result.push_back(ktName(kt));
+ continue;
+ }
+ if (UsedBefore(kt,level))
+ continue;
+ if (kt==keyGdDecade)
+ {
+ if (UsedBefore(keyGdYear,level)) continue;
+ }
+ if (kt==keyGdGenre1)
+ {
+ if (UsedBefore(keyGdGenre2,level)) continue;
+ if (UsedBefore(keyGdGenre3,level)) continue;
+ if (UsedBefore(keyGdGenres,level)) continue;
+ }
+ if (kt==keyGdGenre2)
+ {
+ if (UsedBefore(keyGdGenre3,level)) continue;
+ if (UsedBefore(keyGdGenres,level)) continue;
+ }
+ if (kt==keyGdGenre3)
+ {
+ if (UsedBefore(keyGdGenres,level)) continue;
+ }
+ if (kt==keyGdFolder1)
+ {
+ if (UsedBefore(keyGdFolder2,level)) continue;
+ if (UsedBefore(keyGdFolder3,level)) continue;
+ if (UsedBefore(keyGdFolder4,level)) continue;
+ }
+ if (kt==keyGdFolder2)
+ {
+ if (UsedBefore(keyGdFolder3,level)) continue;
+ if (UsedBefore(keyGdFolder4,level)) continue;
+ }
+ if (kt==keyGdFolder3)
+ {
+ if (UsedBefore(keyGdFolder4,level)) continue;
+ }
+ if (kt==keyGdCollectionItem)
+ {
+ if (!UsedBefore(keyGdCollection,level)) continue;
+ }
+ if (kt==keyGdCollection)
+ result.push_back(ktName(kt));
+ else if (keycount(kt)>1)
+ result.push_back(ktName(kt));
+ }
+ return result;
+}
+
+bool
+mgSelectionGd::NeedKey(unsigned int i) const
+{
+ assert(m_level<ordersize());
+ mgKey *k = Keys[i];
+ mgKeyTypes kt = k->Type();
+ for (unsigned int j=i+1;j<=m_level;j++)
+ {
+ mgKey *kn = Keys[j];
+ if (kn && !kn->id().empty())
+ {
+ mgKeyTypes knt = kn->Type();
+ if (knt==keyGdUnique)
+ return false;
+ if (iskeyGdGenre(kt) && iskeyGdGenre(knt) && knt>kt)
+ return false;
+ if (kt==keyGdDecade && knt==keyGdYear)
+ return false;
+ }
+ }
+ return true;
+}
+
+mgParts
+mgSelectionGd::SelParts(bool distinct, bool deepsort) const
+{
+ if (distinct && !deepsort && m_level==0 && isCollectionOrder())
+ {
+ mgParts result;
+ result.orderByCount = m_orderByCount;
+ // sql command contributed by jarny
+ result.special_statement = string("SELECT playlist.title,playlist.id, "
+ "COUNT(*) * CASE WHEN playlistitem.playlist IS NULL THEN 0 ELSE 1 END FROM playlist "
+ "LEFT JOIN playlistitem ON playlist.id = playlistitem.playlist "
+ "GROUP BY playlist.title,playlistitem.playlist,playlist.id");
+ return result;
+ }
+ else
+ return mgSelection::SelParts(distinct, deepsort);
+}
+
+mgKeyTypes
+mgSelectionGd::ktLow() const
+{
+ return mgGdKeyTypesLow;
+}
+
+mgKeyTypes
+mgSelectionGd::ktHigh() const
+{
+ return mgGdKeyTypesHigh;
+}
+
+const char * const
+mgSelectionGd::ktName(const mgKeyTypes kt) const
+{
+ const char * result = "";
+ switch (kt)
+ {
+ case keyGdGenres: result = "Genre";break;
+ case keyGdGenre1: result = "Genre1";break;
+ case keyGdGenre2: result = "Genre2";break;
+ case keyGdGenre3: result = "Genre3";break;
+ case keyGdFolder1: result = "Folder1";break;
+ case keyGdFolder2: result = "Folder2";break;
+ case keyGdFolder3: result = "Folder3";break;
+ case keyGdFolder4: result = "Folder4";break;
+ case keyGdArtist: result = "Artist";break;
+ case keyGdArtistABC: result = "ArtistABC";break;
+ case keyGdTitle: result = "Title";break;
+ case keyGdTitleABC: result = "TitleABC";break;
+ case keyGdTrack: result = "Track";break;
+ case keyGdDecade: result = "Decade";break;
+ case keyGdAlbum: result = "Album";break;
+ case keyGdCreated: result = "Created";break;
+ case keyGdModified: result = "Modified";break;
+ case keyGdCollection: result = "Collection";break;
+ case keyGdCollectionItem: result = "Collection item";break;
+ case keyGdLanguage: result = "Language";break;
+ case keyGdRating: result = "Rating";break;
+ case keyGdYear: result = "Year";break;
+ case keyGdUnique: result = "ID";break;
+ default: result="not implemented";break;
+ }
+ return tr(result);
+}
+
+void
+mgSelectionGd::MakeCollection()
+{
+ clear();
+ setKey(keyGdCollection);
+ setKey(keyGdCollectionItem);
+ setKey(keyGdUnique);
+}
+
+bool
+mgSelectionGd::isCollectionOrder() const
+{
+ return (ordersize()==3
+ && (Keys[0]->Type()==keyGdCollection)
+ && (Keys[1]->Type()==keyGdCollectionItem));
+}
+
+bool
+mgSelectionGd::isLanguagelist() const
+{
+ return (getKeyType(0) == keyGdLanguage);
+}
+
+bool
+mgSelectionGd::isCollectionlist () const
+{
+ if (ordersize()==0) return false;
+ return (getKeyType(0) == keyGdCollection && orderlevel() == 0);
+}
+
+bool
+mgSelectionGd::inCollection(const string Name) const
+{
+ bool result = false;
+ for (unsigned int idx = 0 ; idx <= orderlevel(); idx++)
+ {
+ if (idx==ordersize()) break;
+ if (getKeyType(idx) == keyGdCollection)
+ if (!Name.empty() && getKeyItem(idx)->value() != Name)
+ break;
+ if (getKeyType(idx) == keyGdCollectionItem)
+ {
+ result = true;
+ break;
+ }
+ }
+ return result;
+}
+
+bool
+mgSelectionGd::InitDefaultOrder(unsigned int i)
+{
+ clear();
+ switch (i) {
+ case 1:
+ setKey(keyGdArtist);
+ setKey(keyGdAlbum);
+ setKey(keyGdTrack);
+ break;
+ case 2:
+ setKey(keyGdAlbum);
+ setKey(keyGdTrack);
+ break;
+ case 3:
+ setKey(keyGdGenres);
+ setKey(keyGdArtist);
+ setKey(keyGdAlbum);
+ setKey(keyGdTrack);
+ break;
+ case 4:
+ setKey(keyGdArtist);
+ setKey(keyGdTrack);
+ break;
+ default:
+ return false;
+ }
+ setKey(keyGdUnique);
+ return true;
+}
diff --git a/mg_sel_gd.h b/mg_sel_gd.h
new file mode 100644
index 0000000..f625b1f
--- /dev/null
+++ b/mg_sel_gd.h
@@ -0,0 +1,48 @@
+/*!
+ * \file mg_sel_gd.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_SEL_GD_H
+#define _MG_SEL_GD_H
+
+#include "mg_selection.h"
+
+using namespace std;
+
+class mgSelectionGd : public mgSelection
+{
+ public:
+ mgSelectionGd(const mgSelection *s);
+ mgSelectionGd(const bool fall_through = false);
+ void MakeCollection();
+ vector <const char*> Choices(unsigned int level, unsigned int *current) const;
+ bool NeedKey(unsigned int i) const;
+ mgParts SelParts(bool distinct, bool deepsort) const;
+ bool inCollection(const string Name="") const;
+ bool isLanguagelist() const;
+ bool isCollectionlist() const;
+ bool InitDefaultOrder(unsigned int i=0);
+ bool keyIsUnique(mgKeyTypes kt) const { return kt==keyGdUnique;}
+
+
+ protected:
+ bool DeduceKeyValue(mgKeyTypes new_kt,const mgSelection *s,
+ vector<mgListItem>& items);
+ void InitSelection ();
+ const char * const ktName(const mgKeyTypes kt) const;
+ mgKeyTypes ktLow() const;
+ mgKeyTypes ktHigh() const;
+ bool isCollectionOrder() const;
+
+ private:
+ void clean();
+};
+
+#endif
diff --git a/mg_selection.c b/mg_selection.c
index f0016e9..8e30945 100644
--- a/mg_selection.c
+++ b/mg_selection.c
@@ -13,27 +13,18 @@
#include <sys/time.h>
#include <sys/stat.h>
#include <stdio.h>
-#include <fts.h>
#include <assert.h>
-#include "i18n.h"
#include "mg_selection.h"
-#include "vdr_setup.h"
+#include "mg_setup.h"
#include "mg_tools.h"
#include "mg_thread_sync.h"
-#include <mpegfile.h>
-#include <flacfile.h>
-#include <id3v2tag.h>
-#include <fileref.h>
-
#if VDRVERSNUM >= 10307
#include <interface.h>
#include <skins.h>
#endif
-//! \brief adds string n to string s, using a comma to separate them
-static string comma (string & s, string n);
/*! \brief returns a random integer within some range
*/
@@ -45,17 +36,101 @@ randrange (const unsigned int high)
return result;
}
+bool compvalue (const mgListItem* x, const mgListItem* y)
+{
+ return x->value()<y->value();
+}
-static string
-comma (string & s, string n)
+bool compid (const mgListItem* x, const mgListItem* y)
{
- return addsep (s, ",", n);
+ return x->id()<y->id();
}
+bool compidnum (const mgListItem* x, const mgListItem* y)
+{
+ return atol(x->id().c_str())<atol(y->id().c_str());
+}
+
+bool compcount (const mgListItem* x, const mgListItem* y)
+{
+ return x->count()>y->count();
+}
+
+bool compitem (const mgItem* x, const mgItem* y)
+{
+ const mgSelection *s = x->getSelection();
+ string xval="";
+ string yval="";
+ int xnum;
+ int ynum;
+ for (unsigned int idx=s->orderlevel();idx<s->ordersize();idx++)
+ {
+ mgSortBy sb = s->getKeySortBy(idx);
+ mgListItem *xitem = x->getKeyItem(s->getKeyType (idx));
+ mgListItem *yitem = y->getKeyItem(s->getKeyType (idx));
+ switch (sb) {
+ case mgSortNone:
+ xval="";
+ yval="";
+ break;
+ case mgSortById:
+ xval=xitem->id();
+ yval=yitem->id();
+ break;
+ case mgSortByIdNum:
+ xnum=atol(xitem->id().c_str());
+ ynum=atol(yitem->id().c_str());
+ if (xnum<ynum) {
+ xval="0";
+ yval="1";
+ } else if (xnum>ynum) {
+ xval="1";
+ yval="0";
+ } else {
+ xval="0";
+ yval="0";
+ }
+ break;
+ case mgSortByValue:
+ xval=xitem->value();
+ yval=yitem->value();
+ break;
+ }
+ if (xval!=yval) break;
+ }
+ return xval<yval;
+}
+
+void
+mgSelection::mgListItems::sort(bool bycount,mgSortBy SortBy)
+{
+ if (SortBy==mgSortNone)
+ {
+ return;
+ }
+ if (bycount)
+ {
+ std::sort(m_items.begin(),m_items.end(),compcount);
+ }
+ else if (SortBy==mgSortById)
+ {
+ std::sort(m_items.begin(),m_items.end(),compid);
+ }
+ else if (SortBy==mgSortByIdNum)
+ {
+ std::sort(m_items.begin(),m_items.end(),compidnum);
+ }
+ else
+ {
+ std::sort(m_items.begin(),m_items.end(),compvalue);
+ }
+}
void
mgSelection::mgListItems::clear()
{
+ for (unsigned int i=0;i<m_items.size();i++)
+ delete m_items[i];
m_items.clear();
}
@@ -65,7 +140,7 @@ mgSelection::mgListItems::operator==(const mgListItems&x) const
bool result = m_items.size()==x.m_items.size();
if (result)
for (unsigned int i=0;i<size();i++)
- result &= m_items[i]==x.m_items[i];
+ result &= *(m_items[i])==*(x.m_items[i]);
return result;
}
@@ -78,13 +153,13 @@ mgSelection::mgListItems::size() const
return m_items.size();
}
-mgListItem&
+mgListItem*
mgSelection::mgListItems::operator[](unsigned int idx)
{
if (!m_sel)
mgError("mgListItems: m_sel is 0");
m_sel->refreshValues();
- if (idx>=size()) return zeroitem;
+ assert(idx<m_items.size());
return m_items[idx];
}
@@ -94,6 +169,24 @@ mgSelection::mgListItems::setOwner(mgSelection* sel)
m_sel = sel;
}
+int
+mgSelection::mgListItems::search (const string v) const
+{
+ if (!m_sel)
+ mgError("mgListItems::index(%s): m_sel is 0",v.c_str());
+ unsigned int itemsize = m_items.size();
+ const char *cstr = v.c_str();
+ unsigned int clen = strlen(cstr);
+ int result = -1;
+ for (unsigned int idx = 0 ; idx < itemsize; idx++)
+ if( strncasecmp( m_items[idx]->value().c_str(), cstr, clen )>=0)
+ {
+ result = idx;
+ break;
+ }
+ return result;
+}
+
unsigned int
mgSelection::mgListItems::valindex (const string v) const
{
@@ -103,7 +196,7 @@ mgSelection::mgListItems::valindex (const string v) const
unsigned int
mgSelection::mgListItems::idindex (const string i) const
{
- return index(i,true);
+ return index(i,false);
}
unsigned int
@@ -116,18 +209,25 @@ mgSelection::mgListItems::index (const string s,bool val,bool second_try) const
{
if (val)
{
- if (m_items[i].value() == s)
+ if (m_items[i]->value() == s)
return i;
}
else
{
- if (m_items[i].id() == s)
+ if (m_items[i]->id() == s)
return i;
}
}
// nochmal mit neuen Werten:
if (second_try) {
- mgDebug(2,"index: Gibt es nicht:%s",s.c_str());
+ mgDebug(5,"index: Gibt es nicht:%s",s.c_str());
+ mgDebug(5,"index: wir haben z.B.");
+ if (size()>0) mgDebug(5,"%s/%s",m_items[0]->value().c_str(),m_items[0]->id().c_str());
+ if (size()>1) mgDebug(5,"%s/%s",m_items[1]->value().c_str(),m_items[1]->id().c_str());
+ if (size()>2) mgDebug(5,"%s/%s",m_items[2]->value().c_str(),m_items[2]->id().c_str());
+ if (size()>3) mgDebug(5,"%s/%s",m_items[3]->value().c_str(),m_items[3]->id().c_str());
+ if (size()>4) mgDebug(5,"%s/%s",m_items[4]->value().c_str(),m_items[4]->id().c_str());
+ if (size()>5) mgDebug(5,"%s/%s",m_items[5]->value().c_str(),m_items[5]->id().c_str());
return 0;
}
else
@@ -137,6 +237,24 @@ mgSelection::mgListItems::index (const string s,bool val,bool second_try) const
}
}
+bool
+mgSelection::inItem() const
+{
+ return m_level==ordersize()-1;
+}
+
+bool
+mgSelection::inItems() const
+{
+ return m_level==ordersize()-2;
+}
+
+void
+mgSelection::setOrderByCount(bool orderbycount)
+{
+ m_orderByCount = orderbycount;
+}
+
void
mgSelection::clearCache() const
{
@@ -147,28 +265,47 @@ mgSelection::clearCache() const
string
mgSelection::getCurrentValue()
{
- return listitems[gotoPosition()].value();
+ gotoPosition();
+ return getValue(m_position);
}
-mgListItem&
+string
+mgSelection::getValue(unsigned int idx) const
+{
+ if (idx>=listitems.size())
+ return "";
+ else
+ return listitems[idx]->value();
+}
+
+mgListItem*
mgSelection::getKeyItem(const unsigned int level) const
{
- return order.getKeyItem(level);
+ assert(level<Keys.size());
+ return Keys[level]->get();
}
mgKeyTypes
mgSelection::getKeyType (const unsigned int level) const
{
- return order.getKeyType(level);
+ assert(level<Keys.size());
+ return Keys[level]->Type();
+}
+
+mgSortBy
+mgSelection::getKeySortBy (const unsigned int level) const
+{
+ assert(level<Keys.size());
+ return Keys[level]->SortBy();
}
-mgContentItem *
+mgItem *
mgSelection::getItem (unsigned int position)
{
- if (position >= getNumItems ())
+ if (position >= items().size())
return 0;
- return &(m_items[position]);
+ return m_items[position];
}
@@ -188,17 +325,17 @@ mgSelection::setShuffleMode (mgSelection::ShuffleMode mode)
void
mgSelection::Shuffle() const
{
- unsigned int numitems = getNumItems();
+ unsigned int numitems = items().size();
if (numitems==0) return;
switch (m_shuffle_mode)
{
case SM_NONE:
{
- long id = m_items[getItemPosition()].getItemid ();
+ long id = m_items[getItemPosition()]->getItemid ();
m_current_tracks = ""; // force a reload
- numitems = getNumItems(); // getNumItems also reloads
+ numitems = items().size(); // also reloads
for (unsigned int i = 0; i < numitems; i++)
- if (m_items[i].getItemid () == id)
+ if (m_items[i]->getItemid () == id)
{
m_items_position = i;
break;
@@ -209,7 +346,7 @@ mgSelection::Shuffle() const
case SM_NORMAL:
{
// play all, beginning with current item:
- mgContentItem tmp = m_items[getItemPosition()];
+ mgItem* tmp = m_items[getItemPosition()];
m_items[getItemPosition()]=m_items[0];
m_items[0]=tmp;
m_items_position = 0;
@@ -223,7 +360,7 @@ mgSelection::Shuffle() const
}
} break;
/*
- * das kapiere ich nicht... (wolfgang)
+
- Party mode (see iTunes)
- initialization
- find 15 titles according to the scheme below
@@ -251,136 +388,101 @@ mgSelection::LoopMode mgSelection::toggleLoopMode ()
unsigned int
mgSelection::AddToCollection (const string Name)
{
- if (!m_db.Connected()) return 0;
- CreateCollection(Name);
- string listid = m_db.sql_string (m_db.get_col0
- ("SELECT id FROM playlist WHERE title=" + m_db.sql_string (Name)));
- unsigned int numitems = getNumItems ();
- if (numitems==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(numitems)+","+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()) - numitems + 1;
-
- // replace the place holder trackid by the correct value:
- m_db.exec_sql("UPDATE playlistitem SET trackid="+ltos(m_items[numitems-1].getItemid())+
- " WHERE playlist="+listid+" AND trackid="+trackid);
-
- // insert all other items:
- const char *sql_prefix = "INSERT INTO playlistitem VALUES ";
- sql = "";
- for (unsigned int i = 0; i < numitems-1; i++)
- {
- string item = "(" + listid + "," + ltos (first + i) + "," +
- ltos (m_items[i].getItemid ()) + ")";
- comma(sql, item);
- if ((i%100)==99)
- {
- m_db.exec_sql (sql_prefix+sql);
- sql = "";
- }
- }
- if (!sql.empty()) m_db.exec_sql (sql_prefix+sql);
- if (inCollection(Name)) clearCache ();
- return numitems;
+ int result = m_db->AddToCollection(Name,items(),0);
+ if (result>0)
+ if (inCollection(Name)) clearCache ();
+ return result;
}
unsigned int
mgSelection::RemoveFromCollection (const string Name)
{
- if (!m_db.Connected()) return 0;
- mgParts p = order.Parts(m_db,m_level,false);
- string sql = p.sql_delete_from_collection(KeyMaps.id(keyCollection,Name));
- m_db.exec_sql (sql);
- unsigned int removed = m_db.affected_rows ();
- if (inCollection(Name)) clearCache ();
- return removed;
+ mgParts p = SelParts(false,false);
+ unsigned int result = m_db->RemoveFromCollection(Name,items(),&p);
+ if (result>0)
+ if (inCollection(Name)) clearCache ();
+ return result;
}
bool mgSelection::DeleteCollection (const string Name)
{
- if (!m_db.Connected()) return false;
- ClearCollection(Name);
- m_db.exec_sql ("DELETE FROM playlist WHERE title=" + m_db.sql_string (Name));
- if (isCollectionlist()) clearCache ();
- return (m_db.affected_rows () == 1);
+ bool result = m_db->DeleteCollection (Name);
+ if (result)
+ if (isCollectionlist()) clearCache ();
+ return result;
}
void mgSelection::ClearCollection (const string Name)
{
- if (!m_db.Connected()) return;
- string listid = KeyMaps.id(keyCollection,Name);
- m_db.exec_sql ("DELETE FROM playlistitem WHERE playlist="+m_db.sql_string(listid));
+ m_db->ClearCollection (Name);
if (inCollection(Name)) clearCache ();
}
bool mgSelection::CreateCollection(const string Name)
{
- 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;
- m_db.exec_sql ("INSERT playlist VALUES(" + name + ",'VDR',NULL,NULL,NULL)");
- if (isCollectionlist()) clearCache ();
- return true;
+ bool result = m_db->CreateCollection (Name);
+ if (result)
+ if (isCollectionlist()) clearCache ();
+ return result;
}
string mgSelection::exportM3U ()
{
-
+ enter();
// open a file for writing
string fn = "/tmp/" + ListFilename () + ".m3u";
FILE * listfile = fopen (fn.c_str (), "w");
if (!listfile)
return "";
fprintf (listfile, "#EXTM3U\n");
- unsigned int numitems = getNumItems ();
+ unsigned int numitems = items().size();
for (unsigned i = 0; i < numitems; i++)
{
- mgContentItem& t = m_items[i];
- fprintf (listfile, "#EXTINF:%d,%s\n", t.getDuration (),
- t.getTitle ().c_str ());
- fprintf (listfile, "#MUGGLE:%ld\n", t.getItemid());
- fprintf (listfile, "%s\n", t.getSourceFile (false).c_str ());
+ mgItem* t = m_items[i];
+ fprintf (listfile, "#EXTINF:%d,%s\n", t->getDuration (),
+ t->getTitle ().c_str ());
+ fprintf (listfile, "#MUGGLE:%ld\n", t->getItemid());
+ fprintf (listfile, "%s\n", t->getSourceFile (false).c_str ());
}
fclose (listfile);
+ leave();
return fn;
}
bool
mgSelection::empty()
{
- if (m_level>= order.size ()-1)
- return ( getNumItems () == 0);
- else
- return ( listitems.size () == 0);
+ return ( listitems.size () == 0);
}
void
mgSelection::setPosition (unsigned int position)
{
- if (m_level == order.size())
- mgError("setPosition:m_level==order.size()");
+ assert(m_level<ordersize());
m_position = position;
}
void
+mgSelection::setPosition(string value)
+{
+ setPosition (listitems.valindex (value));
+}
+
+unsigned int
+mgSelection::searchPosition(string search)
+{
+ int res = listitems.search (search);
+ if (res>=0)
+ setPosition (res);
+ return gotoPosition();
+}
+
+void
mgSelection::GotoItemPosition (unsigned int position) const
{
m_items_position = position;
@@ -390,24 +492,27 @@ mgSelection::GotoItemPosition (unsigned int position) const
unsigned int
mgSelection::getPosition () const
{
- if (m_level == order.size())
- mgError("getPosition:m_level==order.size()");
+ assert(m_level<ordersize());
return m_position;
}
unsigned int
mgSelection::gotoPosition ()
{
- if (m_level == order.size())
- mgError("gotoPosition:m_level==order.size()");
+ assert(m_level<ordersize());
unsigned int itemsize = listitems.size();
if (itemsize==0)
m_position = 0;
else if (m_position >= itemsize)
m_position = itemsize -1;
+ if (itemsize==0)
+ Key(m_level)->set (0);
+ else
+ Key(m_level)->set (listitems[m_position]);
return m_position;
}
+
unsigned int
mgSelection::getItemPosition() const
{
@@ -422,7 +527,7 @@ mgSelection::getItemPosition() const
unsigned int
mgSelection::gotoItemPosition()
{
- unsigned int numitems = getNumItems ();
+ unsigned int numitems = items().size();
if (numitems == 0)
{
m_items_position = 0;
@@ -435,7 +540,7 @@ mgSelection::gotoItemPosition()
bool mgSelection::skipItems (int steps) const
{
- unsigned int numitems = getNumItems();
+ unsigned int numitems = items().size();
if (numitems == 0)
{
m_items_position=0;
@@ -462,8 +567,9 @@ bool mgSelection::skipItems (int steps) const
m_items_position = new_pos;
while (true)
{
- if (m_items[m_items_position].Valid())
+ if (m_items[m_items_position]->Valid())
break;
+ delete m_items[m_items_position];
m_items.erase(m_items.begin()+m_items_position);
if (m_items.size()==0)
{
@@ -482,9 +588,9 @@ unsigned long
mgSelection::getLength ()
{
unsigned long result = 0;
- unsigned int numitems = getNumItems ();
+ unsigned int numitems = items().size();
for (unsigned int i = 0; i < numitems; i++)
- result += m_items[i].getDuration ();
+ result += m_items[i]->getDuration ();
return result;
}
@@ -495,7 +601,7 @@ mgSelection::getCompletedLength () const
unsigned long result = 0;
items (); // make sure they are loaded
for (unsigned int i = 0; i < getItemPosition(); i++)
- result += m_items[i].getDuration ();
+ result += m_items[i]->getDuration ();
return result;
}
@@ -505,16 +611,14 @@ string mgSelection::getListname () const
{
list<string> st;
for (unsigned int i = 0; i < m_level; i++)
- st.push_back(order.getKeyItem(i).value());
+ st.push_back(getKeyItem(i)->value());
st.unique();
string result="";
for (list < string >::iterator it = st.begin (); it != st.end (); ++it)
- {
addsep (result, ":", *it);
- }
if (result.empty ())
- if (order.size()>0)
- result = string(ktName(order.getKeyType(0)));
+ if (ordersize()>0)
+ result = string(ktName(getKeyType(0)));
return result;
}
@@ -539,23 +643,61 @@ string mgSelection::ListFilename ()
return res;
}
-const vector < mgContentItem > &
+mgParts
+mgSelection::SelParts(bool distinct, bool deepsort) const
+{
+ assert(m_level<ordersize());
+ mgKey *high = Keys[m_level];
+ mgListItem* highitem = 0;
+ if (high->Type()!=keyGdUnique)
+ {
+ highitem = high->get();
+ high->set(0);
+ }
+ mgParts result;
+ result.orderByCount = m_orderByCount;
+ for (unsigned int i=0;i<ordersize();i++)
+ {
+ if (!deepsort && i>m_level)
+ break;
+ if (NeedKey(i))
+ result += Keys[i]->Parts(m_db,distinct&&i==m_level);
+ }
+ if (highitem)
+ {
+ high->set(highitem);
+ delete highitem;
+ }
+ return result;
+}
+
+const vector < mgItem* > &
mgSelection::items () const
{
- if (!m_db.Connected()) return m_items;
- if (!m_current_tracks.empty()) return m_items;
- m_current_tracks=order.GetContent(m_db,m_level,m_items);
- if (m_shuffle_mode!=SM_NONE)
- Shuffle();
+ if (m_current_tracks.empty())
+ {
+ mgParts p = SelParts(false,true);
+ m_current_tracks = m_db->LoadItemsInto(p,m_items);
+ if (m_shuffle_mode==SM_NONE)
+ {
+ if (!inCollection(""))
+ {
+ for (unsigned int i=0;i<m_items.size();i++)
+ m_items[i]->setSelection(this);
+ std::sort(m_items.begin(),m_items.end(),compitem);
+ }
+ }
+ else
+ Shuffle();
+ }
return m_items;
}
void mgSelection::InitSelection() {
- m_level = 0;
+ m_active = false;
m_position = 0;
m_items_position = 0;
- m_itemid = -1;
if (the_setup.InitShuffleMode)
m_shuffle_mode = SM_NORMAL;
else
@@ -566,6 +708,8 @@ void mgSelection::InitSelection() {
m_loop_mode = LM_NONE;
clearCache();
listitems.setOwner(this);
+ clear();
+ m_orderByCount = false;
}
@@ -585,399 +729,425 @@ mgSelection::mgSelection (const mgSelection* s)
InitFrom(s);
}
-mgSelection::mgSelection (mgValmap& nv)
+
+void mgSelection::DumpState(mgValmap& nv, const char *prefix) const
{
- InitFrom(nv);
+ nv.put(m_fall_through,"%s.FallThrough",prefix);
+ nv.put(m_orderByCount,"%s.OrderByCount",prefix);
+ for (unsigned int i=0;i<ordersize();i++)
+ {
+ nv.put(int(Key(i)->Type()),"%s.Keys.%d.Type",prefix,i);
+ if (i<=m_level)
+ nv.put(getKeyItem(i)->value(),
+ "%s.Keys.%u.Position",prefix,i);
+ }
}
-void
-mgSelection::setOrder(mgOrder* o)
+
+void mgSelection::ShowState(char *w) const
{
- if (o)
- {
- order = *o;
+ mgDebug(1,"ShowState:%s,m_level=%d",w,m_level);
+ for (unsigned int i=0;i<ordersize();i++)
+ {
+ mgDebug(1," %d:Type=%s,val=%s,id=%s",
+ i,ktName(Key(i)->Type()),
+ getKeyItem(i)->value().c_str(),
+ getKeyItem(i)->id().c_str());
}
- else
- mgWarning("mgSelection::setOrder(0)");
}
-
void
-mgSelection::InitFrom(mgValmap& nv)
+mgSelection::InitFrom(const char *prefix,mgValmap& nv)
{
- extern time_t createtime;
- if (m_db.ServerConnected() && !m_db.Connected()
- && (time(0)>createtime+10))
+ InitSelection();
+ clear();
+ m_fall_through = true;
+ setOrderByCount(nv.getbool("%s.OrderByCount",prefix));
+ for (unsigned int idx = 0 ; idx < 999 ; idx++)
{
- 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 items?")))
- {
- mgThreadSync *s = mgThreadSync::get_instance();
- if (s)
- {
- extern char *sync_args[];
- s->Sync(sync_args,(bool)the_setup.DeleteStaleReferences);
+ unsigned int type = nv.getuint("%s.Keys.%u.Type",prefix,idx);
+ if (type==0) break;
+ setKey(mgKeyTypes(type));
+ }
+ vector<mgListItem> items;
+ for (unsigned int idx = 0 ; idx < ordersize() ; idx++)
+ {
+ char b[100];
+ sprintf(b,"%s.Keys.%u.Position",prefix,idx);
+ if (!nv.count(b)) break;
+ string newval = nv.getstr(b);
+ items.push_back(mgListItem(newval,KeyMaps.id(Keys[idx]->Type(),newval),0));
+ }
+ if (ordersize() && Keys[ordersize()-1]->Type()!=keyGdUnique)
+ setKey(keyGdUnique);
+ InitOrder(items);
+}
+
+
+void
+mgSelection::CopyKeyValues(mgSelection* s)
+{
+ if (!s)
+ mgError("mgSelection::CopyKeyValues(0)");
+ if (s==this)
+ return;
+ mgItem *o=0;
+ s->enter();
+ if (s->items().size()==1)
+ o = s->getItem(0)->Clone();
+ SetLevel(0);
+ vector<mgListItem> items;
+ for (unsigned int idx = 0; idx < ordersize(); idx++)
+ {
+ bool found = false;
+ mgKeyTypes new_kt = getKeyType(idx);
+ if (o && o->getItemid()>=0)
+ {
+ items.push_back(o->getKeyItem(new_kt));
+ found = true;
+ continue;
+ }
+ if (s) for (unsigned int i=0;i<s->ordersize();i++)
+ {
+ mgKeyTypes old_kt = s->getKeyType(i);
+ if (old_kt==new_kt && s->getKeyItem(i))
+ {
+ items.push_back(s->getKeyItem(i));
+ found = true;
+ break;
}
- }
- }
- free(b);
+ }
+ if (found)
+ continue;
+ if (!DeduceKeyValue(new_kt,s,items))
+ break;
}
- InitSelection();
- m_fall_through = nv.getbool("FallThrough");
- while (m_level < nv.getuint("Level"))
+ delete o;
+ assert(items.size()<=ordersize());
+ InitOrder(items);
+}
+
+void
+mgSelection::InitOrder(vector<mgListItem>& items)
+{
+ mgDebug(5,"InitOrder:");
+ for (unsigned int idx = 0; idx < items.size(); idx++)
+ mgDebug(5,"%d:%s/%s",idx,items[idx].value().c_str(),items[idx].id().c_str());
+ if (ordersize()==0)
+ return;
+ for (unsigned int idx = 0; idx < ordersize(); idx++)
+ Key(idx)->set(0);
+ for (unsigned int idx = 0; idx < items.size(); idx++)
+ Key(idx)->set (&items[idx]);
+ m_active = false;
+}
+
+void
+mgSelection::Activate()
+{
+ assert(ordersize());
+ if (m_level)
+ assert(m_level<ordersize());
+ if (m_active)
+ return;
+ m_active = true;
+ m_level = 0;
+ for (unsigned int lev = 0; lev < ordersize(); lev++)
{
- char *idx;
- asprintf(&idx,"order.Keys.%u.Position",m_level);
- string newval = nv.getstr(idx);
- free(idx);
- if (!enter (newval))
- if (!select (newval)) break;
+ if (!getKeyItem(lev))
+ break;
+ m_level = lev;
}
- assert(m_level<=order.size());
- m_itemid = nv.getlong("ItemId");
- if (m_level==order.size())
- m_items_position = nv.getlong("ItemPosition");
+ mgListItem* item = getKeyItem(m_level);
+ if (item)
+ setPosition(item->value());
else
- setPosition(nv.getstr("Position"));
+ setPosition(0);
+ if (inItem())
+ DecLevel();
}
-
mgSelection::~mgSelection ()
{
+ m_level=0;
+ truncate(0);
+ delete m_db;
}
void mgSelection::InitFrom(const mgSelection* s)
{
InitSelection();
+ if (!s)
+ return;
+ for (unsigned int i = 0; i < s->ordersize();i++)
+ {
+ mgKey *k = ktGenerate(s->getKeyType(i));
+ k->set(s->getKeyItem(i));
+ Keys.push_back(k);
+ }
+ m_active = s->m_active;
+ SetLevel(s->m_level);
+ if (m_level)
+ assert(m_level<ordersize());
m_fall_through = s->m_fall_through;
- order = s->order;
- m_level = s->m_level;
+ m_orderByCount = s->m_orderByCount;
m_position = s->m_position;
- m_itemid = s->m_itemid;
m_items_position = s->m_items_position;
setShuffleMode (s->getShuffleMode ());
setLoopMode (s->getLoopMode ());
}
-unsigned int
-mgSelection::valcount (string value)
-{
- return listitems[listitems.valindex(value)].count();
-}
void
mgSelection::refreshValues () const
{
assert(this);
- if (!m_db.Connected())
- return;
- if (m_current_values.empty())
- {
- mgParts p = order.Parts(m_db,m_level);
- m_current_values = p.sql_select();
- listitems.clear ();
- MYSQL_RES *rows = m_db.exec_sql (m_current_values);
- if (rows)
- {
- unsigned int num_fields = mysql_num_fields(rows);
- MYSQL_ROW row;
- while ((row = mysql_fetch_row (rows)) != 0)
- {
- if (!row[0]) continue;
- string r0 = row[0];
- if (!strcmp(row[0],"NULL")) // there is a genre NULL!
- continue;
- mgListItem n;
- if (num_fields==3)
- {
- if (!row[1]) continue;
- n.set(row[0],row[1],atol(row[2]));
- }
- else
- n.set(value(order.Key(m_level),r0),r0,atol(row[1]));
- listitems.push_back(n);
- }
- mysql_free_result (rows);
- }
- }
+ assert(m_db);
+ if (!m_current_values.empty())
+ return;
+ mgParts p = SelParts(true,false);
+ m_current_values = m_db->LoadValuesInto(
+ p,getKeyType(m_level),listitems.items(),m_level<ordersize()-2);
+ if (!inCollection(""))
+ listitems.sort(m_orderByCount,Keys[m_level]->SortBy());
}
-unsigned int
-mgSelection::count () const
+
+void
+mgSelection::DecLevel()
{
- return listitems.size ();
+ m_level--;
+ clearCache();
}
+void
+mgSelection::IncLevel()
+{
+ m_level++;
+ clearCache();
+}
+
+void
+mgSelection::SetLevel(unsigned int level)
+{
+ m_level=level;
+ clearCache();
+}
bool mgSelection::enter (unsigned int position)
{
- if (order.empty())
- mgWarning("mgSelection::enter(%u): order is empty", position);
- if (empty())
- return false;
- if (m_level == order.size ())
+ assert(!Keys.empty());
+ if (inItem())
return false;
- setPosition (position);
- position = gotoPosition(); // reload adjusted position
- if (listitems.size()==0)
+ if (empty())
+ refreshValues();
+ if (empty())
return false;
- mgListItem item = listitems[position];
+ mgDebug(5,"%X:level %d:enter(%d)",this,m_level,position);
+ if (inCollection())
+ {
+ mgListItem *item=Key(m_level)->get();
+ IncLevel();
+ Key(m_level)->set(item);
+ setPosition(0);
+ gotoPosition();
+ return true;
+ }
mgListItems prev;
- if (m_fall_through && listitems.size()<10)
- prev=listitems;
+ if (m_level<ordersize()-2 && m_fall_through && listitems.size()<100)
+ prev=listitems;
while (1)
{
- if (m_level >= order.size () - 1)
- return false;
- order[m_level++]->set (item);
- clearCache();
- m_position = 0;
+ setPosition(position);
+ position = gotoPosition(); // reload adjusted position
+ Key(m_level)->set (listitems[position]);
+ mgDebug(5,"enter:level=%d,set to %s",m_level,getCurrentValue().c_str());
+ IncLevel();
refreshValues();
- if (count()==0)
- break;
- item=listitems[0];
+ position = 0;
+ if (empty())
+ break;
if (!m_fall_through)
break;
- if (m_level==order.size()-1)
+ if (m_level>=ordersize()-2)
break;
- if (count () > 1 && !(prev==listitems))
+ if (listitems.size () > 1 && !(prev==listitems))
break;
}
+ setPosition(position);
+ position = gotoPosition();
+ mgDebug(5,"enter exits:level=%d,set to %s",m_level,getCurrentValue().c_str());
return true;
}
-
-bool mgSelection::select (unsigned int position)
-{
- if (m_level == order.size () - 1)
- {
- if (getNumItems () <= position)
- return false;
- order[m_level]->set (listitems[position]);
- m_level++;
- m_itemid = m_items[position].getItemid ();
-
- clearCache();
- return true;
- }
- else
- return enter (position);
-}
-
bool
mgSelection::leave ()
{
- string prevvalue;
- if (order.empty())
- {
- mgWarning("mgSelection::leave(): order is empty");
- return false;
- }
- if (m_level == order.size ())
- {
- m_level--;
- prevvalue=order.getKeyItem(m_level).value();
- order[m_level]->set(zeroitem);
- m_itemid = -1;
- clearCache();
- setPosition(prevvalue);
- return true;
- }
+ unsigned int position=m_position;
+ assert(!Keys.empty());
mgListItems prev;
- if (m_fall_through && listitems.size()<10)
- prev=listitems;
+ if (m_level>1 && m_fall_through && listitems.size()<100)
+ prev=listitems;
while (1)
{
- if (m_level < 1)
+ setPosition(position);
+ position = gotoPosition(); // reload adjusted position
+ Key(m_level)->set(0);
+ if (m_level==0)
return false;
- if (m_level<order.size()) order[m_level]->set (zeroitem);
- m_level--;
- prevvalue=order.getKeyItem(m_level).value();
- if (m_level<order.size()) order[m_level]->set (zeroitem);
- clearCache();
+ DecLevel();
+ refreshValues();
+ position = listitems.valindex (getKeyItem(m_level)->value());
if (!m_fall_through)
break;
- if (count () > 1 && !(prev==listitems))
+ if (m_level==0)
+ break;
+ if (listitems.size () > 1 && !(prev==listitems))
break;
}
- setPosition(prevvalue);
+ setPosition(position);
return true;
}
void
mgSelection::leave_all ()
{
- m_level=0;
- for (unsigned int i=0;i<order.size();i++)
- order[i]->set (zeroitem);
- clearCache();
+ SetLevel(0);
+ for (unsigned int i=0;i<ordersize();i++)
+ Key(i)->set (0);
}
-void
-mgSelection::selectfrom(mgOrder& oldorder,mgContentItem* o)
+
+void
+mgSelection::truncate(unsigned int i)
{
- leave_all();
- mgListItem selitem;
- assert(m_level==0);
- for (unsigned int idx = 0; idx < order.size(); idx++)
- {
- selitem = zeroitem;
- mgKeyTypes new_kt = getKeyType(idx);
- for (unsigned int i=0;i<oldorder.size();i++)
- {
- mgKeyTypes old_kt = oldorder.getKeyType(i);
- if (old_kt==new_kt)
- selitem = oldorder.getKeyItem(i);
- else if (old_kt>new_kt
- && iskeyGenre(old_kt)
- && iskeyGenre(new_kt))
- {
- string selid=KeyMaps.id(new_kt,KeyMaps.value(new_kt,oldorder.getKeyItem(i).id()));
- selitem=mgListItem (KeyMaps.value(new_kt,selid),selid);
- }
- if (selitem.valid()) break;
- }
- if (!selitem.valid() && o && o->getItemid()>=0)
- selitem = o->getKeyItem(new_kt);
- if (!selitem.valid())
- break;
- if (m_level<order.size()-1)
- {
- order[m_level++]->set (selitem);
- }
- else
- {
- setPosition(selitem.value());
- return;
- }
- }
- if (m_level>0)
+ while (ordersize()>i)
{
- m_level--;
- selitem = order.getKeyItem(m_level);
- order[m_level]->set(zeroitem);
- setPosition(selitem.value());
- order[m_level+1]->set(zeroitem);
+ delete Keys.back();
+ Keys.pop_back();
}
- assert(m_level<order.size());
}
-
-string
-mgSelection::value(mgKey* k, string idstr) const
-{
- return KeyMaps.value(k->Type(),idstr);
-}
-
-string
-mgSelection::value(mgKey* k) const
+void
+mgSelection::setKey (const mgKeyTypes kt)
{
- return value(k,k->id());
+ mgKey *newkey = ktGenerate(kt);
+ if (newkey)
+ Keys.push_back(newkey);
}
-
-string
-mgSelection::id(mgKey* k, string val) const
+void
+mgSelection::setKeys(vector<const char *>& kt)
{
- return KeyMaps.id(k->Type(),val);
+ clear();
+ for (unsigned int i=0;i<kt.size();i++)
+ {
+ setKey(ktValue(kt[i]));
+ }
+ clean();
}
-string
-mgSelection::id(mgKey* k) const
+void
+mgSelection::clear()
{
- return k->id();
+ m_level=0;
+ clearCache();
+ truncate(0);
}
-bool mgSelection::isLanguagelist() const
+void
+mgSelection::clean()
{
- return (order.getKeyType(0) == keyLanguage);
+ // remove double entries:
+ keyvector::iterator a;
+ keyvector::iterator b;
+ for (a = Keys.begin () ; a != Keys.end (); ++a)
+ {
+cleanagain:
+ for (b = a+1 ; b != Keys.end(); ++b)
+ if ((*a)->Type() == (*b)->Type())
+ {
+ delete *b;
+ Keys.erase(b);
+ goto cleanagain;
+ }
+ }
}
-bool mgSelection::isCollectionlist () const
+string
+mgSelection::Name()
{
- if (order.size()==0) return false;
- return (order.getKeyType(0) == keyCollection && m_level == 0);
+ string result="";
+ if (ordersize()>0)
+ for (unsigned int idx=0;idx<ordersize()-1;idx++)
+ {
+ if (!result.empty()) result += ":";
+ result += ktName(Keys[idx]->Type());
+ }
+ return result;
}
bool
-mgSelection::inCollection(const string Name) const
+mgSelection::SameOrder(const mgSelection* other)
{
- if (order.size()==0) return false;
- bool result = (order.getKeyType(0) == keyCollection && m_level == 1);
+ bool result = ordersize()==other->ordersize() && m_orderByCount == other->m_orderByCount;
if (result)
- if (order.getKeyType(1) != keyCollectionItem)
- mgError("inCollection: key[1] is not keyCollectionItem");
- if (!Name.empty())
- result &= (order.getKeyItem(0).value() == Name);
+ for (unsigned int i=0; i<ordersize();i++)
+ {
+ result &= Key(i)->Type()==other->Key(i)->Type();
+ if (!result) break;
+ }
return result;
}
-
-void mgSelection::DumpState(mgValmap& nv) const
+mgKey*
+mgSelection::Key(unsigned int idx) const
{
- nv.put("FallThrough",m_fall_through);
- nv.put("Level",int(m_level));
- for (unsigned int i=0;i<order.size();i++)
- {
- if (i<m_level) {
- char *n;
- asprintf(&n,"order.Keys.%u.Position",i);
- nv.put(n,getKeyItem(i).value());
- free(n);
- }
- }
- nv.put("ItemId",m_itemid);
- nv.put("Position",listitems[m_position].value());
- if (m_level>=order.size()-1)
- nv.put("ItemPosition",getItemPosition());
+ assert(idx<ordersize());
+ return Keys[idx];
}
-map <mgKeyTypes, string>
-mgSelection::UsedKeyValues()
+bool
+mgSelection::UsedBefore(const mgKeyTypes kt,unsigned int level) const
{
- map <mgKeyTypes, string> result;
- for (unsigned int idx = 0 ; idx < m_level ; idx++)
- {
- result[order.getKeyType(idx)] = order.getKeyItem(idx).value();
- }
- if (m_level < order.size()-1)
- {
- mgKeyTypes ch = order.getKeyType(m_level);
- result[ch] = getCurrentValue();
- }
- return result;
+ for (unsigned int lx = 0; lx < level; lx++)
+ if (getKeyType(lx)==kt)
+ return true;
+ return false;
}
static vector<int> keycounts;
unsigned int
-mgSelection::keycount(mgKeyTypes kt)
+mgSelection::keycount(mgKeyTypes kt) const
{
if (keycounts.size()==0)
{
- for (unsigned int ki=int(mgKeyTypesLow);ki<=int(mgKeyTypesHigh);ki++)
+ for (unsigned int ki=(unsigned int)(ktLow());ki<=(unsigned int)(ktHigh());ki++)
{
keycounts.push_back(-1);
}
}
- int& count = keycounts[int(kt-mgKeyTypesLow)];
- if (count==-1)
+ int& kcount = keycounts[int(kt-ktLow())];
+ if (kcount==-1)
{
mgKey* k = ktGenerate(kt);
if (k->Enabled(m_db))
- count = m_db.exec_count(k->Parts(m_db,true).sql_count());
+ kcount = m_db->exec_count(k->Parts(m_db,true).sql_count());
else
- count = 0;
+ kcount = 0;
delete k;
}
- return count;
+ return kcount;
}
+mgKeyTypes
+mgSelection::ktValue(const char * name) const
+{
+ for (int kt=int(ktLow());kt<=int(ktHigh());kt++)
+ if (!strcmp(name,ktName(mgKeyTypes(kt))))
+ return mgKeyTypes(kt);
+ mgError("ktValue(%s): unknown name",name);
+ return mgKeyTypes(0);
+}
diff --git a/mg_selection.h b/mg_selection.h
index 60018f6..ec4307b 100644
--- a/mg_selection.h
+++ b/mg_selection.h
@@ -20,10 +20,11 @@ using namespace std;
#include "mg_tools.h"
#include "mg_valmap.h"
-#include "mg_order.h"
-#include "mg_content.h"
+#include "mg_item.h"
+#include "mg_db.h"
typedef vector<string> strvector;
+typedef vector<mgKey*> keyvector;
/*!
* \brief the only interface to the database.
@@ -35,31 +36,7 @@ typedef vector<string> strvector;
class mgSelection
{
public:
- class mgListItems
- {
- public:
- mgListItems() { m_sel=0; }
- void setOwner(mgSelection* sel);
- mgListItem& operator[](unsigned int idx);
- string& id(unsigned int);
- unsigned int count(unsigned int);
- bool operator==(const mgListItems&x) const;
- size_t size() const;
- unsigned int valindex (const string v) const;
- unsigned int idindex (const string i) const;
- void clear();
- void push_back(mgListItem& item) { m_items.push_back(item); }
- private:
- unsigned int index (const string s,bool val,bool second_try=false) const;
- vector<mgListItem> m_items;
- mgSelection* m_sel;
- };
-
-//! \brief defines an order to be used
- void setOrder(mgOrder *o);
-
- mgOrder& getOrder() { return order; }
-
+ void CopyKeyValues(mgSelection* s);
/*! \brief define various ways to play music in random order
* \todo Party mode is not implemented, does same as SM_NORMAL
*/
@@ -78,6 +55,29 @@ class mgSelection
LM_FULL //!< \brief loop the whole item list
};
+ class mgListItems
+ {
+ public:
+ mgListItems() { m_sel=0; }
+ void setOwner(mgSelection* sel);
+ mgListItem* operator[](unsigned int idx);
+ string& id(unsigned int);
+ unsigned int count(unsigned int);
+ bool operator==(const mgListItems&x) const;
+ size_t size() const;
+ int search (const string v) const;
+ unsigned int valindex (const string v) const;
+ unsigned int idindex (const string i) const;
+ void clear();
+ void push_back(mgListItem* item) { m_items.push_back(item); }
+ vector<mgListItem*>& items() { return m_items; } //! \brief use only for loading!
+ void sort(bool bycount,mgSortBy SortBy);
+ private:
+ unsigned int index (const string s,bool val,bool second_try=false) const;
+ vector<mgListItem*> m_items;
+ mgSelection* m_sel;
+ };
+
/*! \brief the main constructor
* \param fall_through if TRUE: If enter() returns a choice
* containing only one item, that item is automatically entered.
@@ -98,12 +98,13 @@ class mgSelection
*/
mgSelection (const mgSelection* s);
+ virtual void MakeCollection() =0;
//! \brief initializes from a map.
- void InitFrom(mgValmap& nv);
+ void InitFrom(const char *prefix,mgValmap& nv);
//! \brief the normal destructor
- ~mgSelection ();
+ virtual ~mgSelection ();
/*! \brief represents all items for the current level. The result
* is cached, subsequent accesses to values only incur a
@@ -116,22 +117,19 @@ class mgSelection
*/
mgKeyTypes getKeyType (const unsigned int level) const;
+ mgSortBy getKeySortBy (const unsigned int level) const;
+
//! \brief return the current value of this key
- mgListItem& getKeyItem (const unsigned int level) const;
+ mgListItem* getKeyItem (const unsigned int level) const;
+/*! \brief returns an item value
+ */
+ string getValue(unsigned int idx) const;
+
/*! \brief returns the current item from the value() list
*/
string getCurrentValue();
-//! \brief returns a map (new allocated) for all used key fields and their values
- map<mgKeyTypes,string> UsedKeyValues();
-
-//! \brief the number of key fields used for the query
- unsigned int ordersize () { return order.size(); }
-
-//! \brief the number of music items currently selected
- unsigned int count () const;
-
//! \brief the current position
unsigned int getPosition ()const;
@@ -139,7 +137,6 @@ class mgSelection
// go to the nearest.
unsigned int gotoPosition ();
-
//! \brief the current position in the item list
unsigned int getItemPosition () const;
@@ -156,11 +153,6 @@ class mgSelection
*/
bool enter (unsigned int position);
- /*! \brief like enter but if we are at the leaf level simply select
- * the entry at position
- */
- bool select (unsigned int position);
-
/*! \brief enter the next higher level, expanding the current position.
* See also enter(unsigned int position)
*/
@@ -168,15 +160,6 @@ class mgSelection
{
return enter (gotoPosition ());
}
-
- /*! \brief like enter but if we are at the leaf level simply select
- * the current entry
- */
- bool select ()
- {
- return select (gotoPosition ());
- }
-
/*! \brief enter the next higher level, expanding the position holding a certain value
* \param value the position holding value will be expanded.
*/
@@ -184,22 +167,6 @@ class mgSelection
{
return enter (listitems.valindex (value));
}
-
- /*! \brief like enter but if we are at the leaf level simply select
- * the current entry
- */
- bool select (const string value)
- {
- return select (listitems.valindex(value));
- }
-
- bool selectid (const string i)
- {
- return select(listitems.idindex(i));
- }
-
- void selectfrom(mgOrder& oldorder,mgContentItem* o);
-
/*! \brief leave the current level, go one up in the tree.
* If fall_through (see constructor) is set to true, and the
* level entered by leave() contains only one item, automatically
@@ -217,11 +184,20 @@ class mgSelection
*/
void leave_all ();
-//! \brief the current level in the tree. This is at most order.size().
- unsigned int level () const
- {
- return m_level;
- }
+ unsigned int ordersize() const
+ {
+ return Keys.size();
+ }
+/*! \brief the orderlevel is 0 for the top level. After initializing
+ * an mgSelection from file or from another mgSelection, it is 0.
+ * It will only be correct after Activate() has been called. This
+ * is so because setting it correctly needs to access the database.
+ * We do not want to do that before we really need to.
+ */
+ unsigned int orderlevel() const
+ {
+ return m_level;
+ }
//! \brief true if the selection holds no items
bool empty();
@@ -233,17 +209,17 @@ class mgSelection
* loss of performance. See value(), the same warning applies.
* \todo call this more seldom. See getNumItems()
*/
- const vector < mgContentItem > &items () const;
+ const vector < mgItem* > &items () const;
/*! \brief returns an item from the items() list
* \param position the position in the items() list
* \return returns NULL if position is out of range
*/
- mgContentItem* getItem (unsigned int position);
+ mgItem* getItem (unsigned int position);
/*! \brief returns the current item from the items() list
*/
- mgContentItem* getCurrentItem ()
+ mgItem* getCurrentItem ()
{
return getItem (gotoItemPosition());
}
@@ -320,10 +296,13 @@ class mgSelection
/*! \brief go to the position with value in the current level
* \param value the value of the wanted position
*/
- void setPosition (const string value)
- {
- setPosition (listitems.valindex (value));
- }
+ void setPosition (string value);
+
+/*! \brief search the first position starting with search in the current level
+ * \param search the search string
+ * \return the new position
+ */
+ unsigned int searchPosition (string search);
/*! \brief go to a position in the item list
* \param position the wanted position. If it is too big, go to the
@@ -346,41 +325,17 @@ class mgSelection
*/
unsigned long getCompletedLength () const;
-/*! returns the number of items in the item list
- * \todo should not call items () which loads all item info.
- * instead, only count the items. If the size differs from
- * m_items.size(), invalidate m_items
- */
- unsigned int getNumItems () const
- {
- return items ().size ();
- }
/*! returns the name of the current play list. If no play list is active,
* the name is built from the name of the key fields.
*/
string getListname () const;
-/*! \brief true if this selection currently selects a list of collections
- */
- 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;
-
/*! \brief dumps the entire state of this selection into a map,
* \param nv the values will be entered into this map
*/
- void DumpState(mgValmap& nv) const;
-
- /*! \brief creates a new selection using saved definitions
- * \param nv this map contains the saved definitions
- */
- mgSelection(mgValmap& nv);
+ void DumpState(mgValmap& nv,const char *prefix) const;
+ void ShowState(char *w) const;
//! \brief clear the cache, next access will reload from data base
void clearCache() const;
@@ -392,55 +347,74 @@ class mgSelection
{
return (m_current_values=="" && m_current_tracks=="");
}
- string value(mgKeyTypes kt, string idstr) const;
- string value(mgKey* k, string idstr) const;
- string value(mgKey* k) const;
- string id(mgKeyTypes kt, string val) const;
- string id(mgKey* k, string val) const;
- string id(mgKey* k) const;
- unsigned int keycount(mgKeyTypes kt);
- unsigned int valcount (string val);
- bool Connected() { return m_db.Connected(); }
+
+ void setKeys(vector<const char*>& kt);
+ string Name();
+ bool SameOrder(const mgSelection* other);
+ mgKey* Key(unsigned int idx) const;
+ virtual vector <const char*> Choices(unsigned int level, unsigned int *current) const = 0;
+ void setOrderByCount(bool groupbycount);
+ bool getOrderByCount() const { return m_orderByCount; }
+ virtual bool NeedKey(unsigned int i) const = 0;
+ virtual mgParts SelParts(bool distinct,bool deepsort) const;
+ virtual bool inCollection(const string Name="") const =0;
+ virtual bool isLanguagelist() const =0;
+ virtual bool isCollectionlist() const =0;
+/*! \brief sets a default order. Every backend can define any number of
+ * default orders. \param i references the wanted order
+ * \return If i is higher than the highest default order, we return false
+ */
+ virtual bool InitDefaultOrder(unsigned int i=0) =0;
+/*! \brief prepare for use: initialize m_level and go to the
+ * correct position. This will execute only once after creation
+ * of the mgSelection, so we can call it too often
+ */
+ void Activate();
+
+ virtual bool keyIsUnique(mgKeyTypes kt) const = 0;
+ bool inItem() const;
+ bool inItems() const;
+
+ protected:
+ void InitFrom(const mgSelection* s);
+ virtual bool DeduceKeyValue(mgKeyTypes new_kt,const mgSelection *s,
+ vector<mgListItem>& items) {return false;}
+ virtual void clean();
+ virtual void InitSelection ();
+ virtual bool isCollectionOrder() const=0;
+ keyvector Keys;
+ unsigned int m_level;
+ mutable mgDb* m_db;
+ bool m_orderByCount;
+ bool UsedBefore(const mgKeyTypes kt,unsigned int level) const;
+ unsigned int keycount(mgKeyTypes kt) const;
+ virtual mgKeyTypes ktLow() const =0;
+ virtual mgKeyTypes ktHigh() const =0;
+ virtual const char * const ktName(const mgKeyTypes kt) const=0;
+ mgKeyTypes ktValue(const char * name) const;
+ void truncate(unsigned int i);
+ void clear();
+ void setKey ( const mgKeyTypes kt);
private:
- mutable map <mgKeyTypes, map<string,string> > map_values;
- mutable map <mgKeyTypes, map<string,string> > map_ids;
+ bool m_active;
mutable string m_current_values;
mutable string m_current_tracks;
//! \brief be careful when accessing this, see mgSelection::items()
- mutable vector < mgContentItem > m_items;
- //! \brief initializes maps for id/value mapping in both direction
- bool loadvalues (mgKeyTypes kt) const;
+ mutable vector < mgItem* > m_items;
bool m_fall_through;
unsigned int m_position;
mutable unsigned int m_items_position;
ShuffleMode m_shuffle_mode;
void Shuffle() const;
LoopMode m_loop_mode;
- mutable mgmySql m_db;
- unsigned int m_level;
- long m_itemid;
-
- mgOrder order;
- void InitSelection ();
- /*! \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.
- * This must be so for the leaf level because otherwise
- * the value() entries do not correspond to the track()
- * entries and the wrong items might be played.
- */
- string sql_values ();
- string ListFilename ();
- string m_Directory;
- void loadgenres ();
- void InitFrom(const mgSelection* s);
+ string ListFilename ();
+ void InitOrder(vector<mgListItem>& items);
+ void SetLevel(unsigned int level);
+ void IncLevel();
+ void DecLevel();
};
-
-unsigned int randrange (const unsigned int high);
-
-
-#endif // _DB_H
+#endif
diff --git a/mg_setup.c b/mg_setup.c
index a1ffcd4..3afc30d 100644
--- a/mg_setup.c
+++ b/mg_setup.c
@@ -16,18 +16,24 @@
#include "mg_setup.h"
+#include "mg_tools.h"
+#include "mg_db.h"
+#include <stdio.h>
#include <string>
+#include <getopt.h>
+#include <tools.h>
mgSetup the_setup;
mgSetup::mgSetup ()
{
+ m_mugglei=false;
InitLoopMode = 0;
InitShuffleMode = 0;
AudioMode = 1;
DisplayMode = 3;
- BackgrMode = 1;
+ BackgrMode = 2;
TargetLevel = DEFAULT_TARGET_LEVEL;
LimiterLevel = DEFAULT_LIMITER_LEVEL;
Only48kHz = 0;
@@ -38,9 +44,26 @@ mgSetup::mgSetup ()
DbName = strdup ("GiantDisc");
DbUser = 0;
DbPass = 0;
+ asprintf(&DbDatadir,"%s/.muggle",getenv("HOME"));
ToplevelDir = strdup("/mnt/music/");
-
+ CreateMode = false;
DeleteStaleReferences = false;
+
+ // stuff related to cover image display
+ ImageCacheDir = strdup( "/tmp" );
+ UseDeviceStillPicture = true;
+}
+
+bool
+mgSetup::IsMugglei() const
+{
+ return m_mugglei;
+}
+
+void
+mgSetup::SetMugglei()
+{
+ m_mugglei = true;
}
mgSetup::~mgSetup ()
@@ -50,7 +73,9 @@ mgSetup::~mgSetup ()
free(DbName);
free(DbUser);
free(DbPass);
+ free(DbDatadir);
free(ToplevelDir);
+ free(ImageCacheDir);
}
bool
@@ -59,3 +84,156 @@ mgSetup::NoHost() const
return !DbHost || strlen(DbHost)==0;
}
+bool mgSetup::ProcessArguments (int argc, char *argv[])
+{
+ mgSetDebugLevel (1);
+ char b[1000];
+ sprintf(b,"mgSetup::ProcessArgs ");
+ for (int i=1;i<argc;i++)
+ {
+ if (strlen(b)+strlen(argv[i]+2)>1000) break;;
+ strcat(b," ");
+ strcat(b,argv[i]);
+ }
+ mgDebug(1,b);
+
+ struct option long_options[50];
+ char short_options[100];
+ memset(short_options,0,sizeof(short_options));
+ memset(long_options,0,sizeof(long_options));
+ static struct option all_options[] =
+ {
+ {"host", required_argument, 0, 'h'},
+ {"socket", required_argument, 0, 's'},
+ {"port", required_argument, 0, 'p'},
+ {"user", required_argument, 0, 'u'},
+ {"password", required_argument, 0, 'w'},
+ {"name", required_argument, 0, 'n'},
+ {"datadir", required_argument, 0, 'd'},
+ {"toplevel", required_argument, 0, 't'},
+ {"verbose", required_argument, 0, 'v'},
+ {"create", no_argument, 0, 'c'},
+ {"delete", no_argument, 0, 'z'},
+ {0,0,0}
+ };
+ char wanted_opts[50];
+ strcpy(wanted_opts,"ndtv");
+ if (IsMugglei())
+ strcat(wanted_opts,"cz");
+ mgDb* db = GenerateDB();
+ strcat(wanted_opts,db->Options());
+ delete db;
+ char *p = wanted_opts;
+ char *s = short_options;
+ int li = 0;
+ while (*p)
+ {
+ for (unsigned int idx = 0; all_options[idx].name;idx++)
+ {
+ if (*p==all_options[idx].val)
+ {
+ *s++ = *p;
+ if (all_options[idx].has_arg==required_argument)
+ *s++ = ':';
+ long_options[li++]=all_options[idx];
+ break;
+ }
+ }
+ p++;
+ }
+ int c, option_index = 0;
+ while ((c = getopt_long(argc, argv, short_options,long_options,&option_index)) != -1)
+ {
+ switch (c)
+ {
+ case 'h':
+ {
+ free(DbHost);
+ DbHost = strdup (optarg);
+ }
+ break;
+ case 's':
+ {
+ free(DbSocket);
+ DbSocket = strdup(optarg);
+ }
+ break;
+ case 'p':
+ {
+ DbPort = atoi (optarg);
+ }
+ break;
+ case 'u':
+ {
+ free(DbUser);
+ DbUser = strdup(optarg);
+ }
+ break;
+ case 'w':
+ {
+ free(DbPass);
+ DbPass = strdup(optarg);
+ }
+ break;
+ case 'n':
+ {
+ free(DbName);
+ DbName = strdup(optarg);
+ }
+ break;
+ case 'd':
+ {
+ free(DbDatadir);
+ DbDatadir = strdup(optarg);
+ }
+ break;
+ case 'v':
+ {
+ mgSetDebugLevel (atol(optarg));
+ }
+ break;
+ case 't':
+ {
+ free(ToplevelDir);
+ if (optarg[strlen (optarg) - 1] != '/')
+ {
+ std::string res = std::string (optarg) + "/";
+ ToplevelDir = strdup (res.c_str ());
+ }
+ else
+ ToplevelDir = strdup(optarg);
+ }
+ break;
+ case 'z':
+ {
+ DeleteStaleReferences = true;
+ }
+ break;
+ case 'c':
+ {
+ CreateMode = true;
+ }
+ break;
+ }
+ }
+ return true;
+}
+
+const char*
+mgSetup::HelpText()
+{
+ static char buf[2000];
+ strcpy(buf,
+ " -n NNNN, --name=NNNN specify database name (default is GiantDisc)\n"
+ " -t TTTT, --toplevel=TTTT specify toplevel directory for music (default is /mnt/music)\n"
+ " -d DIRN, --datadir=DIRN specify directory for embedded sql data (default is $HOME/.muggle)\n"
+ " -v, --verbose specify debug level. The higher the more. Default is 1\n");
+ if (IsMugglei())
+ strcat(buf,
+ " -z --delete scan all data base entries and delete entries if their file is not found\n"
+ " -c --create delete the entire data base and create a new one\n");
+ mgDb* db = GenerateDB();
+ strcat(buf,db->HelpText());
+ delete db;
+ return buf;
+}
diff --git a/mg_setup.h b/mg_setup.h
index f5fd95d..4b5a47f 100644
--- a/mg_setup.h
+++ b/mg_setup.h
@@ -30,8 +30,10 @@
class mgSetup
{
public:
- mgSetup (void);
+ mgSetup ();
~mgSetup (void);
+ const char *HelpText();
+ bool ProcessArguments(int argc, char *argv[]);
bool NoHost() const;
int InitLoopMode;
int InitShuffleMode;
@@ -47,11 +49,20 @@ class mgSetup
char *DbName;
char *DbUser;
char *DbPass;
+ char *DbDatadir;
+
int DbPort;
char *ToplevelDir;
- int DeleteStaleReferences;
+ char *ImageCacheDir;
+ bool UseDeviceStillPicture;
+ int DeleteStaleReferences;
+ bool CreateMode;
+ bool IsMugglei() const;
+ void SetMugglei();
+ private:
+ bool m_mugglei;
};
diff --git a/mg_sync.c b/mg_sync.c
deleted file mode 100644
index 1e4645a..0000000
--- a/mg_sync.c
+++ /dev/null
@@ -1,313 +0,0 @@
-/*!
- * \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 <stdio.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 *
-mgDbGd::sql_Cstring(TagLib::String s,char *buf)
-{
- return m_db.sql_Cstring(s.toCString(),buf);
-}
-
-char *
-mgDbGd::lower(char *s)
-{
- char *p=s;
- while (*p)
- {
- int i=(int)(*p);
- (*p)=(char)tolower(i);
- p++;
- }
- return s;
-}
-
-TagLib::String
-mgDbGd::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 *
-mgDbGd::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;
-}
-
-mgDbGd::mgDbGd(bool separate_thread)
-{
- m_separate_thread = separate_thread;
- if (separate_thread)
- mysql_thread_init();
- 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 );
-}
-
-mgDbGd::~mgDbGd()
-{
- if (m_genre_rows) mysql_free_result(m_genre_rows);
- if (m_separate_thread)
- mysql_thread_end();
-}
-
-void
-mgDbGd::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
-mgDbGd::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
-mgDbGd::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;
- }
- TagLib::String sgenre1=tag->genre();
- const char *genrename=sgenre1.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 *folders[4];
- char *fbuf=SeparateFolders(filename,folders,4);
- sql_Cstring(folders[0],c_folder1);
- sql_Cstring(folders[1],c_folder2);
- sql_Cstring(folders[2],c_folder3);
- sql_Cstring(folders[3],c_folder4);
- free(fbuf);
- }
- return true;
-}
-
-void
-mgDbGd::SyncFile(const char *filename)
-{
- if (!strncmp(filename,"./",2)) // strip leading ./
- filename += 2;
- const char *cfilename=filename;
- if (isdigit(filename[0]) && isdigit(filename[1]) && filename[2]=='/' && !strchr(filename+3,'/'))
- cfilename=cfilename+3;
- if (strlen(cfilename)>255)
- {
- mgWarning("Length of file exceeds database field capacity: %s", filename);
- return;
- }
- mgDebug(3,"Importing %s",filename);
- sql_Cstring(cfilename,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
-mgDbGd::Sync(char * const * path_argv, bool delete_missing)
-{
- extern void showimportcount(unsigned int,bool final=false);
- 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)
- showimportcount(count);
- }
- }
- fts_close(fts);
- }
- showimportcount(count,true);
-}
-
-void
-mgDbGd::Create()
-{
- m_db.Create();
-}
diff --git a/mg_sync.h b/mg_sync.h
deleted file mode 100644
index bcc32db..0000000
--- a/mg_sync.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/*!
- * \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 mgDbGd
-{
- public:
- mgDbGd(bool SeparateThread=false);
- ~mgDbGd();
- //! \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:
- bool m_separate_thread;
- 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
diff --git a/mg_thread_sync.c b/mg_thread_sync.c
index 710eb1c..33d30ad 100644
--- a/mg_thread_sync.c
+++ b/mg_thread_sync.c
@@ -1,9 +1,16 @@
#include "mg_thread_sync.h"
-#include "mg_sync.h"
+#include "mg_db.h"
+#include "mg_tools.h"
static mgThreadSync* the_instance = NULL;
+mgThreadSync::mgThreadSync()
+{
+ m_path = 0;
+ m_has_args = false;
+}
+
mgThreadSync* mgThreadSync::get_instance()
{
if( !the_instance )
@@ -22,20 +29,19 @@ mgThreadSync* mgThreadSync::get_instance()
}
}
-void mgThreadSync::SetArguments( char * const * path_argv, bool delete_missing )
+void mgThreadSync::SetArguments( char * const * path_argv)
{
m_path = path_argv;
- m_delete = delete_missing;
+ m_has_args = true;
}
-bool mgThreadSync::Sync(char * const * path_argv, bool delete_missing )
+bool mgThreadSync::Sync(char * const * path_argv)
{
mgThreadSync *s = mgThreadSync::get_instance();
if( s )
{
- s->SetArguments( path_argv, delete_missing );
+ s->SetArguments( path_argv);
s->Start();
-
return true;
}
else
@@ -47,10 +53,11 @@ bool mgThreadSync::Sync(char * const * path_argv, bool delete_missing )
void
mgThreadSync::Action()
{
- if( m_path )
+ if( m_has_args )
{
- mgDbGd s(true);
- s.Sync( m_path, m_delete );
+ mgDb *s = GenerateDB(true);
+ s->Sync( m_path );
+ delete s;
}
}
diff --git a/mg_thread_sync.h b/mg_thread_sync.h
index d9a2e3f..23085d5 100644
--- a/mg_thread_sync.h
+++ b/mg_thread_sync.h
@@ -17,10 +17,10 @@
class mgThreadSync : public cThread
{
public:
-
+ mgThreadSync();
static mgThreadSync* get_instance();
- bool Sync(char * const * path_argv, bool delete_missing );
+ bool Sync(char * const * path_argv=0);
protected:
/*! \brief Runs the import routine as a separate thread
@@ -29,9 +29,10 @@ class mgThreadSync : public cThread
private:
- void SetArguments( char * const * path_argv, bool delete_missing );
+ void SetArguments( char * const * path_argv);
char * const *m_path;
+ bool m_has_args;
bool m_delete;
};
diff --git a/mg_tools.c b/mg_tools.c
index 147f9c7..1f8654b 100644
--- a/mg_tools.c
+++ b/mg_tools.c
@@ -8,13 +8,13 @@
* \author file owner: $Author$
*/
-#include "mg_tools.h"
-
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
+#include "mg_tools.h"
+
//! \brief buffer for messages
#define MAX_BUFLEN 2048
@@ -22,6 +22,7 @@ static char buffer[MAX_BUFLEN];
static int DEBUG_LEVEL = 3;
+
void
mgSetDebugLevel (int new_level)
{
@@ -40,6 +41,7 @@ mgDebug (int level, const char *fmt, ...)
vsnprintf (buffer, MAX_BUFLEN - 1, fmt, ap);
syslog(LOG_DEBUG,"%s\n",buffer);
+ fprintf(stderr,"%s\n",buffer);
}
va_end (ap);
}
@@ -51,8 +53,10 @@ mgDebug (const char *fmt, ...)
va_list ap;
va_start (ap, fmt);
mgDebug (1, fmt, ap);
+ va_end (ap);
}
+extern void showmessage(int duration,const char*,...);
void
mgWarning (const char *fmt, ...)
@@ -61,10 +65,9 @@ mgWarning (const char *fmt, ...)
va_list ap;
va_start (ap, fmt);
vsnprintf (buffer, MAX_BUFLEN - 1, fmt, ap);
-
syslog(LOG_INFO,"Warning: %s\n",buffer);
- extern void showmessage(const char*,int duration=0);
- showmessage(buffer);
+ fprintf(stderr,"%s\n",buffer);
+ showmessage(0,buffer);
va_end (ap);
}
@@ -78,6 +81,8 @@ mgError (const char *fmt, ...)
vsnprintf (buffer, MAX_BUFLEN - 1, fmt, ap);
syslog (LOG_ERR,"Error in Muggle: %s\n", buffer);
+ fprintf(stderr,"%s\n",buffer);
+ showmessage(0,buffer);
va_end (ap);
}
@@ -116,3 +121,57 @@ SeparateFolders(const char *filename, char * folders[],unsigned int fcount)
return fbuf;
}
+string&
+addsep (string & s, string sep, string n)
+{
+ if (!n.empty ())
+ {
+ if (!s.empty ())
+ s.append (sep);
+ s.append (n);
+ }
+ return s;
+}
+
+
+string
+comma (string & s, string n)
+{
+ return addsep (s, ",", n);
+}
+
+//! \brief converts long to string
+string
+itos (int i)
+{
+ std::stringstream s;
+ s << i;
+ return s.str ();
+}
+
+//! \brief convert long to string
+string
+ltos (long l)
+{
+ std::stringstream s;
+ s << l;
+ return s.str ();
+}
+
+char *
+extension(const char *filename)
+{
+ char *dot = strrchr(filename,'.');
+ if (!dot)
+ dot = strrchr(filename,0)-1;
+ return dot+1;
+}
+
+bool
+notempty(const char *s)
+{
+ if (!s)
+ return false;
+ else
+ return strlen(s);
+}
diff --git a/mg_tools.h b/mg_tools.h
index b36ac3f..7f63f64 100644
--- a/mg_tools.h
+++ b/mg_tools.h
@@ -14,8 +14,11 @@
#define _MUGGLE_TOOLS_H
#include <iostream>
+#include <sstream>
#include <string>
+using namespace std;
+
/*!
* \brief Logging utilities
*
@@ -27,7 +30,6 @@ void mgSetDebugLevel (int new_level);
void mgDebug (int level, const char *fmt, ...);
void mgDebug (const char *fmt, ...);
void mgWarning (const char *fmt, ...);
-//! \todo mgError should display the message on the OSD. How?
void mgError (const char *fmt, ...);
//@}
@@ -58,12 +60,12 @@ class mgLog
LOG, WARNING, ERROR, FATAL
} mgLogLevel;
- std::ostream & getStream ()
+ ostream & getStream ()
{
return std::cout;
}
- mgLog (std::string methodname):m_methodname (methodname)
+ mgLog (string methodname):m_methodname (methodname)
{
getStream () << m_methodname << " entered" << std::endl;
};
@@ -75,40 +77,27 @@ class mgLog
private:
- std::string m_methodname;
+ string m_methodname;
};
-std::string trim(std::string const& source, char const* delims = " \t\r\n");
+string trim(string const& source, char const* delims = " \t\r\n");
char *SeparateFolders(const char *filename, char * folders[],unsigned int fcount);
-enum mgKeyTypes {
- keyGenre1=1, // the genre types must have exactly this order!
- keyGenre2,
- keyGenre3,
- keyGenres,
- keyDecade,
- keyYear,
- keyArtist,
- keyAlbum,
- keyTitle,
- keyTrack,
- keyLanguage,
- keyRating,
- keyFolder1,
- keyFolder2,
- keyFolder3,
- keyFolder4,
- keyCreated,
- keyModified,
- keyArtistABC,
- keyTitleABC,
- keyCollection,
- keyCollectionItem,
-};
-const mgKeyTypes mgKeyTypesLow = keyGenre1;
-const mgKeyTypes mgKeyTypesHigh = keyCollectionItem;
-const unsigned int mgKeyTypesNr = keyCollectionItem;
+//! \brief adds string n to string s, using string sep to separate them
+string& addsep (string & s, string sep, string n);
+
+//! \brief adds string n to string s, using a comma to separate them
+string comma (string &s, string n);
+
+//! \brief converts long to string
+string itos (int i);
+
+//! \brief convert long to string
+string ltos (long l);
+
+char *extension (const char *filename);
+bool notempty(const char *s);
#endif /* _MUGGLE_TOOLS_H */
diff --git a/mg_valmap.c b/mg_valmap.c
index 2361b00..1728701 100644
--- a/mg_valmap.c
+++ b/mg_valmap.c
@@ -1,5 +1,7 @@
+#include <stdarg.h>
+
#include "mg_valmap.h"
-#include "mg_order.h"
+#include "mg_tools.h"
mgValmap::mgValmap(const char *key) {
m_key = key;
@@ -36,35 +38,105 @@ void mgValmap::Write(FILE *f) {
}
}
-void mgValmap::put(const char* name, const string value) {
- if (value.empty()) return;
- (*this)[string(name)] = value;
-}
-
-void mgValmap::put(const char* name, const char* value) {
- if (!value || *value==0) return;
- (*this)[string(name)] = value;
+void mgValmap::put(const string value, const char* name, ... ) {
+ va_list ap;
+ va_start(ap, name);
+ my_put(value,name,ap);
}
-void mgValmap::put(const char* name, const int value) {
- put(name,ltos(value));
+void mgValmap::put(int value, const char* name, ...) {
+ va_list ap;
+ va_start(ap, name);
+ my_put(ltos(value),name,ap);
+ va_end(ap);
}
-void mgValmap::put(const char* name, const unsigned int value) {
- put(name,ltos(value));
+void mgValmap::put(unsigned int value, const char* name, ...) {
+ va_list ap;
+ va_start(ap, name);
+ my_put(ltos(value),name,ap);
+ va_end(ap);
}
-void mgValmap::put(const char* name, const long value) {
- put(name,ltos(value));
+void mgValmap::put(long value,const char* name, ...) {
+ va_list ap;
+ va_start(ap, name);
+ my_put(ltos(value),name,ap);
+ va_end(ap);
}
-void mgValmap::put(const char* name, const bool value) {
- string s;
+void mgValmap::put(const bool value,const char* name, ...) {
+ string s;
if (value)
s = "true";
else
s = "false";
- put(name,s);
+ va_list ap;
+ va_start(ap, name);
+ my_put(s,name,ap);
+ va_end(ap);
+}
+
+void mgValmap::put(const char* value, const char* name, ...)
+{
+ if (!value) return;
+ va_list ap;
+ va_start(ap, name);
+ my_put(value, name, ap);
+ va_end(ap);
+}
+
+string
+mgValmap::getstr(const char* name, ...)
+{
+ va_list ap;
+ va_start(ap, name);
+ string result = my_get(name, ap);
+ va_end(ap);
+ return result;
}
+bool
+mgValmap::getbool(const char* name, ...)
+{
+ va_list ap;
+ va_start(ap, name);
+ bool result = my_get(name, ap)=="true";
+ va_end(ap);
+ return result;
+}
+
+long
+mgValmap::getlong(const char* name, ...)
+{
+ va_list ap;
+ va_start(ap, name);
+ long result = atol(my_get(name, ap).c_str());
+ va_end(ap);
+ return result;
+}
+
+unsigned int
+mgValmap::getuint(const char* name, ...)
+{
+ va_list ap;
+ va_start(ap, name);
+ unsigned int result = atol(my_get(name, ap).c_str());
+ va_end(ap);
+ return result;
+}
+
+void mgValmap::my_put(const string value, const char* name, va_list& ap)
+{
+ char buffer[600];
+ vsnprintf(buffer, 599, name, ap);
+ (*this)[string(buffer)] = value;
+}
+string
+mgValmap::my_get(const char *name, va_list& ap)
+{
+ char buffer[600];
+ vsnprintf(buffer, 599, name, ap);
+ return (*this)[buffer];
+}
diff --git a/mg_valmap.h b/mg_valmap.h
index ca39139..d2c70d6 100644
--- a/mg_valmap.h
+++ b/mg_valmap.h
@@ -2,6 +2,7 @@
#define _MG_VALMAP_H
#include <stdio.h>
+#include <stdarg.h>
#include <string>
#include <map>
@@ -21,32 +22,27 @@ class mgValmap : public map<string,string> {
//! \brief write to file
void Write(FILE *f);
//! \brief enter a string value
- void put(const char*name, string value);
+ void put(string value,const char*name, ...);
//! \brief enter a C string value
- void put(const char*name, const char* value);
+ void put(const char*value, const char* name, ...);
//! \brief enter a long value
- void put(const char*name, long value);
+ void put(long value, const char*name, ...);
//! \brief enter a int value
- void put(const char*name, int value);
+ void put(int value, const char*name, ... );
//! \brief enter a unsigned int value
- void put(const char*name, unsigned int value);
+ void put(unsigned int value, const char*name, ...);
//! \brief enter a bool value
- void put(const char*name, bool value);
+ void put(bool value, const char*name, ...);
//! \brief return a string
- string getstr(const char* name) {
- return (*this)[name];
- }
- //! \brief return a C string
- bool getbool(const char* name) {
- return (getstr(name)=="true");
- }
+ string getstr(const char* name, ...);
+ //! \brief return a bool
+ bool getbool(const char* name, ...);
//! \brief return a long
- long getlong(const char* name) {
- return atol(getstr(name).c_str());
- }
+ long getlong(const char* name, ...);
//! \brief return an unsigned int
- unsigned int getuint(const char* name) {
- return (unsigned long)getlong(name);
- }
+ unsigned int getuint(const char* name, ...);
+ private:
+ void my_put(const string value, const char *name, va_list& ap);
+ string my_get(const char *name, va_list& ap);
};
#endif
diff --git a/muggle.c b/muggle.c
index 58936a5..9c8db4f 100644
--- a/muggle.c
+++ b/muggle.c
@@ -20,7 +20,7 @@
#include <getopt.h>
#include <config.h>
-static const char *VERSION = "0.1.7";
+static const char *VERSION = "0.1.8";
static const char *DESCRIPTION = "Media juggle plugin for VDR";
static const char *MAINMENUENTRY = "Muggle";
@@ -34,168 +34,42 @@ mgMuggle::Version (void)
const char *
mgMuggle::Description (void)
{
- return tr(DESCRIPTION);
+ return DESCRIPTION;
}
const char *
mgMuggle::MainMenuEntry (void)
{
- return tr(MAINMENUENTRY);
+ return MAINMENUENTRY;
}
mgMuggle::mgMuggle (void)
{
-#ifndef HAVE_ONLY_SERVER
- char *buf;
- asprintf(&buf,"%s/.muggle",getenv("HOME"));
- set_datadir(buf);
- free(buf);
-#endif
}
void
mgMuggle::Stop (void)
{
+ delete DbServer;
+ DbServer = 0;
}
const char *
mgMuggle::CommandLineHelp (void)
{
-// Return a string that describes all known command line options.
- return
-#ifdef HAVE_ONLY_SERVER
- " -h HHHH, --host=HHHH specify database host (default is localhost)\n"
-#else
- " -h HHHH, --host=HHHH specify database host (default is mysql embedded)\n"
-#endif
- " -s SSSS --socket=PATH specify database socket\n"
- " -n NNNN, --name=NNNN specify database name (default is GiantDisc)\n"
- " -p PPPP, --port=PPPP specify port of database server (default is )\n"
- " -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"
-#ifndef HAVE_ONLY_SERVER
- " -d DIRN, --datadir=DIRN specify directory for embedded sql data (default is $HOME/.muggle)\n"
-#endif
- " -v, --verbose specify debug level. The higher the more. Default is 1\n"
- "\n"
- "if the specified host is localhost, sockets will be used if possible.\n"
- "Otherwise the -s parameter will be ignored";
+ return the_setup.HelpText();
}
bool mgMuggle::ProcessArgs (int argc, char *argv[])
{
- mgSetDebugLevel (1);
- char b[1000];
- sprintf(b,"mgMuggle::ProcessArgs ");
- for (int i=1;i<argc;i++)
- {
- if (strlen(b)+strlen(argv[i]+2)>1000) break;;
- strcat(b," ");
- strcat(b,argv[i]);
- }
- mgDebug(1,b);
-
-// Implement command line argument processing here if applicable.
- static struct option
- long_options[] =
- {
- {"host", required_argument, NULL, 'h'},
- {"socket", required_argument, NULL, 's'},
- {"name", required_argument, NULL, 'n'},
- {"port", required_argument, NULL, 'p'},
- {"user", required_argument, NULL, 'u'},
- {"password", required_argument, NULL, 'w'},
-#ifndef HAVE_ONLY_SERVER
- {"datadir", required_argument, NULL, 'd'},
-#endif
- {"toplevel", required_argument, NULL, 't'},
- {NULL}
- };
-
- int
- c,
- option_index = 0;
- while ((c =
-#ifndef HAVE_ONLY_SERVER
- getopt_long (argc, argv, "h:s:n:p:t:u:w:d:v:", long_options,
-#else
- getopt_long (argc, argv, "h:s:n:p:t:u:w:v:", long_options,
-#endif
- &option_index)) != -1)
- {
- switch (c)
- {
- case 'h':
- {
- the_setup.DbHost = strcpyrealloc (the_setup.DbHost, optarg);
- }
- break;
- case 's':
- {
- the_setup.DbSocket = strcpyrealloc (the_setup.DbSocket, optarg);
- }
- break;
- case 'n':
- {
- the_setup.DbName = strcpyrealloc (the_setup.DbName, optarg);
- }
- break;
- case 'p':
- {
- the_setup.DbPort = atoi (optarg);
- }
- break;
- case 'u':
- {
- the_setup.DbUser = strcpyrealloc (the_setup.DbUser, optarg);
- }
- break;
- case 'w':
- {
- the_setup.DbPass = strcpyrealloc (the_setup.DbPass, optarg);
- }
- break;
-#ifndef HAVE_ONLY_SERVER
- case 'd':
- {
- set_datadir(optarg);
- }
- break;
-#endif
- case 'v':
- {
- mgSetDebugLevel (atol(optarg));
- }
- break;
- case 't':
- {
- if (optarg[strlen (optarg) - 1] != '/')
- {
- std::string res = std::string (optarg) + "/";
- the_setup.ToplevelDir = strdup (res.c_str ());
- }
- else
- {
- the_setup.ToplevelDir =
- strcpyrealloc (the_setup.ToplevelDir, optarg);
- }
- }
- break;
- default:
- return false;
- }
- }
-
- return true;
+ return the_setup.ProcessArguments(argc,argv);
}
-
bool mgMuggle::Initialize (void)
{
// Initialize any background activities the plugin shall perform.
diff --git a/muggle.doxygen b/muggle.doxygen
index 98099e5..a7dbc2c 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.7
+PROJECT_NUMBER = 0.1.8
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
# base path where the generated documentation will be put.
diff --git a/mugglei.c b/mugglei.c
index a0f9a68..1e3d059 100755
--- a/mugglei.c
+++ b/mugglei.c
@@ -25,20 +25,36 @@
#include "mg_tools.h"
#include "mg_setup.h"
-#include "mg_sync.h"
+#include "mg_db.h"
using namespace std;
int SysLogLevel = 1;
-bool import_assorted, delete_mode, create_mode;
+void showmessage(int duration,const char *msg,...)
+{
+ va_list ap;
+ va_start(ap,msg);
+ vfprintf(stderr,msg,ap);
+ fprintf(stderr,"\n");
+ va_end(ap);
+}
+
+void showimportcount(unsigned int importcount,bool final=false)
+{
+ if (final)
+ mgDebug(1,"Imported %d tracks",importcount);
+}
-void showmessage(const char *msg,int duration)
+bool
+create_question()
{
+ return the_setup.CreateMode;
}
-void showimportcount(unsigned int count,bool final=false)
+void
+import()
{
}
@@ -47,8 +63,26 @@ const char *I18nTranslate(const char *s,const char *Plugin)
return s;
}
+bool
+path_within_tld()
+{
+ char path[5000];
+ if (!getcwd(path,4999))
+ {
+ std::cout << "Path too long" << std::endl;
+ exit (1);
+ }
+ int tldlen = strlen(the_setup.ToplevelDir);
+ strcat(path,"/");
+ int pathlen = strlen(path);
+ if (pathlen<tldlen)
+ return false;
+ return !strncmp(path,the_setup.ToplevelDir,tldlen);
+}
+
int main( int argc, char *argv[] )
{
+ the_setup.SetMugglei();
mgSetDebugLevel(1);
if( argc < 2 )
@@ -64,110 +98,26 @@ int main( int argc, char *argv[] )
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;
-#ifdef HAVE_ONLY_SERVER
- std::cout << " -h <hostname> - specify host of mySql database server (default is 'localhost')" << std::endl;
-#else
- std::cout << " -h <hostname> - specify host of mySql database server (default is mysql embedded')" << std::endl;
-#endif
- 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 << " -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 << " -z is not yet implemented" << std::endl;
- std::cout << " -c - delete the entire database and recreate a new empty one" << std::endl;
-#ifndef HAVE_ONLY_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;
- std::cout << std::endl << std::endl;
- std::cout << "if the specified host is localhost, sockets will be used if possible." << std::endl;
- std::cout << "Otherwise the -s parameter will be ignored" << std::endl;
-
- exit( 1 );
- }
+ std::cout << the_setup.HelpText();
- // option defaults
- import_assorted = false;
- delete_mode = false;
- create_mode = false;
-#ifndef HAVE_ONLY_SERVER
- char *buf;
- asprintf(&buf,"%s/.muggle",getenv("HOME"));
- set_datadir(buf);
- free(buf);
-#endif
-
- // parse command line options
- while( 1 )
- {
-#ifndef HAVE_ONLY_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;
-
- switch (c)
- {
- case 0:
- { // long option
-
- } break;
- case 'h':
- {
- the_setup.DbHost = strdup(optarg);
- } break;
- case 'n':
- {
- the_setup.DbName = strdup(optarg);
- } break;
- case 'u':
- {
- the_setup.DbUser = strdup(optarg);
- } break;
- case 'p':
- {
- the_setup.DbPass = strdup(optarg);
- } break;
- case 's':
- {
- the_setup.DbSocket = strdup(optarg);
- } break;
- case 't':
- {
- the_setup.ToplevelDir = strdup(optarg);
- } break;
- case 'z':
- {
- delete_mode = true;
- } break;
- case 'c':
- {
- create_mode = true;
- } break;
- case 'v':
- {
- mgSetDebugLevel(atol(optarg));
- } break;
-#ifndef HAVE_ONLY_SERVER
- case 'd':
- {
- set_datadir(optarg);
- } break;
-#endif
- }
+ exit( 2 );
}
- mgDbGd *sync = new mgDbGd; // because we want to delete it before database_end
- if (create_mode)
- sync->Create();
+
+ the_setup.ProcessArguments(argc,argv);
+
+ if (!path_within_tld())
+ {
+ std::cout << "you should be in " << the_setup.ToplevelDir
+ << " or below" << std::endl;
+ exit( 2 );
+ }
if (optind<argc)
- sync->Sync(argv+optind,delete_mode);
- delete sync;
- database_end();
+ {
+ mgDb *sync = GenerateDB();
+ sync->Sync(argv+optind);
+ delete sync;
+ delete DbServer;
+ }
return 0;
}
diff --git a/mugglei.h b/mugglei.h
deleted file mode 100644
index e69de29..0000000
--- a/mugglei.h
+++ /dev/null
diff --git a/scripts/image_convert.sh b/scripts/image_convert.sh
new file mode 100755
index 0000000..1fbe153
--- /dev/null
+++ b/scripts/image_convert.sh
@@ -0,0 +1,80 @@
+#!/bin/bash
+#
+# requires: ...topnm, pnmscale, pnmcomp, ppmntsc, ppmtoy4m, mpeg2enc
+#
+
+# video format. pal or ntsc
+FORMAT=pal
+
+# target image width/height (taking into account visible screen area)
+if [ "$FORMAT" = "ntsc" ]; then
+ TW=600
+ TH=420
+else
+ TW=632
+ TH=512
+fi
+
+TMP=/tmp/image_convert.$$.pnm
+IMG=$1
+MPG=$2
+
+DIR=`dirname "$MPG"`
+if [ ! -d "$DIR" ]; then
+ mkdir -p "$DIR"
+fi
+#
+# get the file type and set the according converter to PNM
+#
+FILE_TYPE=`file -i -L -b "$IMG" 2>/dev/null | cut -f2 -d/`
+case "$FILE_TYPE" in
+ jpg | jpeg)
+ TO_PNM=jpegtopnm
+ ;;
+ tiff)
+ TO_PNM=tifftopnm
+ ;;
+ bmp | x-bmp)
+ TO_PNM=bmptoppm
+ ;;
+ png | x-png)
+ TO_PNM=pngtopnm
+ ;;
+ Netpbm | pnm | x-portable-pixmap)
+ TO_PNM=cat
+ ;;
+ gif)
+ TO_PNM=giftopnm
+ ;;
+ *)
+ echo "filetype '$FILE_TYPE' is not supported"
+ exit 1
+ ;;
+esac
+#
+# extract the image size & compute scale value
+#
+LANG=C # get the decimal point right
+$TO_PNM "$IMG" >$TMP 2>/dev/null
+S=`pnmfile $TMP | awk '{ printf "%d %d ",$4,$6 }'`
+S=`echo $S $TW $TH | awk '{ sw=$3/$1; sh=$4/$2; s=(sw<sh)?sw:sh; printf "%.4f\n",(s>1)?1.0:s; }'`
+#
+# now run the conversion
+#
+if [ "$FORMAT" = "ntsc" ]; then
+ pnmscale $S $TMP | \
+ pnmpad -black -width 704 -height 480 | \
+ ppmntsc | \
+ ppmtoy4m -v 0 -n 1 -r -F 30000:1001 | \
+ mpeg2enc -f 7 -T 90 -F 4 -nn -a 2 -v 0 -o "$MPG"
+else
+ pnmscale $S $TMP | \
+ pnmpad -black -width 704 -height 576 | \
+ ppmntsc --pal | \
+ ppmtoy4m -v 0 -n 1 -r -F 25:1 | \
+ mpeg2enc -f 7 -T 90 -F 3 -np -a 2 -v 0 -o "$MPG"
+fi
+#
+# cleanup
+#
+rm $TMP
diff --git a/vdr_actions.c b/vdr_actions.c
index 0615a32..a150a15 100644
--- a/vdr_actions.c
+++ b/vdr_actions.c
@@ -22,7 +22,7 @@
#include <tools.h>
#include <plugin.h>
-#include "vdr_setup.h"
+#include "mg_setup.h"
#include "vdr_actions.h"
#include "vdr_menu.h"
#include "i18n.h"
@@ -30,7 +30,6 @@
#define DEBUG
#include "mg_tools.h"
-#include "mg_order.h"
#include "mg_thread_sync.h"
static bool
@@ -55,8 +54,6 @@ mgAction::setHandle(unsigned int handle)
eOSState
mgAction::ProcessKey(eKeys key)
{
- if (key!=kNone)
- mgDebug(1,"mgAction::ProcessKey(%d)",key);
eOSState result = Process(key);
if (result != osUnknown)
IgnoreNextEvent = true;
@@ -76,9 +73,9 @@ class mgEntry : public mgOsdItem
public:
void Notify();
bool Enabled(mgActions on) { return IsEntry(on);}
- const char *MenuName (const unsigned int idx,const mgListItem& item);
+ const char *MenuName (const unsigned int idx,const mgListItem* item);
eOSState Process(eKeys key);
- void Execute();
+ bool Execute();
eOSState Back();
};
@@ -107,13 +104,13 @@ class mgDoCollEntry : public mgEntry
class mgAddCollEntry : public mgDoCollEntry
{
public:
- void Execute();
+ bool Execute();
};
class mgRemoveCollEntry : public mgDoCollEntry
{
public:
- void Execute();
+ bool Execute();
};
void
@@ -147,7 +144,7 @@ mgDoCollEntry::Process(eKeys key)
}
-void
+bool
mgAddCollEntry::Execute()
{
string target = selection()->getCurrentValue();
@@ -157,17 +154,19 @@ mgAddCollEntry::Execute()
collselection()->ClearCollection(target);
osd()->Message1 ("Added %s entries",itos (osd()->moveselection->AddToCollection (target)));
- osd()->CollectionChanged(target);
+ osd()->CollectionChanged(target,true);
+ return true;
}
-void
+bool
mgRemoveCollEntry::Execute()
{
string target = selection()->getCurrentValue();
int removed = osd()->moveselection->RemoveFromCollection (target);
osd()->Message1 ("Removed %s entries",ltos(removed));
- osd()->CollectionChanged(target);
+ osd()->CollectionChanged(target,false);
+ return true;
}
@@ -231,15 +230,15 @@ class mgCommand : public mgOsdItem
class mgActOrder : public mgOsdItem
{
public:
- const char* MenuName(const unsigned int idx,const mgListItem& item);
+ const char* MenuName(const unsigned int idx,const mgListItem* item);
virtual eOSState Process(eKeys key);
- void Execute();
+ bool Execute();
};
const char*
-mgActOrder::MenuName(const unsigned int idx,const mgListItem& item)
+mgActOrder::MenuName(const unsigned int idx,const mgListItem* item)
{
- return strdup(item.value().c_str());
+ return strdup(item->value().c_str());
}
eOSState
@@ -253,8 +252,8 @@ mgActOrder::Process(eKeys key)
case kBack:
break;
case kOk:
- Execute ();
- break;
+ if (Execute ())
+ break;
default:
osd ()->newmenu = n; // wrong key: stay in submenu
result = osUnknown;
@@ -263,21 +262,10 @@ mgActOrder::Process(eKeys key)
return result;
}
-void
+bool
mgActOrder::Execute()
{
- mgSelection *s = osd()->selection();
- mgOrder oldorder = s->getOrder();
- mgContentItem o;
- s->select();
- if (s->getNumItems()==1)
- o = s->getItem(0);
- osd()->UseNormalSelection(); // Default for all orders
- osd()->setOrder(s,osd()->Current());
- mgSelection *newsel = osd()->selection();
- newsel->selectfrom(oldorder,&o);
- osd()->newposition = newsel->getPosition();
- osd()->SaveState();
+ return osd()->SwitchSelection();
}
bool
@@ -316,11 +304,11 @@ mgEntry::Notify()
const char *
-mgEntry::MenuName(const unsigned int idx,const mgListItem& item)
+mgEntry::MenuName(const unsigned int idx,const mgListItem* item)
{
char ct[20];
- unsigned int selcount = item.count();
- if (selection()->level()<selection()->ordersize()-1 || selcount>1)
+ unsigned int selcount = item->count();
+ if (!selection()->inItems())
{
char numct[20];
sprintf(numct,"%u",selcount);
@@ -338,31 +326,31 @@ mgEntry::MenuName(const unsigned int idx,const mgListItem& item)
char *result;
if (selection()->isCollectionlist())
{
- if (item.value() == osd()->default_collection)
- asprintf(&result,"-> %s%s",ct,item.value().c_str());
+ if (item->value() == osd()->default_collection)
+ asprintf(&result,"-> %s%s",ct,item->value().c_str());
else
- asprintf(&result," %s%s",ct,item.value().c_str());
+ asprintf(&result," %s%s",ct,item->value().c_str());
}
else if (selection()->inCollection())
- asprintf(&result,"%4d %s",idx,item.value().c_str());
+ asprintf(&result,"%4d %s",idx,item->value().c_str());
else if (selection()->isLanguagelist())
- asprintf(&result,"%s%s",ct,dgettext("iso_639",item.value().c_str()));
+ asprintf(&result,"%s%s",ct,dgettext("iso_639",item->value().c_str()));
else
- asprintf(&result,"%s%s",ct,item.value().c_str());
+ asprintf(&result,"%s%s",ct,item->value().c_str());
return result;
}
-void
+bool
mgEntry::Execute()
{
- if (selection ()->enter ())
- {
- osd()->forcerefresh = true;
- }
+ if (selection()->inItems())
+ m->ExecuteAction(actInstantPlay,Type());
else
{
- m->ExecuteAction(actInstantPlay,Type());
+ selection()->enter();
+ osd()->forcerefresh = true;
}
+ return true;
}
eOSState
@@ -469,7 +457,7 @@ class mgExternal : public mgCommand
{
public:
const char *ButtonName();
- void Execute();
+ bool Execute();
bool Enabled(mgActions on) { return true; }
private:
cCommand * Command();
@@ -521,7 +509,7 @@ mgExternal::Command()
return command;
}
-void
+bool
mgExternal::Execute()
{
cCommand *command = Command();
@@ -538,9 +526,7 @@ mgExternal::Execute()
if (confirmed)
{
osd()->Message1 ("%s...", command->Title ());
- selection ()->select ();
string m3u_file = selection ()->exportM3U ();
- selection ()->leave ();
if (!m3u_file.empty ())
{
/*char *result = (char *)*/
@@ -565,6 +551,7 @@ mgExternal::Execute()
}
}
}
+ return true;
}
//! \brief select search order
@@ -573,9 +560,9 @@ class mgChooseOrder : public mgCommand
public:
bool Enabled(mgActions on=mgActions(0));
virtual eOSState Process(eKeys key);
- void Execute ();
+ bool Execute ();
const char *ButtonName() { return tr("Order"); }
- const char *MenuName(const unsigned int idx,const mgListItem& item)
+ const char *MenuName(const unsigned int idx,const mgListItem* item)
{ return strdup(tr("Select an order")); }
};
@@ -600,10 +587,12 @@ mgChooseOrder::Process(eKeys key)
return mgCommand::Process(key);
}
-void mgChooseOrder::Execute()
+bool
+mgChooseOrder::Execute()
{
osd ()->newmenu = new mgMenuOrders;
- osd ()->newposition = osd()->getCurrentOrder();
+ osd ()->newposition = osd()->getCurrentSelection();
+ return true;
}
@@ -612,7 +601,7 @@ class mgEditOrder : public mgCommand
public:
bool Enabled(mgActions on) { return true; }
eOSState Process(eKeys key);
- void Execute () { osd ()->newmenu = new mgMenuOrder; }
+ bool Execute () { osd ()->newmenu = new mgMenuOrder; return true; }
const char *ButtonName() { return tr("Edit"); }
};
@@ -632,33 +621,35 @@ class mgCreateOrder : public mgCommand
{
public:
bool Enabled(mgActions on) { return true; }
- void Execute ();
+ bool Execute ();
const char *ButtonName() { return tr("Create"); }
};
-void
+bool
mgCreateOrder::Execute()
{
- osd()->AddOrder();
+ osd()->AddSelection();
osd()->SaveState();
osd()->forcerefresh = true;
+ return true;
}
class mgDeleteOrder : public mgCommand
{
public:
bool Enabled(mgActions on) { return true; }
- void Execute ();
+ bool Execute ();
const char *ButtonName() { return tr("Delete"); }
};
-void
+bool
mgDeleteOrder::Execute()
{
- osd()->DeleteOrder();
+ osd()->DeleteSelection();
osd()->SaveState();
osd()->forcerefresh = true;
osd()->newposition = osd()->Current();
+ return true;
}
//! \brief show the normal selection list
@@ -667,7 +658,7 @@ class mgShowList: public mgOsdItem
public:
bool Enabled(mgActions) { return true; }
const char *ButtonName () { return tr("List"); }
- void Execute() { osd()->newmenu=NULL; }
+ bool Execute() { osd()->newmenu=NULL; return true;}
};
@@ -677,7 +668,7 @@ class mgShowCommands: public mgOsdItem
public:
bool Enabled(mgActions on) { return true; }
const char *ButtonName () { return tr("Commands"); }
- void Execute() { osd()->newmenu = new mgSubmenu; }
+ bool Execute() { osd()->newmenu = new mgSubmenu;return true; }
};
@@ -686,7 +677,7 @@ class mgToggleSelection:public mgCommand
{
public:
bool Enabled(mgActions on) { return true; }
- void Execute ();
+ bool Execute ();
const char *ButtonName ();
};
@@ -699,7 +690,7 @@ mgToggleSelection::ButtonName ()
return tr ("Collections");
}
-void
+bool
mgToggleSelection::Execute ()
{
if (osd ()->UsingCollection)
@@ -709,61 +700,46 @@ mgToggleSelection::Execute ()
osd ()->UseCollectionSelection ();
selection()->clearCache();
}
+ selection()->Activate();
osd()->newposition = selection ()->gotoPosition ();
+ return true;
}
class mgCmdSync : public mgOsdItem
{
public:
bool Enabled(mgActions on) { return true; }
- void Execute();
eOSState ProcessKey(eKeys key);
const char *ButtonName() { return tr("Synchronize database"); }
};
-char *sync_args[] =
-{
- ".",
- 0
-};
-
eOSState
mgCmdSync::ProcessKey(eKeys key)
{
if (key==kOk)
- if (Interface->Confirm(tr("Synchronize database with track files?")))
{
- Execute();
+ extern bool import();
+ import();
return osContinue;
}
return osUnknown;
}
-void
-mgCmdSync::Execute()
-{
- mgThreadSync *s = mgThreadSync::get_instance();
- if( s )
- {
- s->Sync( sync_args, (bool) the_setup.DeleteStaleReferences );
- }
-}
-
//! \brief sets the default collection selection
class mgSetDefaultCollection:public mgCommand
{
public:
bool Enabled(mgActions on);
- void Execute ();
+ bool Execute ();
const char *ButtonName ()
{
return tr ("Default");
}
- const char *MenuName (const unsigned int idx,const mgListItem& item);
+ const char *MenuName (const unsigned int idx,const mgListItem* item);
};
-const char * mgSetDefaultCollection::MenuName(const unsigned int idx,const mgListItem& item)
+const char * mgSetDefaultCollection::MenuName(const unsigned int idx,const mgListItem* item)
{
char *b;
asprintf (&b, tr("Set default to collection '%s'"),
@@ -778,16 +754,17 @@ mgSetDefaultCollection::Enabled(mgActions on)
bool result = IsEntry(on);
result &= (!osd()->DefaultCollectionSelected());
result &= osd()->UsingCollection;
- result &= (selection ()->level () == 0);
+ result &= (selection ()->orderlevel () == 0);
return result;
}
-void
+bool
mgSetDefaultCollection::Execute ()
{
osd ()->default_collection = selection ()->getCurrentValue();
osd()->Message1 ("Default collection now is '%s'",
osd ()->default_collection);
+ return true;
}
@@ -802,44 +779,47 @@ class mgSetButton : public mgCommand
//! \brief instant play
class mgInstantPlay : public mgCommand {
public:
- void Execute ();
+ bool Execute ();
const char *ButtonName () { return tr ("Instant play"); }
};
-void
+bool
mgInstantPlay::Execute()
{
osd()->PlayInstant(true);
+ return true;
}
//! \brief add selected items to a collection
class mgAddAllToCollection:public mgCommand {
public:
- void Execute ();
+ bool Enabled(mgActions on);
+ bool Execute ();
//! \brief adds the whole selection to a collection
// \param collection the target collection. Default is the default collection
const char *ButtonName ()
{
return tr ("Add");
}
- const char *MenuName (const unsigned int idx,const mgListItem& item);
+ const char *MenuName (const unsigned int idx,const mgListItem* item);
protected:
void ExecuteMove();
};
const char *
-mgAddAllToCollection::MenuName (const unsigned int idx,const mgListItem& item)
+mgAddAllToCollection::MenuName (const unsigned int idx,const mgListItem* item)
{
return strdup(tr("Add all to a collection"));
}
-void
+bool
mgAddAllToCollection::Execute()
{
// work on a copy, so we don't have to clear the cache of selection()
// which would result in an osd()->forcerefresh which could scroll.
- osd()->moveselection = new mgSelection(selection());
+ osd()->moveselection = GenerateSelection(selection());
ExecuteMove();
+ return true;
}
void
@@ -859,19 +839,16 @@ mgAddAllToCollection::ExecuteMove()
//! \brief add selected items to default collection
class mgAddAllToDefaultCollection:public mgCommand {
public:
- void Execute ();
+ bool Enabled(mgActions on);
+ bool Execute ();
+ const char *MenuName (const unsigned int idx,const mgListItem* item);
//! \brief adds the whole selection to the default collection
// \param collection the default collection.
void ExecuteSelection (mgSelection *s);
- const char *ButtonName ()
- {
- return tr ("Add");
- }
- const char *MenuName (const unsigned int idx,const mgListItem& item);
};
const char *
-mgAddAllToDefaultCollection::MenuName (const unsigned int idx,const mgListItem& item)
+mgAddAllToDefaultCollection::MenuName (const unsigned int idx,const mgListItem* item)
{
char *b;
asprintf (&b, tr ("Add all to '%s'"),
@@ -879,13 +856,13 @@ mgAddAllToDefaultCollection::MenuName (const unsigned int idx,const mgListItem&
return b;
}
-void
+bool
mgAddAllToDefaultCollection::Execute()
{
- mgSelection *sel = new mgSelection(selection());
- sel->select ();
+ mgSelection *sel = GenerateSelection(selection());
ExecuteSelection(sel);
delete sel;
+ return true;
}
@@ -910,83 +887,74 @@ mgAddAllToDefaultCollection::ExecuteSelection (mgSelection *s)
}
}
+
//! \brief add selected items to a collection
class mgAddThisToCollection:public mgAddAllToCollection
{
public:
- bool Enabled(mgActions on);
- void Execute ();
- const char *ButtonName ();
- const char *MenuName (const unsigned int idx,const mgListItem& item);
+ bool Execute ();
+ const char *MenuName (const unsigned int idx,const mgListItem* item);
};
-
-void
+bool
mgAddThisToCollection::Execute ()
{
// work on a copy, so we don't have to clear the cache of selection()
// which would result in an osd()->forcerefresh which could scroll.
- osd()->moveselection = new mgSelection(selection());
- osd()->moveselection->select ();
- mgAddAllToCollection::ExecuteMove();
-}
-
-const char *
-mgAddThisToCollection::ButtonName ()
-{
- return tr("Add");
+ osd()->moveselection = GenerateSelection(selection());
+ osd()->moveselection->enter();
+ ExecuteMove();
+ return true;
}
bool
-mgAddThisToCollection::Enabled(mgActions on)
+mgAddAllToCollection::Enabled(mgActions on)
{
return IsEntry(on);
}
const char *
-mgAddThisToCollection::MenuName (const unsigned int idx,const mgListItem& item)
+mgAddThisToCollection::MenuName (const unsigned int idx,const mgListItem* item)
{
return strdup(tr("Add to a collection"));
}
+
+bool
+mgAddAllToDefaultCollection::Enabled(mgActions on)
+{
+ bool result = IsEntry(on);
+ result &= (!osd()->DefaultCollectionSelected());
+ return result;
+}
+
//! \brief add selected items to default collection
class mgAddThisToDefaultCollection:public mgAddAllToDefaultCollection
{
public:
- bool Enabled(mgActions on);
- void Execute ();
- const char *ButtonName ();
- const char *MenuName (const unsigned int idx,const mgListItem& item);
+ bool Execute ();
+ const char *ButtonName ()
+ {
+ return tr ("Add");
+ }
+ const char *MenuName (const unsigned int idx,const mgListItem* item);
};
-void
+bool
mgAddThisToDefaultCollection::Execute ()
{
// work on a copy, so we don't have to clear the cache of selection()
// which would result in an osd()->forcerefresh which could scroll.
- mgSelection *sel = new mgSelection(selection());
- sel->select ();
- mgAddAllToDefaultCollection::ExecuteSelection(sel);
+ mgSelection *sel = GenerateSelection(selection());
+ sel->enter ();
+ ExecuteSelection(sel);
delete sel;
+ return true;
}
const char *
-mgAddThisToDefaultCollection::ButtonName ()
-{
- return tr("Add");
-}
-
-bool
-mgAddThisToDefaultCollection::Enabled(mgActions on)
-{
- bool result = IsEntry(on);
- result &= (!osd()->DefaultCollectionSelected());
- return result;
-}
-
-const char *
-mgAddThisToDefaultCollection::MenuName (const unsigned int idx,const mgListItem& item)
+mgAddThisToDefaultCollection::MenuName (const unsigned int idx,const mgListItem* item)
{
char *b;
asprintf (&b, tr ("Add to '%s'"), osd ()->default_collection.c_str ());
@@ -997,27 +965,42 @@ mgAddThisToDefaultCollection::MenuName (const unsigned int idx,const mgListItem&
class mgRemoveAllFromCollection:public mgCommand
{
public:
- void Execute ();
- const char *ButtonName ()
- {
- return tr ("Remove");
- }
- const char *MenuName (const unsigned int idx,const mgListItem& item);
+ bool Enabled(mgActions on);
+ bool Execute ();
+ const char *MenuName (const unsigned int idx,const mgListItem* item);
+ protected:
+ void ExecuteRemove();
};
-void
+bool
+mgRemoveAllFromCollection::Enabled(mgActions on)
+{
+ return IsEntry(on);
+}
+
+bool
mgRemoveAllFromCollection::Execute ()
{
+ osd()->moveselection = GenerateSelection(selection());
+ ExecuteRemove();
+ return true;
+}
+
+void
+mgRemoveAllFromCollection::ExecuteRemove ()
+{
if (osd() ->Menus.size()>1)
osd ()->CloseMenu(); // TODO Gebastel...
char *b;
asprintf(&b,tr("Remove '%s' from collection"),osd()->moveselection->getListname().c_str());
osd ()->newmenu = new mgTreeRemoveFromCollSelector(string(b));
+ osd ()->collselection()->leave_all();
+ osd ()->newposition = osd()->collselection()->getPosition();
free(b);
}
const char *
-mgRemoveAllFromCollection::MenuName (const unsigned int idx,const mgListItem& item)
+mgRemoveAllFromCollection::MenuName (const unsigned int idx,const mgListItem* item)
{
return strdup(tr("Remove all from a collection"));
}
@@ -1026,16 +1009,16 @@ class mgClearCollection : public mgCommand
{
public:
bool Enabled(mgActions on);
- void Execute ();
+ bool Execute ();
const char *ButtonName ()
{
return tr ("Clear");
}
- const char *MenuName (const unsigned int idx,const mgListItem& item);
+ const char *MenuName (const unsigned int idx,const mgListItem* item);
};
const char *
-mgClearCollection::MenuName (const unsigned int idx,const mgListItem& item)
+mgClearCollection::MenuName (const unsigned int idx,const mgListItem* item)
{
return strdup(tr("Clear the collection"));
}
@@ -1046,43 +1029,45 @@ mgClearCollection::Enabled(mgActions on)
return selection()->isCollectionlist();
}
-void
+bool
mgClearCollection::Execute()
{
if (Interface->Confirm(tr("Clear the collection?")))
{
string target = selection()->getCurrentValue();
selection()->ClearCollection(target);
- osd()->CollectionChanged(target);
+ osd()->CollectionChanged(target,false);
}
+ return true;
}
//! \brief remove selected items from default collection
class mgRemoveThisFromCollection:public mgRemoveAllFromCollection
{
public:
- void Execute ();
+ bool Execute ();
const char *ButtonName ()
{
return tr ("Remove");
}
- const char *MenuName (const unsigned int idx,const mgListItem& item);
+ const char *MenuName (const unsigned int idx,const mgListItem* item);
};
-void
+bool
mgRemoveThisFromCollection::Execute ()
{
// work on a copy, so we don't have to clear the cache of selection()
// which would result in an osd()->forcerefresh which could scroll.
- osd()->moveselection = new mgSelection(selection());
- osd()->moveselection->select ();
- mgRemoveAllFromCollection::Execute();
+ osd()->moveselection = GenerateSelection(selection());
+ osd()->moveselection->enter();
+ ExecuteRemove();
+ return true;
}
const char *
-mgRemoveThisFromCollection::MenuName (const unsigned int idx,const mgListItem& item)
+mgRemoveThisFromCollection::MenuName (const unsigned int idx,const mgListItem* item)
{
return strdup(tr("Remove from a collection"));
}
@@ -1143,8 +1128,8 @@ class mgCreateCollection : public mgCreate
public:
mgCreateCollection();
bool Enabled(mgActions on);
- void Execute ();
- const char *MenuName (const unsigned int idx=0,const mgListItem& item=zeroitem);
+ bool Execute ();
+ const char *MenuName (const unsigned int idx=0,const mgListItem* item=0);
};
mgCreateCollection::mgCreateCollection() : mgCreate(MenuName())
@@ -1152,16 +1137,16 @@ mgCreateCollection::mgCreateCollection() : mgCreate(MenuName())
}
const char*
-mgCreateCollection::MenuName(const unsigned int idx,const mgListItem& item)
+mgCreateCollection::MenuName(const unsigned int idx,const mgListItem* item)
{
return strdup(tr ("Create collection"));
}
-void
+bool
mgCreateCollection::Execute ()
{
string name = trim(m_value);
- if (name.empty()) return;
+ if (name.empty()) return false;
bool created = selection ()->CreateCollection (name);
if (created)
{
@@ -1173,9 +1158,13 @@ mgCreateCollection::Execute ()
selection ()->setPosition(name);
}
osd()->forcerefresh = true;
+ return true;
}
else
+ {
osd()->Message1 ("Collection '%s' NOT created", name);
+ return false;
+ }
}
bool
@@ -1189,13 +1178,13 @@ mgCreateCollection::Enabled(mgActions on)
class mgDeleteCollection:public mgCommand
{
public:
- void Execute ();
+ bool Execute ();
bool Enabled(mgActions on);
const char *ButtonName ()
{
return tr ("Delete");
}
- const char *MenuName (const unsigned int idx,const mgListItem& item);
+ const char *MenuName (const unsigned int idx,const mgListItem* item);
};
bool
@@ -1211,16 +1200,16 @@ mgDeleteCollection::Enabled(mgActions on)
return result;
}
-const char* mgDeleteCollection::MenuName(const unsigned int idx,const mgListItem& item)
+const char* mgDeleteCollection::MenuName(const unsigned int idx,const mgListItem* item)
{
return strdup(tr("Delete the collection"));
}
-void
+bool
mgDeleteCollection::Execute ()
{
- if (!Interface->Confirm(tr("Delete the collection?"))) return;
+ if (!Interface->Confirm(tr("Delete the collection?"))) return false;
string name = selection ()->getCurrentValue();
if (selection ()->DeleteCollection (name))
{
@@ -1228,9 +1217,13 @@ mgDeleteCollection::Execute ()
mgDebug(1,"Deleted collection %s",name.c_str());
selection ()->clearCache();
osd()->forcerefresh = true;
+ return true;
}
else
+ {
osd()->Message1 ("Collection '%s' NOT deleted", name);
+ return false;
+ }
}
@@ -1238,24 +1231,23 @@ mgDeleteCollection::Execute ()
class mgExportItemlist:public mgCommand
{
public:
- void Execute ();
+ bool Execute ();
const char *ButtonName ()
{
return tr ("Export");
}
- const char *MenuName (const unsigned int idx,const mgListItem& item)
+ const char *MenuName (const unsigned int idx,const mgListItem* item)
{
return strdup(tr ("Export track list"));
}
};
-void
+bool
mgExportItemlist::Execute ()
{
- selection ()->select ();
string m3u_file = selection ()->exportM3U ();
- selection ()->leave ();
osd()->Message1 ("written to %s", m3u_file);
+ return true;
}
mgActions
@@ -1400,11 +1392,11 @@ mgKeyItem::Process(eKeys key)
mgMenuOrder *menu = dynamic_cast<mgMenuOrder*>(m);
if (key==kOk)
{
- if (menu->ChangeOrder(key))
+ if (menu->ChangeSelection(key))
return osContinue;
else
{
- menu->SaveOrder();
+ menu->SaveSelection();
osd ()->newmenu = NULL;
return osContinue;
}
@@ -1414,7 +1406,7 @@ mgKeyItem::Process(eKeys key)
return osContinue;
}
if (key==kUp || key==kDown)
- if (menu->ChangeOrder(key))
+ if (menu->ChangeSelection(key))
return osContinue;
return cMenuEditStraItem::ProcessKey(key);
}
@@ -1426,11 +1418,11 @@ mgBoolItem::Process(eKeys key)
mgMenuOrder *menu = dynamic_cast<mgMenuOrder*>(m);
if (key==kOk)
{
- if (menu->ChangeOrder(key))
+ if (menu->ChangeSelection(key))
return osContinue;
else
{
- menu->SaveOrder();
+ menu->SaveSelection();
osd ()->newmenu = NULL;
return osContinue;
}
@@ -1440,7 +1432,7 @@ mgBoolItem::Process(eKeys key)
return osContinue;
}
if (key==kUp || key==kDown)
- if (menu->ChangeOrder(key))
+ if (menu->ChangeSelection(key))
return osContinue;
return cMenuEditBoolItem::ProcessKey(key);
}
diff --git a/vdr_actions.h b/vdr_actions.h
index 5c7a9f8..374083e 100644
--- a/vdr_actions.h
+++ b/vdr_actions.h
@@ -18,7 +18,7 @@
#include <osd.h>
#include <plugin.h>
-#include "mg_order.h"
+#include "mg_selection.h"
using namespace std;
@@ -92,7 +92,7 @@ class mgAction
virtual bool Enabled(mgActions on = mgActions(0));
//! \brief the action to be executed
- virtual void Execute () {}
+ virtual bool Execute () {return true;}
//! \brief handles the kBack key
virtual eOSState Back();
@@ -109,7 +109,7 @@ class mgAction
* to execute this. The returned C string must be freeable at any time.
* \param value a string that can be used for building the menu name.
*/
- virtual const char *MenuName (const unsigned int idx=0,const mgListItem& item=zeroitem)
+ virtual const char *MenuName (const unsigned int idx=0,const mgListItem* item=0)
{
return strdup(ButtonName());
}
diff --git a/vdr_config.h b/vdr_config.h
index 20f7d23..c2d0d0d 100644
--- a/vdr_config.h
+++ b/vdr_config.h
@@ -1,5 +1,5 @@
/*!
- * \file vdr_menu.c
+ * \file vdr_config.h
* \brief Implements menu handling for browsing media libraries within VDR
*
* \version $Revision: 1.2 $
diff --git a/vdr_decoder.c b/vdr_decoder.c
index 7a1e00a..4ca42d9 100644
--- a/vdr_decoder.c
+++ b/vdr_decoder.c
@@ -27,11 +27,13 @@
#include <interface.h>
-#include "vdr_setup.h"
#include "vdr_decoder.h"
#include "vdr_decoder_mp3.h"
+#include "vdr_decoder_ogg.h"
+#include "vdr_decoder_flac.h"
+#include "vdr_decoder_sndfile.h"
-extern void showmessage(const char *,int duration=0);
+extern void showmessage(int duration,const char *,...);
#ifdef HAVE_VORBISFILE
#include "vdr_decoder_ogg.h"
@@ -79,7 +81,18 @@ mgMediaType mgDecoders::getMediaType (std::string s)
#else
mgWarning("Support for flac not compiled in, define HAVE_FLAC in Makefile");
#endif
- }
+ }
+ else
+ {
+ if( !strcasecmp( p, ".wav" ) )
+ {
+#ifdef HAVE_SNDFILE
+ mt = MT_SND;
+#else
+ mgWarning("Support for wav files not compiled in, define HAVE_SNDFILE in Makefile" );
+#endif
+ }
+ }
}
}
return mt;
@@ -87,7 +100,7 @@ mgMediaType mgDecoders::getMediaType (std::string s)
mgDecoder *
-mgDecoders::findDecoder (mgContentItem * item)
+mgDecoders::findDecoder (mgItemGd * item)
{
mgDecoder *decoder = 0;
@@ -111,17 +124,17 @@ mgDecoders::findDecoder (mgContentItem * item)
decoder = new mgFlacDecoder( item );
} break;
#endif
- /*
- case MT_MP3_STREAM: decoder = new mgMP3StreamDecoder(full); break;
- #ifdef HAVE_SNDFILE
- case MT_SND: decoder = new cSndDecoder(full); break;
- #endif
- */
+#ifdef HAVE_SNDFILE
+ case MT_SND:
+ {
+ decoder = new mgSndfileDecoder( item );
+ } break;
+#endif
default:
- {
+ {
esyslog ("ERROR: unknown media type ");
- }
- break;
+ }
+ break;
}
if (decoder && !decoder->valid ())
@@ -139,7 +152,7 @@ mgDecoders::findDecoder (mgContentItem * item)
// --- mgDecoder ----------------------------------------------------------------
-mgDecoder::mgDecoder (mgContentItem * item)
+mgDecoder::mgDecoder (mgItemGd * item)
{
m_item = item;
m_locked = 0;
diff --git a/vdr_decoder.h b/vdr_decoder.h
index 3dd6c00..74f51ad 100644
--- a/vdr_decoder.h
+++ b/vdr_decoder.h
@@ -25,7 +25,7 @@
#define DEC_ID(a,b,c,d) (((a)<<24)+((b)<<16)+((c)<<8)+(d))
-class mgContentItem;
+#include "mg_item_gd.h"
// --------From decoder_core.h ------------------------------------
@@ -71,7 +71,7 @@ class mgPlayInfo
*/
enum mgMediaType
{
- MT_MP3, MT_MP3_STREAM, MT_OGG, MT_FLAC, MT_UNKNOWN
+ MT_MP3, MT_MP3_STREAM, MT_OGG, MT_FLAC, MT_SND, MT_UNKNOWN
};
/*!
@@ -83,7 +83,7 @@ class mgDecoder
protected:
/*! \brief database handle to the track being decoded */
- mgContentItem * m_item;
+ mgItemGd * m_item;
/*! \brief The currently playing file */
std::string m_filename;
@@ -112,7 +112,7 @@ class mgDecoder
//@{
/*! \brief The constructor */
- mgDecoder (mgContentItem * item);
+ mgDecoder (mgItemGd * item);
/*! \brief The destructor */
virtual ~ mgDecoder ();
@@ -160,7 +160,7 @@ class mgDecoders
/*! \brief Try to find a valid decoder for a file
*/
- static mgDecoder *findDecoder (mgContentItem * item);
+ static mgDecoder *findDecoder (mgItemGd * item);
/*! \brief determine the media type for a given source
*/
diff --git a/vdr_decoder_flac.c b/vdr_decoder_flac.c
index fd1ca53..e4b7cbb 100644
--- a/vdr_decoder_flac.c
+++ b/vdr_decoder_flac.c
@@ -10,16 +10,12 @@
#ifdef HAVE_FLAC
-#define DEBUG
-
#include <string>
#include <stdlib.h>
#include <stdio.h>
#include "mg_tools.h"
-#include "mg_content.h"
-#include "vdr_setup.h"
#include "vdr_decoder_flac.h"
@@ -32,7 +28,7 @@ static const unsigned MAX_RES_SIZE = 16384;
// --- mgFlacDecoder -------------------------------------------------------------
-mgFlacDecoder::mgFlacDecoder( mgContentItem *item )
+mgFlacDecoder::mgFlacDecoder( mgItemGd *item )
: mgDecoder( item ), FLAC::Decoder::File()
{
mgLog lg( "mgFlacDecoder::mgFlacDecoder" );
@@ -81,7 +77,7 @@ bool mgFlacDecoder::initialize()
// init reservoir buffer; this should be according to the maximum
// frame/sample size that we can probably obtain from metadata
- m_reservoir = new (FLAC__int32*)[2];
+ m_reservoir = new FLAC__int32*[2];
m_reservoir[0] = new FLAC__int32[MAX_RES_SIZE];
m_reservoir[1] = new FLAC__int32[MAX_RES_SIZE];
@@ -262,7 +258,6 @@ mgFlacDecoder::write_callback(const ::FLAC__Frame *frame,
m_current_samples += m_len_decoded;
m_current_time_ms += (m_len_decoded*1000) / m_pcm->samplerate; // in milliseconds
- // cout << "Samples decoded: " << m_len_decoded << ", current time: " << m_current_time_ms << ", bits per sample: "<< m_nBitsPerSample << endl << flush;
// append buffer to m_reservoir
if( m_len_decoded > 0 )
@@ -291,20 +286,20 @@ void mgFlacDecoder::metadata_callback( const ::FLAC__StreamMetadata *metadata )
if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO)
{
- m_nTotalSamples = (unsigned)(metadata->data.stream_info.total_samples & 0xfffffffful);
+ m_nTotalSamples = (long)(metadata->data.stream_info.total_samples & 0xfffffffful);
m_nBitsPerSample = metadata->data.stream_info.bits_per_sample;
m_nCurrentChannels = metadata->data.stream_info.channels;
m_nCurrentSampleRate = metadata->data.stream_info.sample_rate;
m_nFrameSize = metadata->data.stream_info.max_framesize;
m_nBlockSize = metadata->data.stream_info.max_blocksize;
- m_nCurrentSampleRate = (int)get_sample_rate();
+ // m_nCurrentSampleRate = (int)get_sample_rate();
m_nCurrentChannels = (int)get_channels();
m_nCurrentBitsPerSample = (int)get_bits_per_sample();
-
- // m_nLengthMS = m_nTotalSamples * 10 / (m_nCurrentSampleRate / 100);
m_nBlockAlign = (m_nBitsPerSample / 8) * m_nCurrentChannels;
-
+ m_nLengthMS = m_nTotalSamples / m_nCurrentSampleRate;
+ m_nLengthMS *= 1000;
+
// m_nAverageBitRate = ((m_pReader->GetLength() * 8) / (m_nLengthMS / 1000) / 1000);
// m_nCurrentBitrate = m_nAverageBitRate;
}
@@ -346,16 +341,27 @@ bool mgFlacDecoder::skip(int seconds, int avail, int rate)
if( m_playing )
{
- const long target_time_ms = ((seconds-avail)*rate / 1000) + m_current_time_ms;
+ float bufsecs = (float) avail / (float) (rate * (16 / 8 * 2));
+
+ const long target_time_ms = ( ( seconds - (int)bufsecs ) * 1000) + m_current_time_ms;
const double distance = target_time_ms / (double)m_nLengthMS;
- const unsigned target_sample = (unsigned)(distance * (double)m_nTotalSamples);
-
- if( seek_absolute( (FLAC__uint64)target_sample) )
+ const long target_sample = (unsigned)(distance * (double)m_nTotalSamples);
+
+ if( target_sample > 0 )
{
- m_current_time_ms = target_time_ms;
+ if( seek_absolute( (FLAC__uint64)target_sample) )
+ {
+ m_current_time_ms = target_time_ms;
+ }
+ else
+ {
+ seek_absolute( 0 );
+ m_current_time_ms = 0;
+ }
res = true;
}
}
+
unlock();
return res;
}
diff --git a/vdr_decoder_flac.h b/vdr_decoder_flac.h
index 581d1af..b465fa5 100644
--- a/vdr_decoder_flac.h
+++ b/vdr_decoder_flac.h
@@ -58,7 +58,7 @@ class mgFlacDecoder : public mgDecoder,
public:
- mgFlacDecoder( mgContentItem *item );
+ mgFlacDecoder( mgItemGd *item );
~mgFlacDecoder();
virtual mgPlayInfo *playInfo();
diff --git a/vdr_decoder_mp3.c b/vdr_decoder_mp3.c
index 6569760..7cc4558 100644
--- a/vdr_decoder_mp3.c
+++ b/vdr_decoder_mp3.c
@@ -26,10 +26,8 @@
#include "vdr_config.h"
#include "vdr_decoder_mp3.h"
#include "vdr_stream.h"
-#include "vdr_setup.h"
#include "mg_tools.h"
-#include "mg_content.h"
#define d(x) x
@@ -54,7 +52,7 @@ mgMadStream (struct mad_stream *stream, mgStream * str)
// --- mgMP3Decoder -------------------------------------------------------------
-mgMP3Decoder::mgMP3Decoder (mgContentItem * item, bool preinit):mgDecoder
+mgMP3Decoder::mgMP3Decoder (mgItemGd * item, bool preinit):mgDecoder
(item)
{
m_stream = 0;
@@ -398,7 +396,7 @@ int secs, int avail, int dvbrate)
}
-bool mgMP3Decoder::skip (int seconds, int avail, int rate)
+bool mgMP3Decoder::skip(int seconds, int avail, int rate)
{
lock ();
diff --git a/vdr_decoder_mp3.h b/vdr_decoder_mp3.h
index 47d6eb2..0149031 100644
--- a/vdr_decoder_mp3.h
+++ b/vdr_decoder_mp3.h
@@ -30,7 +30,6 @@
#endif
class mgStream;
-class mgContentItem;
// ----------------------------------------------------------------
@@ -79,7 +78,7 @@ class mgMP3Decoder:public mgDecoder
/*!
* \brief construct a decoder from a filename
*/
- mgMP3Decoder (mgContentItem * item, bool preinit = true);
+ mgMP3Decoder (mgItemGd * item, bool preinit = true);
/*!
* \brief the destructor
diff --git a/vdr_decoder_ogg.c b/vdr_decoder_ogg.c
index 47011eb..91c9a70 100644
--- a/vdr_decoder_ogg.c
+++ b/vdr_decoder_ogg.c
@@ -19,9 +19,6 @@
#include <stdlib.h>
#include <stdio.h>
-#include "vdr_setup.h"
-
-#include "mg_content.h"
// --- mgOggFile ----------------------------------------------------------------
@@ -237,7 +234,7 @@ mgOggFile::stream (short *buffer, int samples)
// --- mgOggDecoder -------------------------------------------------------------
-mgOggDecoder::mgOggDecoder (mgContentItem * item):mgDecoder (item)
+mgOggDecoder::mgOggDecoder (mgItemGd * item):mgDecoder (item)
{
m_filename = item->getSourceFile ();
m_file = new mgOggFile (m_filename);
diff --git a/vdr_decoder_ogg.h b/vdr_decoder_ogg.h
index 1f5fcfc..6f142f3 100644
--- a/vdr_decoder_ogg.h
+++ b/vdr_decoder_ogg.h
@@ -42,7 +42,7 @@ class mgOggDecoder:public mgDecoder
public:
- mgOggDecoder (mgContentItem * item);
+ mgOggDecoder (mgItemGd * item);
~mgOggDecoder ();
virtual mgPlayInfo *playInfo ();
diff --git a/vdr_decoder_sndfile.c b/vdr_decoder_sndfile.c
new file mode 100644
index 0000000..0b84eaf
--- /dev/null
+++ b/vdr_decoder_sndfile.c
@@ -0,0 +1,412 @@
+/*
+ * MP3/MPlayer plugin to VDR (C++)
+ *
+ * (C) 2001-2005 Stefan Huelswitt <s.huelswitt@gmx.de>
+ *
+ * This code is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
+ */
+
+#ifdef HAVE_SNDFILE
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <math.h>
+
+#include <tools.h>
+
+#include "mg_setup.h"
+#include "vdr_decoder_sndfile.h"
+#include "i18n.h"
+
+#ifndef SNDFILE_1
+#error You must use libsndfile version 1.x.x
+#endif
+
+#define CDFS_TRACK_OFF 150
+#define CDFS_PROC "/proc/cdfs"
+#define CDFS_MARK_ID "CD (discid=%x) contains %d tracks:"
+#define CDFS_MARK_TR "%*[^[][ %d - %d"
+#define CDFS_TRACK "track-"
+
+#define CDDB_PROTO 5 // used protocol level
+#define CDDB_TOUT 30*1000 // connection timeout (ms)
+
+#ifdef DEBUG_CDFS
+#define dc(x) { (x); }
+#else
+#define dc(x) ;
+#endif
+
+// --- mgSndfileDecoder -------------------------------------------------------------
+
+#define SF_SAMPLES (sizeof(m_pcm->samples[0])/sizeof(mad_fixed_t))
+
+mgSndfileDecoder::mgSndfileDecoder( mgItemGd *item )
+ : mgDecoder( item ), m_file( item )
+{
+
+ m_pcm = 0;
+ m_framebuff = 0;
+ m_playing = false;
+ m_ready = false;
+}
+
+mgSndfileDecoder::~mgSndfileDecoder()
+{
+ clean();
+}
+
+bool mgSndfileDecoder::valid(void)
+{
+ bool res = false;
+
+ if( tryLock() )
+ {
+ if( m_file.Open(false) )
+ {
+ res=true;
+ }
+ mgDecoder::unlock();
+ }
+
+ return res;
+}
+
+mgPlayInfo *
+mgSndfileDecoder::playInfo ()
+{
+ mgPlayInfo *pi = NULL;
+
+ return pi;
+}
+
+void mgSndfileDecoder::init(void)
+{
+ clean();
+
+ m_pcm = new struct mad_pcm;
+ m_framebuff = MALLOC( int, 2*SF_SAMPLES + 8 );
+
+#ifdef GUARD_DEBUG
+ for(int i=0; i<8; i++) m_framebuff[i+(SF_SAMPLES*2)-4]=0xdeadbeaf;
+#endif
+
+ m_index = 0;
+}
+
+bool mgSndfileDecoder::clean(void)
+{
+ m_playing = false;
+
+ m_buffMutex.Lock();
+ m_run = false;
+ m_bgCond.Broadcast();
+ m_buffMutex.Unlock();
+
+ cThread::Cancel(3);
+
+ m_buffMutex.Lock();
+ if( !m_ready )
+ {
+ m_deferedN = -1;
+ m_ready=true;
+ }
+ m_fgCond.Broadcast();
+ m_buffMutex.Unlock();
+
+ delete m_pcm;
+ m_pcm=0;
+
+#ifdef GUARD_DEBUG
+ if(m_framebuff)
+ {
+ printf("snd: bufferguard");
+ for(int i=0; i<8; i++) printf(" %08x",m_framebuff[i+(SF_SAMPLES*2)-4]);
+ printf("\n");
+ }
+#endif
+
+ free(m_framebuff);
+ m_framebuff=0;
+ m_file.Close();
+
+ return false;
+}
+
+bool mgSndfileDecoder::start(void)
+{
+ mgDecoder::lock(true);
+ init();
+ m_playing=true;
+
+ if( m_file.Open() )
+ {
+ // d(printf("snd: open rate=%d frames=%lld channels=%d format=0x%x seek=%d\n",
+ // m_file.SoundfileInfo()->samplerate,m_file.SoundfileInfo()->frames,m_file.SoundfileInfo()->channels,m_file.SoundfileInfo()->format,m_file.SoundfileInfo()->seekable))
+
+ if( m_file.SoundfileInfo()->channels <= 2 )
+ {
+ m_ready = false;
+ m_run = true;
+ m_softCount=0;
+
+ cThread::Start();
+ mgDecoder::unlock();
+ return true;
+ }
+ else
+ {
+ // esyslog("ERROR: cannot play sound file %s: more than 2 channels", m_filename.c_str() );
+ }
+ }
+ mgDecoder::unlock();
+ return clean();
+}
+
+bool mgSndfileDecoder::stop(void)
+{
+ mgDecoder::lock();
+ if( m_playing )
+ {
+ clean();
+ }
+ mgDecoder::unlock();
+ return true;
+}
+
+void mgSndfileDecoder::Action(void)
+{
+ m_buffMutex.Lock();
+ while( m_run )
+ {
+
+ if( m_ready )
+ {
+ m_bgCond.Wait(m_buffMutex);
+ }
+
+ if(!m_ready)
+ {
+ m_buffMutex.Unlock();
+ m_deferedN = m_file.Stream( m_framebuff,SF_SAMPLES );
+ m_buffMutex.Lock();
+ m_ready = true;
+ m_fgCond.Broadcast();
+ }
+ }
+ m_buffMutex.Unlock();
+}
+
+struct mgDecode *mgSndfileDecoder::done(eDecodeStatus status)
+{
+ m_ds.status = status;
+ m_ds.index = m_index*1000 / m_file.SoundfileInfo()->samplerate;
+ m_ds.pcm = m_pcm;
+
+ mgDecoder::unlock(); // release the lock from Decode()
+
+ return &m_ds;
+}
+
+struct mgDecode *mgSndfileDecoder::decode()
+{
+ mgDecoder::lock(); // this is released in Done()
+ if(m_playing)
+ {
+ cMutexLock lock(&m_buffMutex);
+ while(!m_ready)
+ {
+ if( !m_softCount || !m_fgCond.TimedWait( m_buffMutex, m_softCount*5 ) )
+ {
+ if( m_softCount < 20 )
+ {
+ m_softCount++;
+ }
+ return done(dsSoftError);
+ }
+ }
+ m_softCount = 0;
+ m_ready = false;
+ m_bgCond.Broadcast();
+
+ int n = m_deferedN;
+ if( n < 0 )
+ {
+ return done(dsError);
+ }
+
+ if( n == 0 )
+ {
+ return done(dsEof);
+ }
+
+ m_pcm->samplerate = m_file.SoundfileInfo()->samplerate;
+ m_pcm->channels = m_file.SoundfileInfo()->channels;
+ m_pcm->length = n;
+ m_index += n;
+
+ int *data = m_framebuff;
+ mad_fixed_t *sam0 = m_pcm->samples[0], *sam1=m_pcm->samples[1];
+
+ const int s = (sizeof(int)*8)-1-MAD_F_FRACBITS; // shift value for mad_fixed conversion
+
+ if(m_pcm->channels>1)
+ {
+ for(; n>0 ; n--)
+ {
+ *sam0++=(*data++) >> s;
+ *sam1++=(*data++) >> s;
+ }
+ }
+ else
+ {
+ for(; n>0 ; n--)
+ {
+ *sam0++=(*data++) >> s;
+ }
+ }
+ return done(dsPlay);
+ }
+ return done(dsError);
+}
+
+bool mgSndfileDecoder::skip( int seconds, int avail, int rate )
+{
+ mgDecoder::lock();
+ bool res=false;
+ float bsecs = (float) avail / (float) ( rate * (16 / 8 * 2) );
+
+ if( m_playing && m_file.SoundfileInfo()->seekable )
+ {
+ float fsecs = (float)seconds - bsecs;
+ sf_count_t frames = (sf_count_t)( fsecs*(float)m_file.SoundfileInfo()->samplerate );
+ sf_count_t newpos = m_file.Seek( 0, true ) + frames;
+
+ if( newpos > m_file.SoundfileInfo()->frames )
+ {
+ newpos = m_file.SoundfileInfo()->frames-1;
+ }
+
+ if( newpos < 0 )
+ {
+ newpos=0;
+ }
+
+ // d(printf("snd: skip: secs=%d fsecs=%f frames=%lld current=%lld new=%lld\n",Seconds,fsecs,frames,m_file.Seek(0,true),newpos))
+
+ m_buffMutex.Lock();
+ frames = m_file.Seek( newpos, false );
+ m_ready = false;
+ m_bgCond.Broadcast();
+ m_buffMutex.Unlock();
+
+ if( frames >= 0 )
+ {
+ m_index = frames;
+#ifdef DEBUG
+ int i = frames/m_file.SoundfileInfo()->samplerate;
+ printf("snd: skipping to %02d:%02d (frame %lld)\n",i/60,i%60,frames);
+#endif
+ res=true;
+ }
+ }
+ mgDecoder::unlock();
+ return res;
+}
+
+// --- cSndfile ----------------------------------------------------------------
+
+mgSndfile::mgSndfile( mgItemGd *item )
+{
+ m_filename = item->getSourceFile();
+ m_sf = 0;
+}
+
+mgSndfile::~mgSndfile()
+{
+ Close();
+}
+
+SF_INFO* mgSndfile::SoundfileInfo()
+{
+ return &m_sfi;
+}
+
+bool mgSndfile::Open(bool log)
+{
+ if( m_sf )
+ {
+ return( Seek() >= 0 );
+ }
+
+ m_sf = sf_open( m_filename.c_str(), SFM_READ, &m_sfi );
+ if( !m_sf && log )
+ {
+ Error( "open" );
+ }
+
+ return ( m_sf != 0 );
+}
+
+void mgSndfile::Close(void)
+{
+ if( m_sf )
+ {
+ sf_close( m_sf );
+ m_sf = 0;
+ }
+}
+
+void mgSndfile::Error(const char *action)
+{
+ char buff[128];
+ sf_error_str( m_sf, buff, sizeof(buff) );
+ // esyslog( "ERROR: sndfile %s failed on %s: %s", action, Filename, buff );
+}
+
+sf_count_t mgSndfile::Seek( sf_count_t frames, bool relative )
+{
+ int dir = SEEK_CUR;
+ if( !relative )
+ {
+ dir = SEEK_SET;
+ }
+
+ int n = sf_seek( m_sf, frames, dir );
+ if( n < 0 )
+ {
+ Error("seek");
+ }
+
+ return n;
+}
+
+sf_count_t mgSndfile::Stream( int *buffer, sf_count_t frames )
+{
+ sf_count_t n = sf_readf_int( m_sf, buffer, frames );
+ if( n < 0 )
+ {
+ Error("read");
+ }
+ return n;
+}
+
+#endif //HAVE_SNDFILE
diff --git a/vdr_decoder_sndfile.h b/vdr_decoder_sndfile.h
new file mode 100644
index 0000000..eed8a16
--- /dev/null
+++ b/vdr_decoder_sndfile.h
@@ -0,0 +1,104 @@
+/*
+ * Muggle plugin to VDR (C++)
+ *
+ * (C) 2005 Lars von Wedel, Wolfgang Rohdewald, mainly copied from
+ * (C) 2001-2005 Stefan Huelswitt <s.huelswitt@gmx.de>
+ *
+ * This code is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
+ */
+
+#ifndef ___DECODER_SND_H
+#define ___DECODER_SND_H
+
+#define DEC_SND DEC_ID('S','N','D',' ')
+#define DEC_SND_STR "SND"
+
+#ifdef HAVE_SNDFILE
+
+#include <string>
+
+#include <time.h>
+#include <mad.h>
+#include <sndfile.h>
+
+#include <thread.h>
+
+#include "vdr_decoder.h"
+
+#define CDFS_MAGIC 0xCDDA // cdfs filesystem-ID
+
+// ----------------------------------------------------------------
+
+class mgSndfile
+{
+private:
+ SNDFILE *m_sf;
+ std::string m_filename;
+
+ void Error(const char *action);
+
+ SF_INFO m_sfi;
+public:
+
+ mgSndfile( mgItemGd *item );
+ ~mgSndfile();
+
+ SF_INFO* SoundfileInfo();
+ bool Open( bool log = true );
+ void Close();
+ sf_count_t Seek( sf_count_t frames = 0, bool relative = false );
+ sf_count_t Stream( int *buffer, sf_count_t frames );
+};
+
+// ----------------------------------------------------------------
+
+class mgSndfileDecoder : public mgDecoder, public cThread
+{
+private:
+ mgSndfile m_file;
+
+ struct mgDecode m_ds;
+ struct mad_pcm *m_pcm;
+ unsigned long long m_index;
+ //
+ cMutex m_buffMutex;
+ cCondVar m_fgCond, m_bgCond;
+ bool m_run, m_ready;
+ int *m_framebuff, m_deferedN, m_softCount;
+ //
+ void init();
+ bool clean();
+ struct mgDecode *done( eDecodeStatus status );
+
+protected:
+ virtual void Action(void);
+
+public:
+ mgSndfileDecoder( mgItemGd *item );
+ ~mgSndfileDecoder();
+
+ virtual bool valid(void);
+ virtual bool start(void);
+ virtual bool stop(void);
+ virtual bool skip(int seconds, int avail, int rate);
+ virtual struct mgDecode *decode();
+ virtual mgPlayInfo *playInfo();
+};
+
+// ----------------------------------------------------------------
+
+#endif //HAVE_SNDFILE
+#endif //___DECODER_SND_H
diff --git a/vdr_menu.c b/vdr_menu.c
index b683e0b..015b22d 100644
--- a/vdr_menu.c
+++ b/vdr_menu.c
@@ -26,17 +26,15 @@
#include <skins.h>
#endif
-#include "vdr_setup.h"
+#include "mg_setup.h"
#include "vdr_menu.h"
#include "vdr_player.h"
-
#include "mg_incremental_search.h"
-#include "mg_selection.h"
-
+#include "mg_thread_sync.h"
#include "i18n.h"
-#define DEBUG
#include "mg_tools.h"
+#include "mg_sel_gd.h"
void
mgStatus::OsdCurrentItem(const char* Text)
@@ -50,9 +48,13 @@ mgStatus::OsdCurrentItem(const char* Text)
a->TryNotify();
}
-void Play(mgSelection *sel,const bool select) {
- mgSelection *s = new mgSelection(sel);
- if (select) s->select();
+void Play(mgSelection *sel, bool enter)
+{
+ mgSelection *s = GenerateSelection(sel);
+ if (s->ordersize()==0)
+ s->InitDefaultOrder(1);
+ if (enter)
+ s->enter();
s->skipItems(0); // make sure we start with a valid item
if (s->empty()) // no valid item exists
{
@@ -66,6 +68,11 @@ void Play(mgSelection *sel,const bool select) {
cControl::Launch (new mgPlayerControl (s));
}
+mgSelection*
+GenerateSelection(const mgSelection* s)
+{
+ return new mgSelectionGd(s);
+}
//! \brief queue the selection for playing, abort ongoing instant play
void
@@ -78,35 +85,52 @@ mgMainMenu::PlayQueue()
//! \brief queue the selection for playing, abort ongoing queue playing
void
-mgMainMenu::PlayInstant(const bool select)
+mgMainMenu::PlayInstant(bool enter)
{
instant_playing=true;
- Play(selection(),select);
+ Play(selection(),enter);
}
-void
-mgMainMenu::setOrder(mgSelection *sel,unsigned int idx)
+bool
+mgMainMenu::SwitchSelection()
{
- mgOrder* o = getOrder(idx);
- if (o->size()>0)
+ UseNormalSelection();
+ mgSelection* newsel = getSelection(Current());
+ if (newsel->ordersize()>0)
{
- m_current_order = idx;
- sel->setOrder(o);
+ newsel->CopyKeyValues(selection());
+ newsel->Activate();
+ m_current_selection = Current();
+ newposition = selection()->getPosition();
+ SaveState();
+ return true;
}
else
- mgWarning("mgMainMenu::setOrder: orders[%u] is empty",idx);
+ {
+ Message1(tr("Order is undefined"),"");
+ return false;
+ }
+}
+
+void mgMainMenu::setSelection(unsigned int idx,mgSelection *s)
+{
+ if (idx>=selections.size())
+ mgError("mgMainMenu::getSelection(%u): selections.size() is %d",
+ idx,selections.size());
+ delete selections[idx];
+ selections[idx] = s;
}
-mgOrder* mgMainMenu::getOrder(unsigned int idx)
+mgSelection* mgMainMenu::getSelection(unsigned int idx)
{
- if (idx>=orders.size())
- mgError("mgMainMenu::getOrder(%u): orders.size() is %d",
- idx,orders.size());
- return orders[idx];
+ if (idx>=selections.size())
+ mgError("mgMainMenu::getSelection(%u): selections.size() is %d",
+ idx,selections.size());
+ return selections[idx];
}
void
-mgMainMenu::CollectionChanged(string name)
+mgMainMenu::CollectionChanged(string name,bool added)
{
delete moveselection;
moveselection = NULL;
@@ -117,7 +141,7 @@ mgMainMenu::CollectionChanged(string name)
mgPlayerControl *c = PlayerControl();
if (c)
c->ReloadPlaylist();
- else
+ else if (added)
PlayQueue();
}
if (CollectionEntered(name) || selection()->isCollectionlist())
@@ -127,7 +151,7 @@ mgMainMenu::CollectionChanged(string name)
bool
mgMainMenu::ShowingCollections()
{
- return (UsingCollection && selection ()->level () == 0);
+ return (UsingCollection && selection ()->orderlevel () == 0);
}
@@ -142,8 +166,8 @@ bool
mgMainMenu::CollectionEntered(string name)
{
if (!UsingCollection) return false;
- if (selection()->level()==0) return false;
- return trim(selection ()->getKeyItem(0).value()) == name;
+ if (selection()->orderlevel()==0) return false;
+ return trim(selection ()->getKeyItem(0)->value()) == name;
}
@@ -217,53 +241,60 @@ mgMenu::mgMenu ()
void
-mgMainMenu::DumpOrders(mgValmap& nv)
+mgMainMenu::DumpSelections(mgValmap& nv)
{
- for (unsigned int idx=0;idx<orders.size();idx++)
+ for (unsigned int idx=0;idx<selections.size();idx++)
{
- mgOrder *o = orders[idx];
- if (!o)
- mgError("DumpOrders:order[%u] is 0",idx);
+ mgSelection *s = selections[idx];
+ if (!s)
+ mgError("DumpSelections:selection[%u] is 0",idx);
char prefix[20];
sprintf(prefix,"order%u",idx);
- o->DumpState(nv,prefix);
+ s->DumpState(nv,prefix);
}
}
void
mgMainMenu::SaveState()
{
- char *b;
- asprintf(&b,"%s/muggle.state",cPlugin::ConfigDirectory ("muggle"));
- FILE *f = fopen(b,"w");
+ char *oldfile;
+ char *newfile;
+ char *statefile;
+ mgValmap nmain("MainMenu");
+ mgValmap nsel("tree");
+ mgValmap ncol("collection");
+ asprintf(&oldfile,"%s/muggle.state.old",cPlugin::ConfigDirectory ("muggle"));
+ asprintf(&newfile,"%s/muggle.state.new",cPlugin::ConfigDirectory ("muggle"));
+ asprintf(&statefile,"%s/muggle.state",cPlugin::ConfigDirectory ("muggle"));
+ FILE *f = fopen(newfile,"w");
if (!f)
{
if (!m_save_warned)
- mgWarning("Cannot write %s",b);
+ mgWarning("Cannot write %s",newfile);
m_save_warned=true;
- free(b);
- return;
+ goto err_exit;
}
- free(b);
- mgValmap nmain("MainMenu");
- nmain.put("DefaultCollection",default_collection);
- nmain.put("UsingCollection",UsingCollection);
- nmain.put("TreeRedAction",int(Menus.front()->TreeRedAction));
- nmain.put("TreeGreenAction",int(Menus.front()->TreeGreenAction));
- nmain.put("TreeYellowAction",int(Menus.front()->TreeYellowAction));
- nmain.put("CollRedAction",int(Menus.front()->CollRedAction));
- nmain.put("CollGreenAction",int(Menus.front()->CollGreenAction));
- nmain.put("CollYellowAction",int(Menus.front()->CollYellowAction));
- nmain.put("CurrentOrder",m_current_order);
- DumpOrders(nmain);
- mgValmap nsel("tree");
- m_treesel->DumpState(nsel);
- mgValmap ncol("collection");
- m_collectionsel->DumpState(ncol);
+ nmain.put(default_collection,"DefaultCollection");
+ nmain.put(UsingCollection,"UsingCollection");
+ nmain.put(int(Menus.front()->TreeRedAction),"TreeRedAction");
+ nmain.put(int(Menus.front()->TreeGreenAction),"TreeGreenAction");
+ nmain.put(int(Menus.front()->TreeYellowAction),"TreeYellowAction");
+ nmain.put(int(Menus.front()->CollRedAction),"CollRedAction");
+ nmain.put(int(Menus.front()->CollGreenAction),"CollGreenAction");
+ nmain.put(int(Menus.front()->CollYellowAction),"CollYellowAction");
+ nsel.put(m_current_selection,"CurrentSelection");
+ DumpSelections(nsel);
+ m_collectionsel->DumpState(ncol,"collection");
nmain.Write(f);
nsel.Write(f);
ncol.Write(f);
fclose(f);
+ rename(statefile,oldfile);
+ rename(newfile,statefile);
+err_exit:
+ free(oldfile);
+ free(newfile);
+ free(statefile);
}
mgMainMenu::mgMainMenu ():cOsdMenu ("",25)
@@ -282,15 +313,17 @@ mgMainMenu::mgMainMenu ():cOsdMenu ("",25)
mgValmap nmain("MainMenu");
// define defaults for values missing in state file:
- nsel.put("FallThrough",true);
- nmain.put("DefaultCollection",play_collection);
- nmain.put("UsingCollection","false");
- nmain.put("TreeRedAction",int(actAddThisToCollection));
- nmain.put("TreeGreenAction",int(actInstantPlay));
- nmain.put("TreeYellowAction",int(actToggleSelection));
- nmain.put("CollRedAction",int(actAddThisToCollection));
- nmain.put("CollGreenAction",int(actInstantPlay));
- nmain.put("CollYellowAction",int(actToggleSelection));
+ nsel.put(true,"FallThrough");
+ nmain.put(play_collection,"DefaultCollection");
+ nmain.put(false,"UsingCollection");
+ nmain.put(int(actAddThisToCollection),"TreeRedAction");
+ nmain.put(int(actInstantPlay),"TreeGreenAction");
+ nmain.put(int(actToggleSelection),"TreeYellowAction");
+ nmain.put(int(actAddThisToCollection),"CollRedAction");
+ nmain.put(int(actInstantPlay),"CollGreenAction");
+ nmain.put(int(actToggleSelection),"CollYellowAction");
+ nmain.put(0,"CurrentOrder");
+
// load values from state file
char *b;
@@ -305,36 +338,29 @@ mgMainMenu::mgMainMenu ():cOsdMenu ("",25)
}
// get values from mgValmaps
- LoadOrders(nmain);
- default_collection = nmain.getstr("DefaultCollection");
- UsingCollection = nmain.getbool("UsingCollection");
InitMapFromSetup(nsel);
InitMapFromSetup(ncol);
- m_treesel = new mgSelection;
- m_treesel->setOrder(getOrder(m_current_order));
- m_treesel->InitFrom (nsel);
- m_treesel->CreateCollection(default_collection);
+ LoadSelections(nsel);
+ default_collection = nmain.getstr("DefaultCollection");
+ UsingCollection = nmain.getbool("UsingCollection");
+ selections[m_current_selection]->CreateCollection(default_collection);
if (default_collection!=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 = new mgSelection;
- m_collectionsel->setOrder(&o);
- m_collectionsel->InitFrom (ncol);
- m_playsel = new mgSelection;
- m_playsel->setOrder(&o);
- m_playsel->InitFrom(ncol);
-
+ selections[m_current_selection]->CreateCollection(play_collection);
+ m_collectionsel = GenerateSelection();
+ m_collectionsel->InitFrom ("order0",ncol);
+ m_collectionsel->MakeCollection();
+ m_playsel = GenerateSelection();
+ m_playsel->InitFrom("order0",ncol);
+ m_playsel->MakeCollection();
// initialize
- if (m_playsel->level()!=1)
+ if (m_playsel->orderlevel()!=1)
{
m_playsel->leave_all();
m_playsel->enter(play_collection);
}
- UseNormalSelection ();
+ mgSelection *s = selections[m_current_selection];
+ s->CopyKeyValues(s);
+ s->Activate();
unsigned int posi = selection()->gotoPosition();
LoadExternalCommands(); // before AddMenu()
m_root = new mgTree;
@@ -349,56 +375,54 @@ mgMainMenu::mgMainMenu ():cOsdMenu ("",25)
}
void
-mgMainMenu::AddOrder()
+mgMainMenu::AddSelection()
{
- orders.push_back(new mgOrder);
+ selections.push_back(GenerateSelection());
+ newposition = selections.size()-1;
}
void
-mgMainMenu::DeleteOrder()
+mgMainMenu::DeleteSelection()
{
- mgOrder *o = orders[Current()];
+ mgSelection *o = selections[Current()];
delete o;
- orders.erase(orders.begin()+Current());
+ selections.erase(selections.begin()+Current());
}
void
-mgMainMenu::LoadOrders(mgValmap& nv)
+mgMainMenu::LoadSelections(mgValmap& nv)
{
for (unsigned int idx=0;idx<1000;idx++)
{
- char b[10];
- sprintf(b,"order%u",idx);
- mgOrder *o = new mgOrder(nv,b);
- if (o->size()==0)
+ char prefix[10];
+ sprintf(prefix,"order%u",idx);
+ mgSelection *s = GenerateSelection();
+ s->InitFrom(prefix,nv);
+ if (s->ordersize())
+ selections.push_back(s);
+ else
{
- delete o;
+ delete s;
break;
}
- orders.push_back(o);
}
- m_current_order = nv.getuint("CurrentOrder");
- if (m_current_order >= orders.size())
- m_current_order=0;
- if (orders.size()>0) return;
-
- nv.put("order0.Keys.0.Type",keyArtist);
- nv.put("order0.Keys.1.Type",keyAlbum);
- nv.put("order0.Keys.2.Type",keyTrack);
-
- nv.put("order1.Keys.0.Type",keyAlbum);
- nv.put("order1.Keys.1.Type",keyTrack);
-
- nv.put("order2.Keys.0.Type",keyGenres);
- nv.put("order2.Keys.1.Type",keyArtist);
- nv.put("order2.Keys.2.Type",keyAlbum);
- nv.put("order2.Keys.3.Type",keyTrack);
-
- nv.put("order3.Keys.0.Type",keyArtist);
- nv.put("order3.Keys.1.Type",keyTrack);
-
- nv.put("CurrentOrder",0);
- LoadOrders(nv);
+ if (selections.size()==0)
+ {
+ for (unsigned int i=1; i<100;i++)
+ {
+ mgSelection* s=GenerateSelection();
+ if (s->InitDefaultOrder(i))
+ selections.push_back(s);
+ else
+ {
+ delete s;
+ break;
+ }
+ }
+ }
+ m_current_selection = nv.getuint("CurrentSelection");
+ if (m_current_selection >= selections.size())
+ m_current_selection=0;
}
void
@@ -429,15 +453,14 @@ mgMainMenu::LoadExternalCommands()
mgMainMenu::~mgMainMenu()
{
- delete m_treesel;
delete m_collectionsel;
delete m_playsel;
delete m_Status;
delete moveselection;
delete m_root;
delete external_commands;
- for (unsigned int i=0;i<orders.size();i++)
- delete orders[i];
+ for (unsigned int i=0;i<selections.size();i++)
+ delete selections[i];
}
void
@@ -476,14 +499,18 @@ mgMenu::AddExternalAction(const mgActions action, const char *title)
void
mgMainMenu::AddOrderActions(mgMenu* m)
{
- for (unsigned int idx=0;idx<orders.size();idx++)
+ for (unsigned int idx=0;idx<selections.size();idx++)
{
- mgOrder *o = orders[idx];
+ mgSelection *o = selections[idx];
if (!o)
- mgError("AddOrderAction:orders[%u] is 0",idx);
+ mgError("AddOrderAction:selections[%u] is 0",idx);
mgAction *a = m->GenerateAction(actOrder,actNone);
assert(a);
- a->SetText(hk(o->Name().c_str()));
+ string name = o->Name(); // do not combine these 2 lines!
+ const char *oname = name.c_str();
+ if (strlen(oname)==0)
+ oname = tr("Order is undefined");
+ a->SetText(hk(oname));
AddItem(a);
}
}
@@ -491,20 +518,12 @@ mgMainMenu::AddOrderActions(mgMenu* m)
void
mgMenu::AddSelectionItems (mgSelection *sel,mgActions act)
{
+ sel->Activate();
for (unsigned int i = 0; i < sel->listitems.size (); i++)
{
mgAction *a = GenerateAction(act, actEntry);
if (!a) continue;
const char *name = a->MenuName(i+1,sel->listitems[i]);
- // add incremental filter here
-#if 0
- // example:
- if (name[0]!='C')
- continue;
- // adapt newposition since it refers to position in mgSelection:
- if ((signed int)i==osd()->newposition)
- osd()->newposition = osd()->Count();
-#endif
a->SetText(name,false);
a->setHandle(i);
osd()->AddItem(a);
@@ -697,22 +716,10 @@ mgMenu::Process (eKeys key)
void
mgTree::UpdateSearchPosition()
{
- int position = -1;
if( !m_incsearch || m_filter.empty() )
- position = m_start_position;
+ osd()->newposition = m_start_position;
else
- {
- // find the first item starting with m_filter
- mgSelection::mgListItems& listitems = osd()->selection()->listitems;
- for (unsigned int idx = 0 ; idx < listitems.size(); idx++)
- if( strncasecmp( listitems[idx].value().c_str(), m_filter.c_str(), m_filter.size() )>=0 )
- {
- position = idx;
- break;
- }
- }
- osd()->newposition = position;
- osd()->DisplayGoto();
+ osd()->newposition = osd()->selection()->searchPosition(m_filter);
}
bool
@@ -830,7 +837,7 @@ eOSState mgMainMenu::ProcessKey (eKeys key)
mgPlayerControl * c = PlayerControl ();
if (c)
{
- if (!c->Active ())
+ if (!c->Playing ())
{
c->Shutdown ();
if (instant_playing && queue_playing) {
@@ -926,10 +933,7 @@ otherkeys:
else if (newmenu != Menus.back ())
AddMenu (newmenu,newposition);
- if (UsingCollection)
- forcerefresh |= m_collectionsel->cacheIsEmpty();
- else
- forcerefresh |= m_treesel->cacheIsEmpty();
+ forcerefresh |= selection()->cacheIsEmpty();
forcerefresh |= (newposition>=0);
@@ -959,46 +963,48 @@ mgMainMenu::showMessage()
{
if (m_message)
{
- showmessage(m_message);
+ showmessage(0,m_message);
free(m_message);
m_message = NULL;
}
}
void
-showmessage(const char * msg,int duration)
+showmessage(int duration,const char * msg, ...)
{
+ va_list ap;
+ va_start(ap,msg);
+ char buffer[200];
+ vsnprintf(buffer,199,tr(msg),ap);
#if VDRVERSNUM >= 10307
- Skins.Message (mtInfo, msg,duration);
+ if (!duration) duration=2;
+ Skins.Message (mtInfo, buffer,duration);
Skins.Flush ();
#else
- Interface->Status (msg);
+ Interface->Status (buffer);
Interface->Flush ();
#endif
+ va_end(ap);
}
void
-showimportcount(unsigned int count,bool final=false)
+showimportcount(unsigned int impcount,bool final=false)
{
- char b[100];
+#if 0
+ // we should not write to the OSD since this is not the
+ // foreground thread. We could go thru port 2001.
if (final)
- {
- sprintf(b,tr("Import done:Imported %d items"),count);
- assert(strlen(b)<100);
- showmessage(b,1);
- }
+ showmessage(1,"Import done:Imported %d items",impcount);
else
- {
- sprintf(b,tr("Imported %d items..."),count);
- assert(strlen(b)<100);
- showmessage(b);
- }
+ showmessage(2,"Imported %d items...",impcount);
+#endif
}
void
mgMainMenu::AddMenu (mgMenu * m,unsigned int position)
{
Menus.push_back (m);
+ selection()->Activate();
m->setosd (this);
m->setParentIndex(Current());
if (Get(Current()))
@@ -1039,40 +1045,47 @@ mgMenuOrders::BuildOsd ()
mgMenuOrder::mgMenuOrder()
{
- m_order=0;
+ m_selection=0;
+ m_orgselection = 0;
}
mgMenuOrder::~mgMenuOrder()
{
- if (m_order)
- delete m_order;
+ if (m_selection)
+ delete m_selection;
}
string
mgMenuOrder::Title() const
{
- return m_order->Name();
+ return m_selection->Name();
}
void
mgMenuOrder::BuildOsd ()
{
- if (!m_order)
- {
- m_order = new mgOrder;
- *m_order = *(osd()->getOrder(getParentIndex()));
- }
+ if (!m_orgselection)
+ m_orgselection = osd()->getSelection(getParentIndex());;
+ if (!m_selection)
+ m_selection = GenerateSelection(m_orgselection);
+ if (m_selection->ordersize()==0)
+ m_selection->InitDefaultOrder(1);
InitOsd ();
m_keytypes.clear();
- m_keytypes.reserve(mgKeyTypesNr+1);
m_keynames.clear();
- m_keynames.reserve(50);
- m_orderbycount = m_order->getOrderByCount();
- for (unsigned int i=0;i<m_order->size();i++)
+ m_orderbycount = m_selection->getOrderByCount();
+ for (unsigned int i=0;i<m_selection->ordersize();i++)
{
+ if (m_selection->getKeyType(i)==keyGdUnique)
+ break;
unsigned int kt;
- m_keynames.push_back(m_order->Choices(i,&kt));
+ m_keynames.push_back(m_selection->Choices(i,&kt));
m_keytypes.push_back(kt);
+ }
+ for (unsigned int i=0;i<m_selection->ordersize();i++)
+ {
+ if (m_selection->getKeyType(i)==keyGdUnique)
+ break;
char buf[20];
sprintf(buf,tr("Key %d"),i+1);
mgAction *a = actGenerateKeyItem(buf,(int*)&m_keytypes[i],m_keynames[i].size(),&m_keynames[i][0]);
@@ -1085,31 +1098,39 @@ mgMenuOrder::BuildOsd ()
}
bool
-mgMenuOrder::ChangeOrder(eKeys key)
+mgMenuOrder::ChangeSelection(eKeys key)
{
- vector <mgKeyTypes> newtypes;
+ vector <const char*> newtypes;
newtypes.clear();
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);
- bool result = !(n == *m_order);
- *m_order = n;
- if (result)
+ newtypes.push_back(m_keynames[i][m_keytypes[i]]);
+ mgSelection *newsel = GenerateSelection(m_orgselection);
+ newsel->setKeys(newtypes);
+ newsel->setOrderByCount(m_orderbycount);
+ bool changed = !newsel->SameOrder(m_selection);
+ if (changed)
{
+ delete m_selection;
+ m_selection = newsel;
osd()->forcerefresh = true;
int np = osd()->Current();
if (key==kUp && np) np--;
if (key==kDown) np++;
osd()->newposition = np;
}
- return result;
+ else
+ delete newsel;
+ return changed;
}
void
-mgMenuOrder::SaveOrder()
+mgMenuOrder::SaveSelection()
{
- *(osd()->getOrder(getParentIndex())) = *m_order;
+ m_selection->CopyKeyValues(osd()->selection());
+ m_selection->Activate();
+ osd()->setSelection(getParentIndex(),m_selection);
+ m_selection = 0;
+ m_orgselection = 0;
osd()->SaveState();
}
@@ -1177,3 +1198,32 @@ mgMenu::Display ()
osd ()->DisplayGoto ();
}
+bool
+create_question()
+{
+ char *b;
+ asprintf(&b,tr("Create database %s?"),the_setup.DbName);
+ bool result = Interface->Confirm(b);
+ free(b);
+ return result;
+}
+
+bool
+import()
+{
+ if (!Interface->Confirm(tr("Import items?")))
+ return false;
+ mgThreadSync *s = mgThreadSync::get_instance();
+ if (!s)
+ return false;
+ static char *tld_arg[] = { ".", 0};
+ int res = chdir(the_setup.ToplevelDir);
+ if (res)
+ {
+ showmessage(2,tr("Cannot access directory %s:%d"),
+ the_setup.ToplevelDir,errno);
+ return false;
+ }
+ s->Sync(tld_arg);
+ return true;
+}
diff --git a/vdr_menu.h b/vdr_menu.h
index a46b4b8..745fbec 100644
--- a/vdr_menu.h
+++ b/vdr_menu.h
@@ -28,7 +28,7 @@
//! \param select if true, play only what the current position selects
void Play(mgSelection *sel,const bool select=false);
-void showmessage(const char *msg,int duration=2);
+void showmessage(int duration, const char *msg, ...);
void showimportcount(unsigned int count);
class cCommands;
@@ -63,25 +63,25 @@ class mgStatus : public cStatus
class mgMainMenu:public cOsdMenu
{
private:
- mgSelection *m_treesel;
mgSelection *m_playsel;
mgSelection *m_collectionsel;
char *m_message;
void showMessage();
void LoadExternalCommands();
- vector<mgOrder*> orders;
- unsigned int m_current_order;
- void DumpOrders(mgValmap& nv);
- void LoadOrders(mgValmap& nv);
+ vector<mgSelection*> selections;
+ unsigned int m_current_selection;
+ void DumpSelections(mgValmap& nv);
+ void LoadSelections(mgValmap& nv);
mgMenu *m_root;
bool m_save_warned;
public:
- void AddOrder();
- void DeleteOrder();
+ void AddSelection();
+ void DeleteSelection();
void AddOrderActions(mgMenu *m);
- unsigned int getCurrentOrder() { return m_current_order; }
- mgOrder* getOrder(unsigned int idx);
- void setOrder(mgSelection *sel, unsigned int idx);
+ unsigned int getCurrentSelection() { return m_current_selection; }
+ void setSelection(unsigned int idx,mgSelection *s);
+ mgSelection* getSelection(unsigned int idx);
+ bool SwitchSelection();
mgSelection *moveselection;
mgActions CurrentType();
@@ -204,7 +204,7 @@ class mgMainMenu:public cOsdMenu
if (UsingCollection)
return m_collectionsel;
else
- return m_treesel;
+ return selections[m_current_selection];
}
//! \brief the collection selection
@@ -230,7 +230,7 @@ class mgMainMenu:public cOsdMenu
void AddItem(mgAction *a);
- void CollectionChanged(std::string name);
+ void CollectionChanged(std::string name,bool added);
void CloseMenu();
@@ -262,10 +262,7 @@ class mgMenu
int m_parent_index;
std::string m_parent_name;
public:
- /*! sets the correct help keys.
- * \todo without data from mysql, no key is shown,
- * not even yellow or blue
- */
+ //! sets the correct help keys.
void SetHelpKeys(mgActions on = mgActions(0));
//! \brief generates an object for the wanted action
mgAction* GenerateAction(const mgActions action,mgActions on);
@@ -382,7 +379,7 @@ class mgSubmenu:public mgMenu
void BuildOsd ();
};
-//! \brief an mgMenu class for selecting an order
+//! \brief an mgMenu class for selecting a selection
class mgMenuOrders:public mgMenu
{
public:
@@ -399,13 +396,14 @@ class mgMenuOrder : public mgMenu
~mgMenuOrder();
//! \brief computes the title
std::string Title() const;
- bool ChangeOrder(eKeys key);
- void SaveOrder();
+ bool ChangeSelection(eKeys key);
+ void SaveSelection();
protected:
void BuildOsd ();
private:
- void AddKeyActions(mgMenu *m,mgOrder *o);
- mgOrder * m_order;
+ void AddKeyActions(mgMenu *m,mgSelection *o);
+ mgSelection * m_orgselection;
+ mgSelection * m_selection;
int m_orderbycount;
vector<int> m_keytypes;
vector < vector <const char*> > m_keynames;
@@ -444,4 +442,6 @@ class mgTreeRemoveFromCollSelector:public mgTreeCollSelector
virtual mgActions coll_action() { return actRemoveCollEntry; }
};
+mgSelection* GenerateSelection(const mgSelection *s=0);
+
#endif
diff --git a/vdr_player.c b/vdr_player.c
index e31b74a..5da9f4f 100644
--- a/vdr_player.c
+++ b/vdr_player.c
@@ -28,6 +28,7 @@
#include <mad.h>
+#include <linux/dvb/video.h>
#include <player.h>
#include <device.h>
#include <thread.h>
@@ -35,17 +36,20 @@
#include <tools.h>
#include <recording.h>
#include <status.h>
+#include <plugin.h>
#include "vdr_player.h"
#include "vdr_decoder.h"
#include "vdr_config.h"
-#include "vdr_setup.h"
+#include "mg_setup.h"
#include "i18n.h"
#include "mg_tools.h"
// ----------------------------------------------------------------
+// #define DEBUGPES
+
// TODO: check for use of constants
#define OUT_BITS 16 // output 16 bit samples to DVB driver
#define OUT_FACT (OUT_BITS/8*2) // output factor is 16 bit & 2 channels -> 4 bytes
@@ -113,12 +117,15 @@ class mgPCMPlayer:public cPlayer, cThread
{
private:
-//! \brief indicates, whether the player is currently active
+//! \brief indicates whether the player is currently active
bool m_active;
-//! \brief indicates, whether the player has been started
+//! \brief indicates whether the player has been started
bool m_started;
+//! \brief indicates whether the player is currently playing
+ bool m_playing;
+
//! \brief a buffer for decoded sound
cRingBufferFrame *m_ringbuffer;
@@ -132,24 +139,21 @@ class mgPCMPlayer:public cPlayer, cThread
mgSelection *m_playlist;
//! \brief the currently played or to be played item
- mgContentItem *m_current;
-
-//! \brief the currently playing item
- mgContentItem *m_playing;
+ mgItemGd *m_current;
//! \brief the decoder responsible for the currently playing item
mgDecoder *m_decoder;
cFrame *m_rframe, *m_pframe;
- enum ePlayMode
+ enum emgPlayMode
{
pmPlay,
pmStopped,
pmPaused,
pmStartup
};
- ePlayMode m_playmode;
+ emgPlayMode m_playmode;
enum eState
{
@@ -171,8 +175,8 @@ class mgPCMPlayer:public cPlayer, cThread
void PlayTrack();
void StopPlay ();
- void SetPlayMode (ePlayMode mode);
- void WaitPlayMode (ePlayMode mode, bool inv);
+ void SetPlayMode (emgPlayMode mode);
+ void WaitPlayMode (emgPlayMode mode, bool inv);
protected:
virtual void Activate (bool On);
@@ -187,6 +191,11 @@ class mgPCMPlayer:public cPlayer, cThread
return m_active;
}
+ bool Playing ()
+ {
+ return m_playing;
+ }
+
void Pause ();
void Play ();
void Forward ();
@@ -198,23 +207,31 @@ class mgPCMPlayer:public cPlayer, cThread
void ToggleLoop (void);
virtual bool GetIndex (int &Current, int &Total, bool SnapToIFrame = false);
-// bool GetPlayInfo(cMP3PlayInfo *rm); // LVW
+ // bool GetPlayInfo(cMP3PlayInfo *rm); // LVW
void ReloadPlaylist ();
void NewPlaylist (mgSelection * plist);
- mgContentItem *getCurrent ()
+
+ mgItemGd *getCurrent ()
{
return m_current;
}
+
mgSelection *getPlaylist ()
{
return m_playlist;
}
+
+ // background image handling stuff
+ string m_current_image;
+ void CheckImage( string fileName );
+ void ShowImage( );
+ void TransferImageTFT( string cover );
+ void send_pes_packet(unsigned char *data, int len, int timestamp);
};
-mgPCMPlayer::mgPCMPlayer (mgSelection * plist):cPlayer (the_setup.
-BackgrMode ? pmAudioOnly :
-pmAudioOnlyBlack)
+mgPCMPlayer::mgPCMPlayer (mgSelection * plist)
+ : cPlayer(the_setup.BackgrMode? pmAudioOnlyBlack: pmAudioOnly )
{
m_playlist = plist;
@@ -231,7 +248,7 @@ pmAudioOnlyBlack)
m_state = msStop;
m_index = 0;
- m_playing = 0;
+ m_playing = false;
m_current = 0;
}
@@ -240,18 +257,17 @@ mgPCMPlayer::~mgPCMPlayer ()
{
Detach ();
delete m_playlist;
- delete m_current;
delete m_ringbuffer;
}
void
mgPCMPlayer::PlayTrack()
{
- mgContentItem * newcurr = m_playlist->getCurrentItem ();
+ mgItemGd * newcurr = dynamic_cast<mgItemGd*>(m_playlist->getCurrentItem ());
if (newcurr)
{
- delete m_current;
- m_current = new mgContentItem(newcurr);
+ delete m_current;
+ m_current = new mgItemGd(newcurr);
}
Play ();
}
@@ -268,7 +284,6 @@ mgPCMPlayer::Activate (bool on)
Start ();
m_started = true;
- delete m_current;
m_current = 0;
m_playmode_mutex.Lock ();
@@ -314,6 +329,8 @@ mgPCMPlayer::NewPlaylist (mgSelection * plist)
Lock ();
StopPlay ();
+ delete m_current;
+ m_current = 0;
delete m_playlist;
m_playlist = plist;
PlayTrack();
@@ -321,7 +338,7 @@ mgPCMPlayer::NewPlaylist (mgSelection * plist)
}
void
-mgPCMPlayer::SetPlayMode (ePlayMode mode)
+mgPCMPlayer::SetPlayMode (emgPlayMode mode)
{
m_playmode_mutex.Lock ();
if (mode != m_playmode)
@@ -334,7 +351,7 @@ mgPCMPlayer::SetPlayMode (ePlayMode mode)
void
-mgPCMPlayer::WaitPlayMode (ePlayMode mode, bool inv)
+mgPCMPlayer::WaitPlayMode (emgPlayMode mode, bool inv)
{
// must be called with m_playmode_mutex LOCKED !!!
@@ -368,6 +385,10 @@ mgPCMPlayer::Action (void)
int beat = 0;
#endif
+#ifdef DEBUGPES
+ FILE *peslog = fopen( "pes.dump", "w" );
+#endif
+
dsyslog ("muggle: player thread started (pid=%d)", getpid ());
memset (&lpcmFrame, 0, sizeof (lpcmFrame));
@@ -376,6 +397,8 @@ mgPCMPlayer::Action (void)
lpcmFrame.PES[6] = 0x87;
lpcmFrame.LPCM[0] = 0xa0; // substream ID
lpcmFrame.LPCM[1] = 0xff;
+ lpcmFrame.LPCM[2] = 0;
+ lpcmFrame.LPCM[3] = 4;
lpcmFrame.LPCM[5] = 0x01;
lpcmFrame.LPCM[6] = 0x80;
@@ -410,12 +433,35 @@ mgPCMPlayer::Action (void)
case msStart:
{
m_index = 0;
- m_playing = m_current;
+ m_playing = true;
+
+ string img = m_current->getImagePath();
+
+ // check for TFT display of image
+ TransferImageTFT( img );
+
+ // check for background display of image
+ if( the_setup.BackgrMode == 2 )
+ {
+ if (img.empty())
+ {
+ m_current_image="";
+// cDevice::PrimaryDevice()->SetPlayMode(pmAudioOnly);
+ }
+ else
+ {
+// cDevice::PrimaryDevice()->SetPlayMode(pmAudioOnlyBlack);
+ string prev_mpg = m_current_image;
+ CheckImage( img );
+ if( ( prev_mpg != m_current_image ))
+ ShowImage();
+ }
+ }
- if (m_playing)
+ if (m_current)
{
- std::string filename = m_playing->getSourceFile ();
- if ((m_decoder = mgDecoders::findDecoder (m_playing))
+ string filename = m_current->getSourceFile ();
+ if ((m_decoder = mgDecoders::findDecoder (m_current))
&& m_decoder->start ())
{
levelgood = true;
@@ -513,8 +559,8 @@ mgPCMPlayer::Action (void)
{ // If one of the supported frequencies, do it without resampling.
case 96000:
{ // Select a "even" upsampling frequency if possible, too.
- lpcmFrame.LPCM[5] |= 1 << 4;
- dvbSampleRate = 96000;
+ lpcmFrame.LPCM[5] |= 1 << 4;
+ dvbSampleRate = 96000;
}
break;
@@ -526,16 +572,18 @@ mgPCMPlayer::Action (void)
case 22050:
case 44100:
{
- lpcmFrame.LPCM[5] |= 2 << 4;
- dvbSampleRate = 44100;
+ // lpcmFrame.LPCM[5] |= 2 << 4;
+ lpcmFrame.LPCM[5] |= 0x20;
+ dvbSampleRate = 44100;
}
break;
case 8000:
case 16000:
case 32000:
{
- lpcmFrame.LPCM[5] |= 3 << 4;
- dvbSampleRate = 32000;
+ // lpcmFrame.LPCM[5] |= 3 << 4;
+ lpcmFrame.LPCM[5] |= 0x30;
+ dvbSampleRate = 32000;
}
break;
}
@@ -578,13 +626,19 @@ mgPCMPlayer::Action (void)
amRound);
if (outlen)
{
- outlen += sizeof (lpcmFrame.LPCM) + LEN_CORR;
- lpcmFrame.PES[4] = outlen >> 8;
- lpcmFrame.PES[5] = outlen;
- m_rframe = new cFrame ((unsigned char *) &lpcmFrame,
- outlen +
- sizeof (lpcmFrame.PES) -
- LEN_CORR);
+ outlen += sizeof (lpcmFrame.LPCM) + LEN_CORR;
+ lpcmFrame.PES[4] = outlen >> 8;
+ lpcmFrame.PES[5] = outlen;
+
+ // lPCM has 600 fps which is 80 samples at 48kHz per channel
+ // Frame size = (sample_rate * quantization * channels)/4800
+ size_t frame_size = 2 * (dvbSampleRate*16)/4800;
+
+ lpcmFrame.LPCM[1] = (outlen - LPCM_SIZE)/frame_size;
+ m_rframe = new cFrame ((unsigned char *) &lpcmFrame,
+ outlen +
+ sizeof (lpcmFrame.PES) -
+ LEN_CORR);
}
}
else
@@ -607,7 +661,7 @@ mgPCMPlayer::Action (void)
} // fall through
case msStop:
{
- m_playing = 0;
+ m_playing = false;
if (m_decoder)
{ // who deletes decoder?
m_decoder->stop ();
@@ -657,6 +711,10 @@ mgPCMPlayer::Action (void)
if (m_pframe)
{
+#ifdef DEBUGPES
+ fwrite( (void *)p, pc, sizeof( char ), peslog );
+#endif
+
#if VDRVERSNUM >= 10318
int w = PlayPes (p, pc);
#else
@@ -719,10 +777,14 @@ mgPCMPlayer::Action (void)
m_decoder = 0;
}
- m_playing = 0;
+ m_playing = false;
SetPlayMode (pmStopped);
+#ifdef DEBUGPES
+ fclose( peslog );
+#endif
+
Unlock ();
m_active = false;
@@ -772,16 +834,17 @@ mgPCMPlayer::StopPlay ()
bool mgPCMPlayer::SkipFile (bool skipforward)
{
MGLOG("mgPCMPlayer::SkipFile");
- mgContentItem * newcurr = NULL;
+ mgItemGd * newcurr = NULL;
int skip_direction=1;
if (!skipforward)
skip_direction=-1;
if (m_playlist->skipItems (skip_direction))
{
- newcurr = m_playlist->getCurrentItem ();
- if (newcurr) {
+ newcurr = dynamic_cast<mgItemGd*>(m_playlist->getCurrentItem ());
+ if (newcurr)
+ {
delete m_current;
- m_current = new mgContentItem(newcurr);
+ m_current = new mgItemGd(newcurr);
}
}
return (newcurr != NULL);
@@ -872,14 +935,14 @@ void
mgPCMPlayer::Goto (int index, bool still)
{
m_playlist->GotoItemPosition (index - 1);
- mgContentItem *next = m_playlist->getCurrentItem ();
+ mgItemGd *next = dynamic_cast<mgItemGd*>(m_playlist->getCurrentItem ());
if (next)
{
Lock ();
StopPlay ();
- delete m_current;
- m_current = new mgContentItem(next);
+ delete m_current;
+ m_current = new mgItemGd(next);
Play ();
Unlock ();
}
@@ -890,18 +953,20 @@ void
mgPCMPlayer::SkipSeconds (int secs)
{
if (m_playmode != pmStopped)
- {
+ {
Lock ();
+
if (m_playmode == pmPaused)
- {
+ {
SetPlayMode (pmPlay);
- }
- if (m_decoder
- && m_decoder->skip (secs, m_ringbuffer->Available (),
- dvbSampleRate))
- {
+ }
+ if ( m_decoder
+ && m_decoder->skip (secs, m_ringbuffer->Available(),
+ dvbSampleRate) )
+ {
levelgood = false;
- }
+ }
+
Empty ();
Unlock ();
}
@@ -920,232 +985,152 @@ bool mgPCMPlayer::GetIndex (int &current, int &total, bool snaptoiframe)
}
-/*
-string mgPCMPlayer::CheckImage( string fileName, size_t j )
-{
-static char tmpFile[1024];
-char *tmp[2048];
-char *result = NULL;
-FILE *fp;
-
-sprintf (tmpFile, "%s/%s", MP3Setup.ImageCacheDir, &fileName[j]); // ???
-strcpy (strrchr (tmpFile, '.'), ".mpg");
-d(printf("mp3[%d]: cache %s\n", getpid (), tmpFile))
-if ((fp = fopen (tmpFile, "rb")))
-{
-fclose (fp);
-result = tmpFile;
-}
-else
+void mgPCMPlayer::CheckImage( string filename )
{
-if ((fp = fopen (fileName, "rb")))
-{
-fclose (fp);
-d(printf("mp3[%d]: image %s found\n", getpid (), fileName))
-sprintf ((char *) tmp, "image_convert.sh \"%s\" \"%s\"", fileName, tmpFile);
-system ((const char*) tmp);
-result = tmpFile;
-}
-}
-fp = fopen ("/tmp/vdr_mp3_current_image.txt", "w");
-fprintf (fp, "%s\n", fileName);
-fclose (fp);
-return result;
-}
+ FILE *fp;
+ string tmpFile;
-char *cMP3Player::LoadImage(const char *fullname)
-{
-size_t i, j = strlen (MP3Sources.GetSource()->BaseDir()) + 1;
-char imageFile[1024];
-static char mpgFile[1024];
-char *p, *q = NULL;
-char *imageSuffixes[] = { "png", "gif", "jpg" };
-
-d(printf("mp3[%d]: checking %s for images\n", getpid (), fullname))
-strcpy (imageFile, fullname);
-strcpy (mpgFile, "");
-//
-// track specific image, e.g. <song>.jpg
-//
-p = strrchr (imageFile, '.');
-if (p)
-{
-for (i = 0; i < sizeof (imageSuffixes) / sizeof (imageSuffixes[0]); i++)
-{
-strcpy (p + 1, imageSuffixes[i]);
-d(printf("mp3[%d]: %s\n", getpid (), imageFile))
-q = CheckImage (imageFile, j);
-if (q)
-{
-strcpy (mpgFile, q);
-}
-}
-}
-//
-// album specific image, e.g. cover.jpg in song directory
-//
-if (!strlen (mpgFile))
-{
-p = strrchr (imageFile, '/');
-if (p)
-{
-strcpy (p + 1, "cover.");
-p += 6;
-for (i = 0; i < sizeof (imageSuffixes) / sizeof (imageSuffixes[0]); i++)
-{
-strcpy (p + 1, imageSuffixes[i]);
-d(printf("mp3[%d]: %s\n", getpid (), imageFile))
-q = CheckImage (imageFile, j);
-if (q)
-{
-strcpy (mpgFile, q);
-}
-}
-}
-}
-//
-// artist specific image, e.g. artist.jpg in artist directory
-//
-if (!strlen (mpgFile))
-{
-p = strrchr (imageFile, '/');
-if (p)
-{
-*p = '\0';
-p = strrchr (imageFile, '/');
-}
-if (p)
-{
-strcpy (p + 1, "artist.");
-p += 7;
-for (i = 0; i < sizeof (imageSuffixes) / sizeof (imageSuffixes[0]); i++)
-{
-strcpy (p + 1, imageSuffixes[i]);
-d(printf("mp3[%d]: %s\n", getpid (), imageFile))
-q = CheckImage (imageFile, j);
-if (q)
-{
-strcpy (mpgFile, q);
-}
-}
-}
-}
-//
-// default background image
-//
-if (!strlen (mpgFile))
-{
-for (i = 0; i < sizeof (imageSuffixes) / sizeof (imageSuffixes[0]); i++)
-{
-sprintf (imageFile, "%s/background.%s", MP3Setup.ImageCacheDir, imageSuffixes[i]);
-d(printf("mp3[%d]: %s\n", getpid (), imageFile))
-q = CheckImage (imageFile, strlen(MP3Setup.ImageCacheDir) + 1);
-if (q)
-{
-strcpy (mpgFile, q);
-}
-}
-}
-if (!strlen (mpgFile))
-{
-sprintf (mpgFile, "%s/background.mpg", MP3Setup.ImageCacheDir);
-}
-return mpgFile;
-}
+ // determine the filename of a (to be) cached .mpg file
+ unsigned dotpos = filename.rfind( ".", filename.length() );
-void mgPCMPlayer::ShowImage (char *file)
-{
-uchar *buffer;
-int fd;
-struct stat st;
-struct video_still_picture sp;
+ // assemble path from relative paths
+ tmpFile = string( the_setup.ImageCacheDir ) + string( "/" ) + filename.substr( 0, dotpos ) + string( ".mpg" );
+
+ // now filename and tmpFile are absolute path specs
+
+ // if this cached file can be opened, use it
+ if( (fp = fopen( tmpFile.c_str(), "rb" )) )
+ {
+ fclose(fp);
+ }
+ else
+ {
+ // otherwise run the image conversion
+ if( (fp = fopen( filename.c_str(), "rb" )) )
+ {
+ char *tmp;
+ // filename can be opened
+ fclose (fp);
+
+ cout << "Converting " << filename << " to " << tmpFile << endl << flush;
+ asprintf( &tmp, "image_convert.sh \"%s\" \"%s\"", filename.c_str(), tmpFile.c_str() );
+ system( (const char*) tmp );
+ delete tmp;
+ }
+ }
+
+ m_current_image = tmpFile;
-if ((fd = open (file, O_RDONLY)) >= 0)
-{
-d(printf("mp3[%d]: cover still picture %s\n", getpid (), file))
-fstat (fd, &st);
-sp.iFrame = (char *) malloc (st.st_size);
-if (sp.iFrame)
-{
-sp.size = st.st_size;
-if (read (fd, sp.iFrame, sp.size) > 0)
-{
-buffer = (uchar *) sp.iFrame;
-d(printf("mp3[%d]: new image frame (size %d)\n", getpid(), sp.size))
-if(MP3Setup.UseDeviceStillPicture)
-DeviceStillPicture (buffer, sp.size);
-else
-{
-for (int i = 1; i <= 25; i++)
-{
-send_pes_packet (buffer, sp.size, i);
-}
-}
-}
-free (sp.iFrame);
-}
-else
-{
-esyslog ("mp3[%d]: cannot allocate memory (%d bytes) for still image",
-getpid(), (int) st.st_size);
-}
-close (fd);
}
-else
+
+void mgPCMPlayer::ShowImage( )
{
-esyslog ("mp3[%d]: cannot open image file '%s'",
-getpid(), file);
-}
+ // show image .mpg referred in m_current_image
+
+ uchar *buffer;
+ int fd;
+ struct stat st;
+ struct video_still_picture sp;
+
+ if( (fd = open( m_current_image.c_str(), O_RDONLY ) ) >= 0 )
+ {
+ fstat (fd, &st);
+ sp.iFrame = (char *) malloc (st.st_size);
+ if( sp.iFrame )
+ {
+ sp.size = st.st_size;
+ if( read (fd, sp.iFrame, sp.size) > 0 )
+ {
+ buffer = (uchar *) sp.iFrame;
+
+
+ if( the_setup.UseDeviceStillPicture )
+ {
+ sleep(1);
+ DeviceStillPicture( buffer, sp.size );
+ }
+ else
+ {
+ for (int i = 1; i <= 25; i++)
+ {
+ send_pes_packet (buffer, sp.size, i);
+ }
+ }
+ }
+ free (sp.iFrame);
+ }
+ else
+ {
+ esyslog ("mp3[%d]: cannot allocate memory (%d bytes) for still image",
+ getpid(), (int) st.st_size);
+ }
+ close (fd);
+ }
+ else
+ {
+ esyslog ("mp3[%d]: cannot open image file '%s'",
+ getpid(), m_current_image.c_str() );
+ }
}
void mgPCMPlayer::send_pes_packet(unsigned char *data, int len, int timestamp)
{
#define PES_MAX_SIZE 2048
-int ptslen = timestamp ? 5 : 1;
-static unsigned char pes_header[PES_MAX_SIZE];
-
-pes_header[0] = pes_header[1] = 0;
-pes_header[2] = 1;
-pes_header[3] = 0xe0;
+ int ptslen = timestamp ? 5 : 1;
+ static unsigned char pes_header[PES_MAX_SIZE];
-while(len > 0)
-{
-int payload_size = len;
-if(6 + ptslen + payload_size > PES_MAX_SIZE)
-payload_size = PES_MAX_SIZE - (6 + ptslen);
+ pes_header[0] = pes_header[1] = 0;
+ pes_header[2] = 1;
+ pes_header[3] = 0xe0;
-pes_header[4] = (ptslen + payload_size) >> 8;
-pes_header[5] = (ptslen + payload_size) & 255;
-
-if(ptslen == 5)
-{
-int x;
-x = (0x02 << 4) | (((timestamp >> 30) & 0x07) << 1) | 1;
-pes_header[8] = x;
-x = ((((timestamp >> 15) & 0x7fff) << 1) | 1);
-pes_header[7] = x >> 8;
-pes_header[8] = x & 255;
-x = ((((timestamp) & 0x7fff) < 1) | 1);
-pes_header[9] = x >> 8;
-pes_header[10] = x & 255;
-} else
-{
-pes_header[6] = 0x0f;
-}
-
-memcpy(&pes_header[6 + ptslen], data, payload_size);
+ while(len > 0)
+ {
+ int payload_size = len;
+ if(6 + ptslen + payload_size > PES_MAX_SIZE)
+ payload_size = PES_MAX_SIZE - (6 + ptslen);
+
+ pes_header[4] = (ptslen + payload_size) >> 8;
+ pes_header[5] = (ptslen + payload_size) & 255;
+
+ if(ptslen == 5)
+ {
+ int x;
+ x = (0x02 << 4) | (((timestamp >> 30) & 0x07) << 1) | 1;
+ pes_header[8] = x;
+ x = ((((timestamp >> 15) & 0x7fff) << 1) | 1);
+ pes_header[7] = x >> 8;
+ pes_header[8] = x & 255;
+ x = ((((timestamp) & 0x7fff) < 1) | 1);
+ pes_header[9] = x >> 8;
+ pes_header[10] = x & 255;
+ }
+ else
+ {
+ pes_header[6] = 0x0f;
+ }
+
+ memcpy(&pes_header[6 + ptslen], data, payload_size);
#if VDRVERSNUM >= 10318
-PlayPes(pes_header, 6 + ptslen + payload_size);
+ PlayPes(pes_header, 6 + ptslen + payload_size);
#else
-PlayVideo(pes_header, 6 + ptslen + payload_size);
+ PlayVideo(pes_header, 6 + ptslen + payload_size);
#endif
-
-len -= payload_size;
-data += payload_size;
-ptslen = 1;
+
+ len -= payload_size;
+ data += payload_size;
+ ptslen = 1;
+ }
}
+
+void mgPCMPlayer::TransferImageTFT( string cover )
+{
+ cPlugin * graphtft = cPluginManager::GetPlugin("graphtft");
+
+ if( graphtft )
+ {
+ graphtft->SetupParse( "CoverImage", cover.c_str() );
+ }
}
-*/
// --- mgPlayerControl -------------------------------------------------------
@@ -1164,7 +1149,7 @@ mgPCMPlayer (plist))
m_szLastShowStatusMsg = NULL;
-// Notify all cStatusMonitor
+ // Notify all cStatusMonitor
StatusMsgReplaying ();
}
@@ -1191,9 +1176,16 @@ bool mgPlayerControl::Active (void)
}
+bool mgPlayerControl::Playing (void)
+{
+ return player && player->Playing ();
+}
+
+
void
mgPlayerControl::Stop (void)
{
+ cStatus::MsgReplaying( this, NULL );
if (player)
{
delete player;
@@ -1243,11 +1235,11 @@ mgPlayerControl::Backward (void)
void
-mgPlayerControl::SkipSeconds (int Seconds)
+mgPlayerControl::SkipSeconds(int Seconds)
{
if (player)
{
- player->SkipSeconds (Seconds);
+ player->SkipSeconds(Seconds);
}
}
@@ -1348,7 +1340,14 @@ mgPlayerControl::ShowContents ()
m_menu->SetItem (buf, 3, false, false);
free (buf);
}
- if (num_items > 4)
+ if( num_items > 4 )
+ {
+ asprintf (&buf, "Year:\t%d",
+ player->getCurrent ()->getYear () );
+ m_menu->SetItem (buf, 4, false, false);
+ free (buf);
+ }
+ if (num_items > 5)
{
int len = player->getCurrent ()->getDuration ();
asprintf (&buf, "Length:\t%s",
@@ -1357,30 +1356,30 @@ mgPlayerControl::ShowContents ()
#else
IndexToHMSF (SecondsToFrames (len)));
#endif
- m_menu->SetItem (buf, 4, false, false);
+ m_menu->SetItem (buf, 5, false, false);
free (buf);
}
- if (num_items > 5)
+ if (num_items > 6)
{
asprintf (&buf, "Bit rate:\t%s",
player->getCurrent ()->getBitrate ().c_str ());
- m_menu->SetItem (buf, 5, false, false);
+ m_menu->SetItem (buf, 6, false, false);
free (buf);
}
- if (num_items > 6)
+ if (num_items > 7)
{
int sr = player->getCurrent ()->getSampleRate ();
asprintf (&buf, "Sampling rate:\t%d", sr);
- m_menu->SetItem (buf, 6, false, false);
+ m_menu->SetItem (buf, 7, false, false);
free (buf);
}
- if (num_items > 6)
+ if (num_items > 8)
{
string sf = player->getCurrent ()->getSourceFile ();
char *p = strrchr(sf.c_str(),'/');
asprintf (&buf, "File name:\t%s", p+1);
- m_menu->SetItem (buf, 7, false, false);
+ m_menu->SetItem (buf, 8, false, false);
free (buf);
}
}
@@ -1397,7 +1396,7 @@ mgPlayerControl::ShowContents ()
int cur = list->getItemPosition ();
for (int i = 0; i < num_items; i++)
{
- mgContentItem *item = list->getItem (cur - 3 + i);
+ mgItemGd *item = dynamic_cast<mgItemGd*>(list->getItem (cur - 3 + i));
if (item)
{
char *buf;
@@ -1433,8 +1432,10 @@ mgPlayerControl::ShowProgress ()
{
total_frames = SecondsToFrames (list->getLength ());
current_frame += SecondsToFrames (list->getCompletedLength ());
- asprintf (&buf, "%s (%d/%d)", list->getListname ().c_str (),
- list->getItemPosition () + 1, list->getNumItems ());
+ asprintf (&buf, "(%d/%d) %s:%s",
+ list->getItemPosition () + 1, list->items().size(),
+ player->getCurrent ()->getArtist ().c_str (),
+ player->getCurrent ()->getTitle ().c_str ());
}
}
else
@@ -1572,17 +1573,26 @@ eOSState mgPlayerControl::ProcessKey (eKeys key)
case kUp:
{
if (m_visible && !m_progress_view && !m_track_view)
- Backward();
+ {
+ Backward();
+ }
else
- Forward ();
+ {
+ Forward ();
+ }
+ Display();
}
break;
case kDown:
{
if (m_visible && !m_progress_view && !m_track_view)
- Forward ();
+ {
+ Forward ();
+ }
else
- Backward();
+ {
+ Backward();
+ }
}
break;
case kRed:
@@ -1686,10 +1696,10 @@ eOSState mgPlayerControl::ProcessKey (eKeys key)
case kStop:
case kBlue:
{
- InternalHide ();
- Stop ();
+ InternalHide ();
+ Stop ();
- return osEnd;
+ return osEnd;
}
break;
case kOk:
@@ -1708,6 +1718,16 @@ eOSState mgPlayerControl::ProcessKey (eKeys key)
return osEnd;
}
break;
+ case kLeft:
+ {
+ SkipSeconds( -60 );
+ Display();
+ } break;
+ case kRight:
+ {
+ SkipSeconds( 60 );
+ Display();
+ } break;
default:
{
return osUnknown;
@@ -1723,12 +1743,19 @@ mgPlayerControl::StatusMsgReplaying ()
{
MGLOG ("mgPlayerControl::StatusMsgReplaying()");
char *szBuf = NULL;
- if (player && player->getCurrent () && player->getPlaylist ())
+ mgSelection * sel;
+ mgItemGd * item;
+ if (player)
+ {
+ sel = player->getPlaylist();
+ item = dynamic_cast<mgItemGd*>(player->getCurrent ());
+ }
+ if (player && sel && item)
{
char cLoopMode;
char cShuffle;
- switch (player->getPlaylist ()->getLoopMode ())
+ switch (sel->getLoopMode ())
{
default:
case mgSelection::LM_NONE:
@@ -1742,7 +1769,7 @@ mgPlayerControl::StatusMsgReplaying ()
break;
}
- switch (player->getPlaylist ()->getShuffleMode ())
+ switch (sel->getShuffleMode ())
{
default:
case mgSelection::SM_NONE:
@@ -1756,27 +1783,24 @@ mgPlayerControl::StatusMsgReplaying ()
break;
}
- mgContentItem *tmp = player->getCurrent ();
- if (tmp == NULL)
- mgError("mgPlayerControl::StatusMsgReplaying: getCurrent() is NULL");
- if (tmp->getArtist ().length () > 0)
+ if (item->getArtist ().length () > 0)
{
asprintf (&szBuf, "[%c%c] (%d/%d) %s - %s",
cLoopMode,
cShuffle,
- player->getPlaylist ()->getItemPosition () + 1,
- player->getPlaylist ()->getNumItems (),
- player->getCurrent ()->getArtist ().c_str (),
- player->getCurrent ()->getTitle ().c_str ());
+ sel->getItemPosition () + 1,
+ sel->items().size(),
+ item->getArtist ().c_str (),
+ item->getTitle ().c_str ());
}
else
{
asprintf (&szBuf, "[%c%c] (%d/%d) %s",
cLoopMode,
cShuffle,
- player->getPlaylist ()->getItemPosition () + 1,
- player->getPlaylist ()->getNumItems (),
- player->getCurrent ()->getTitle ().c_str ());
+ sel->getItemPosition () + 1,
+ sel->items().size(),
+ item->getTitle ().c_str ());
}
}
else
diff --git a/vdr_player.h b/vdr_player.h
index fd89db5..9cb1d6b 100644
--- a/vdr_player.h
+++ b/vdr_player.h
@@ -78,6 +78,9 @@ class mgPlayerControl:public cControl
//! \brief indicate whether the corresponding player is active
bool Active ();
+//! \brief indicate whether the corresponding player is playing
+ bool Playing ();
+
//! \brief stop the corresponding player
void Stop ();
diff --git a/vdr_setup.c b/vdr_setup.c
index d8facbe..0e28a91 100644
--- a/vdr_setup.c
+++ b/vdr_setup.c
@@ -29,31 +29,31 @@
mgMenuSetup::mgMenuSetup ()
{
SetSection (tr ("Muggle"));
-
+
Add (new
- cMenuEditBoolItem (tr ("Setup.Muggle$Initial loop mode"),
- &the_setup.InitLoopMode));
+ cMenuEditBoolItem (tr ("Setup.Muggle$Initial loop mode"),
+ &the_setup.InitLoopMode));
Add (new
cMenuEditBoolItem (tr ("Setup.Muggle$Initial shuffle mode"),
&the_setup.InitShuffleMode));
Add (new
- cMenuEditBoolItem (tr ("Setup.Muggle$Audio mode"), &the_setup.AudioMode,
- tr ("Round"), tr ("Dither")));
+ cMenuEditBoolItem (tr ("Setup.Muggle$Audio mode"), &the_setup.AudioMode,
+ tr ("Round"), tr ("Dither")));
Add (new
- cMenuEditBoolItem (tr ("Setup.Muggle$Use 48kHz mode only"),
- &the_setup.Only48kHz));
+ cMenuEditBoolItem (tr ("Setup.Muggle$Use 48kHz mode only"),
+ &the_setup.Only48kHz));
Add (new
- cMenuEditIntItem (tr ("Setup.Muggle$Display mode"),
- &the_setup.DisplayMode, 1, 3));
+ cMenuEditIntItem (tr ("Setup.Muggle$Display mode"),
+ &the_setup.DisplayMode, 1, 3));
Add (new
- cMenuEditBoolItem (tr ("Setup.Muggle$Background mode"),
- &the_setup.BackgrMode, tr ("Black"), tr ("Live")));
+ cMenuEditIntItem (tr ("Setup.Muggle$Background mode"),
+ &the_setup.BackgrMode, 1, 3 ) );
Add (new
- cMenuEditIntItem (tr ("Setup.Muggle$Normalizer level"),
- &the_setup.TargetLevel, 0, MAX_TARGET_LEVEL));
+ cMenuEditIntItem (tr ("Setup.Muggle$Normalizer level"),
+ &the_setup.TargetLevel, 0, MAX_TARGET_LEVEL));
Add (new
- cMenuEditIntItem (tr ("Setup.Muggle$Limiter level"),
- &the_setup.LimiterLevel, MIN_LIMITER_LEVEL, 100));
+ cMenuEditIntItem (tr ("Setup.Muggle$Limiter level"),
+ &the_setup.LimiterLevel, MIN_LIMITER_LEVEL, 100));
Add (new
cMenuEditBoolItem (tr ("Setup.Muggle$Delete stale references"),
&the_setup.DeleteStaleReferences));
diff --git a/vdr_sound.c b/vdr_sound.c
index 7c4304f..e7f93e9 100644
--- a/vdr_sound.c
+++ b/vdr_sound.c
@@ -1,5 +1,5 @@
/*!
- * \file vdr_setup.c
+ * \file vdr_sound.c
* \brief Sound manipulation classes for a VDR media plugin (muggle)
*
* \version $Revision: 1.2 $
diff --git a/vdr_stream.c b/vdr_stream.c
index 872ac4b..f7366de 100644
--- a/vdr_stream.c
+++ b/vdr_stream.c
@@ -14,6 +14,8 @@
* (C) 2001-2003 Stefan Huelswitt <huels@iname.com>
*/
+#include "mg_tools.h"
+
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
@@ -22,8 +24,6 @@
#include <interface.h>
-#include "mg_tools.h"
-
// #include "setup-mp3.h"
#include "vdr_stream.h"
#include "vdr_network.h"