diff options
Diffstat (limited to 'mg_db_gd_mysql.c')
-rw-r--r-- | mg_db_gd_mysql.c | 668 |
1 files changed, 668 insertions, 0 deletions
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)"; +} + |