Subversion Repositories mdb

Rev

Rev 31 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2015 by Andreas Theofilu <andreas@theosys.at>
 *
 * All rights reserved. No warranty, explicit or implicit, provided.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Andreas Theofilu and his suppliers, if any.
 * The intellectual and technical concepts contained
 * herein are proprietary to Andreas Theofilu and its suppliers and
 * may be covered by European and Foreign Patents, patents in process,
 * and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Andreas Theofilu.
 */
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <ctype.h>
#include <math.h>
#include <syslog.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sqlite3.h>
#include <id3.h>
#include "config.h"
#include "helplib.h"
#include "list.h"
#include "user.h"
#include "play.h"

struct callPars
{
        int s1;
        int start;
        int length;
        char *type;
};

int globalLine;
int globalPos;

static int listCallback(int s1, char *type, int line, sqlite3_stmt *res);
static int folderCallback(void *hint, int argc, char **argv, char **azColName);
int countPlaylists();

int listSongs(int s1, char *p_type, int start, int length)
{
        char query[1024], hv0[128];
        char fname[256];
        sqlite3 *db;
        sqlite3_stmt *res;
        int rc, pos, line;

        strcpy(fname, configs.home);
        strcat(fname, MUSICDB);
        
        rc = sqlite3_open(fname, &db);
        
        if (rc)
        {
                syslog(LOG_WARNING, "Error opening database %s: %s", fname, sqlite3_errmsg(db));
                strcpy(query, "ERROR:LIST:Error opening database;");
                write (s1, query, strlen(query));
                return FALSE;
        }

        if (configs.debug)
                syslog(LOG_DEBUG, "Listing %s ...", p_type);

        /* First count the future result set */
        if (strcmp(p_type, "QUEUE") && strcmp(p_type, "PLAYLIST"))
        {
                strcpy (query, "select count(*) as cnt from musicdb");

                if (sqlite3_prepare(db, query, -1, &res, NULL) != SQLITE_OK)
                {
                        syslog(LOG_DAEMON, "Error preparing SQL statement [%s]: %s", query, sqlite3_errmsg(db));
                        strcpy(query, "ERROR:USER:Error preparing a SQL statement;");
                        write(s1, query, strlen(query));
                        return FALSE;
                }

                if (sqlite3_step(res) == SQLITE_ROW)
                        pos = sqlite3_column_int(res, 0);
                else
                        pos = 0;

                sqlite3_finalize(res);
        }
        else if (!strcmp(p_type, "QUEUE"))
        {
                if (queueTotal <= 0)
                {
                        int fd;

                        strcpy(fname, configs.home);
                        strcat(fname, NOWPLAY);
                        sleep(1);
                        
                        if (queueTotal <= 0)
                        {
                                if ((fd = open(fname, O_RDONLY)) == -1)
                                {
                                        syslog(LOG_WARNING, "Error opening queue %s: %s", fname, strerror(errno));
                                        strcpy(query, "ERROR:LIST:Error opening queue file;");
                                        write(s1, query, strlen(query));
                                        return FALSE;
                                }

                                scanQueue(fd);
                                close(fd);
                        }
                }

                pos = queueTotal;
        }
        else
                pos = countPlaylists();

        playQuiet = TRUE;
        sprintf(hv0, "TOTAL:%d;", pos);
        write(s1, hv0, strlen(hv0));

        strcpy (query, "select id, title, interpret, album, genre, cover from \"main\".\"musicdb\" ");
        strcat (query, "order by ");
        
        if (strcmp(p_type, "TITLE") == 0)
                strcat (query, "title");
        else if (strcmp(p_type, "ARTIST") == 0)
                strcat (query, "interpret");
        else if (strcmp(p_type, "ALBUM") == 0)
                strcat (query, "album");
        else if (strcmp(p_type, "GENRE") == 0)
                strcat (query, "genre");
        else if (strcmp(p_type, "QUEUE") == 0)
        {
                sqlite3_close(db);
                playQuiet = FALSE;
                return listQueue(s1, start, length);
        }
        else if (strcmp(p_type, "PLAYLIST") == 0)
        {
                sqlite3_close(db);
                playQuiet = FALSE;
                return listPlaylists(s1, start, length);
        }
        else if (strcmp(p_type, "USERS") == 0)
        {
                sqlite3_close(db);
                playQuiet = FALSE;
                return listUsers(s1, start, length);
        }
        else            /* No or unknown type */
        {
                strcpy (query, "ERROR:LIST:Missing type;");
                write(s1, query, strlen(query));
                sqlite3_close(db);
                playQuiet = FALSE;
                return FALSE;
        }

        /* Tell client which page to show */
        sprintf(hv0, "PAGE:%s;", p_type);
        write (s1, hv0, strlen (hv0));

        /* Retrieve data from database */
        line = pos = 0;

        if (sqlite3_prepare(db, query, -1, &res, NULL) != SQLITE_OK)
        {
                syslog(LOG_DAEMON, "Error preparing SQL statement [%s]: %s", query, sqlite3_errmsg(db));
                strcpy(query, "ERROR:USER:Error preparing a SQL statement;");
                write(s1, query, strlen(query));
                playQuiet = FALSE;
                return FALSE;
        }

        while ((rc = sqlite3_step(res)) == SQLITE_ROW)
        {
                pos++;

                if (pos >= start && pos < (start + length))
                {
                        line++;
                        listCallback(s1, p_type, line, res);
                }

                if (pos >= (start + length))
                        break;
        }

        sqlite3_finalize(res);
        sqlite3_close(db);
        playQuiet = FALSE;
        return TRUE;
}

int listQueue(int s1, int start, int length)
{
        int fd, line;
        char fname[256], hv0[256], buffer[8192];
        char *title, *artist, *album, *cover;
        int pos;
        QUEUE *act;

        if (pqueue == NULL)
        {
                strcpy (fname, configs.home);
                strcat (fname, NOWPLAY);

                if (access(fname, R_OK))
                {
                        strcpy (hv0, "ERROR:LIST:No or empty queue;");
                        write (s1, hv0, strlen(hv0));
                        return FALSE;
                }

                if ((fd = open(fname, O_RDONLY)) <= 0)
                {
                        syslog(LOG_WARNING, "Error opening file %s: %s", fname, strerror(errno));
                        strcpy(hv0, "ERROR:LIST:Error opening queue;");
                        write(s1, hv0, strlen(hv0));
                        return FALSE;
                }

                scanQueue(fd);
                close(fd);
        }

        sprintf(hv0, "PAGE:QUEUE;TOTAL:%d;", queueTotal);
        write (s1, hv0, strlen(hv0));
        act = pqueue;
        pos = line = 1;
        playQuiet = TRUE;

        while (act)
        {
                if (pos < start)
                {
                        act = act->next;
                        pos++;
                        continue;
                }
                else if (pos >= (start + length))
                        break;

                title = urlencode(act->title);
                artist = urlencode(act->artist);
                album = urlencode(act->album);
                cover = urlencode(act->cover);
                sprintf(buffer, "LINE:QUEUE:%d:%d:", act->id, line);
                
                if (title != NULL)
                {
                        strcat(buffer, title);
                        free(title);
                }
                else
                        strcat(buffer, act->title);

                strcat(buffer, ":");

                if (artist != NULL)
                {
                        strcat(buffer, artist);
                        free(artist);
                }
                else
                        strcat(buffer, act->artist);

                strcat(buffer, ":");

                if (album != NULL)
                {
                        strcat(buffer, album);
                        free(album);
                }
                else
                        strcat(buffer, act->album);

                strcat(buffer, ":");
                strcat(buffer, act->genre);
                strcat(buffer, ":");

                if (cover != NULL)
                {
                        strcat(buffer, cover);
                        free(cover);
                }
                else
                        strcat(buffer, act->cover);

                strcat(buffer, ";");
                write(s1, buffer, strlen(buffer));
                act = act->next;
                pos++;
                line++;
        }

        playQuiet = FALSE;
        return TRUE;
}

int listFolders(int s1, char *p_type, int start, int length)
{
        char query[1024], field[16];
        char fname[256];
        sqlite3 *db;
        char *zErrMsg = 0;
        int rc;
        struct callPars cp;
        
        strcpy(fname, configs.home);
        strcat(fname, MUSICDB);
        
        rc = sqlite3_open(fname, &db);
        
        if (rc)
        {
                syslog(LOG_WARNING, "Error opening database %s: %s", fname, sqlite3_errmsg(db));
                strcpy(query, "ERROR:FOLDER:Error opening database;");
                write (s1, query, strlen(query));
                return FALSE;
        }
                
        strcpy (query, "select distinct ");
        
        if (strcmp(p_type, "TITLE") == 0)
                strcpy (field, "title");
        else if (strcmp(p_type, "ARTIST") == 0)
                strcpy (field, "interpret");
        else if (strcmp(p_type, "ALBUM") == 0)
                strcpy (field, "album");
        else if (strcmp(p_type, "GENRE") == 0)
                strcpy (field, "genre");
        else            /* No or unknown type */
        {
                strcpy (query, "ERROR:FOLDER:Missing type;");
                write(s1, query, strlen(query));
                sqlite3_close(db);
                return FALSE;
        }

        strcat (query, field);
        strcat (query, " from \"main\".\"musicdb\" order by ");
        strcat (query, field);

        cp.s1 = s1;
        cp.type = p_type;
        cp.start = start;
        cp.length = length;
        globalLine = 0;
        globalPos = 0;
        playQuiet = TRUE;

        if ((rc = sqlite3_exec(db, query, folderCallback, (void *)&cp, &zErrMsg)) != SQLITE_OK)
        {
                syslog(LOG_WARNING, "SQL error [%s]: %s", query, zErrMsg);
                sqlite3_free(zErrMsg);
                sqlite3_close(db);
                strcpy(query, "ERROR:FOLDER:SQL error;");
                write (s1, query, strlen(query));
                return FALSE;
        }

        if (globalPos >= 0)
        {
                sprintf(query, "TOTAL:%d;", globalPos + 1);
                write (s1, query, strlen(query));
        }

        sqlite3_close(db);
        playQuiet = FALSE;
        return TRUE;
}

static int listCallback(int s1, char *type, int line, sqlite3_stmt *res)
{
        int id;
        char id3_title[256], id3_artist[256], id3_album[256], id3_genre[256], id3_cover[256];
        char buffer[8192];
        char *title, *artist, *album;

        memset(id3_title, 0, sizeof(id3_title));
        memset(id3_artist, 0, sizeof(id3_artist));
        memset(id3_album, 0, sizeof(id3_album));
        memset(id3_genre, 0, sizeof(id3_genre));
        memset(id3_cover, 0, sizeof(id3_cover));
        id = -1;
        id = sqlite3_column_int(res, 0);
        strncpy(id3_title, (const char *)sqlite3_column_text(res, 1), sizeof(id3_title));
        strncpy(id3_artist, (const char *)sqlite3_column_text(res, 2), sizeof(id3_artist));
        strncpy(id3_album, (const char *)sqlite3_column_text(res, 3), sizeof(id3_album));
        strncpy(id3_genre, (const char *)sqlite3_column_text(res, 4), sizeof(id3_genre));
        strncpy(id3_cover, (const char *)sqlite3_column_text(res, 5), sizeof(id3_cover));

        title = urlencode(id3_title);
        artist = urlencode(id3_artist);
        album = urlencode(id3_album);

        sprintf(buffer, "LINE:%s:%d:%d:", type, id, line);

        if (title != NULL)
        {
                strcat(buffer, title);
                free(title);
        }
        else
                strcat(buffer, id3_title);

        strcat(buffer, ":");

        if (artist != NULL)
        {
                strcat(buffer, artist);
                free(artist);
        }
        else
                strcat(buffer, id3_artist);

        strcat(buffer, ":");

        if (album != NULL)
        {
                strcat(buffer, album);
                free(album);
        }
        else
                strcat(buffer, id3_album);

        strcat(buffer, ":");
        strcat(buffer, id3_genre);
        strcat(buffer, ":");
        strcat(buffer, id3_cover);
        strcat(buffer, ";");
        write(s1, buffer, strlen(buffer));
        return 0;
}

static int folderCallback(void *hint, int argc, char **argv, char **azColName)
{
        int i;
        char id3_buffer[256];
        char buffer[8192];
        char *buf;
        struct callPars *cp;

        globalPos++;
        cp = (struct callPars *)hint;
        memset(id3_buffer, 0, sizeof(id3_buffer));

        if (globalPos < cp->start || globalPos >= (cp->start + cp->length))
                return 0;

        for(i = 0; i < argc; i++)
        {
                if (strcasecmp(azColName[i], "title") == 0 || strcasecmp(azColName[i], "interpret") == 0 ||
                        strcasecmp(azColName[i], "album") == 0 || strcasecmp(azColName[i], "genre") == 0)
                        if (argv[i])
                                strncpy(id3_buffer, argv[i], sizeof(id3_buffer));
        }

        buf = urlencode(id3_buffer);
        globalLine++;

        sprintf(buffer, "FOLDER:%s:%d:", cp->type, globalLine);

        if (buf != NULL)
        {
                strcat(buffer, buf);
                free(buf);
        }
        else
                strcat(buffer, id3_buffer);

        strcat(buffer, ";");
        write(cp->s1, buffer, strlen(buffer));
        return 0;
}

int listFolderContent(int s1, char *p_type, char *name, int start, int length)
{
        char query[1024], hv0[128], field[32];
        char fname[256];
        sqlite3 *db;
        sqlite3_stmt *res;
        int rc, pos, line;
        
        strcpy(fname, configs.home);
        strcat(fname, MUSICDB);
        
        if (!strcasecmp(p_type, "TITLE"))
                strcpy (field, "title");
        else if (!strcasecmp(p_type, "ARTIST"))
                strcpy (field, "interpret");
        else if (!strcasecmp(p_type, "ALBUM"))
                strcpy (field, "album");
        else if (!strcasecmp(p_type, "GENRE"))
                strcpy (field, "genre");
        else
        {
                strcpy (hv0, "ERROR:LIST:Missing valid type;");
                write(s1, hv0, strlen(hv0));
                return FALSE;
        }

        rc = sqlite3_open(fname, &db);
        
        if (rc)
        {
                syslog(LOG_WARNING, "Error opening database %s: %s", fname, sqlite3_errmsg(db));
                strcpy(query, "ERROR:LIST:Error opening database;");
                write (s1, query, strlen(query));
                return FALSE;
        }
        
        /* First count the future result set */
        sprintf (query, "select count(*) as cnt from musicdb where %s = \"%s\"", field, name);
        
        if (sqlite3_prepare(db, query, -1, &res, NULL) != SQLITE_OK)
        {
                syslog(LOG_DAEMON, "Error preparing SQL statement [%s]: %s", query, sqlite3_errmsg(db));
                strcpy(query, "ERROR:USER:Error preparing a SQL statement;");
                write(s1, query, strlen(query));
                return FALSE;
        }
        
        if (sqlite3_step(res) == SQLITE_ROW)
                pos = sqlite3_column_int(res, 0);
        else
                pos = 0;

        playQuiet = TRUE;
        sprintf(hv0, "TOTAL:%d;", pos);
        write(s1, hv0, strlen(hv0));
        sqlite3_finalize(res);
        
        sprintf (query, "select id, title, interpret, album, genre, cover from \"main\".\"musicdb\" where %s = \"%s\" order by title", field, name);

        /* Tell client which page to show */
        sprintf(hv0, "PAGE:%s;FOLDERNAME:%s;", p_type, name);
        write (s1, hv0, strlen (hv0));
        
        /* Retrieve data from database */
        line = pos = 1;
        
        if (sqlite3_prepare(db, query, -1, &res, NULL) != SQLITE_OK)
        {
                syslog(LOG_DAEMON, "Error preparing SQL statement [%s]: %s", query, sqlite3_errmsg(db));
                strcpy(query, "ERROR:LIST:Error preparing a SQL statement;");
                write(s1, query, strlen(query));
                playQuiet = FALSE;
                return FALSE;
        }
        
        while ((rc = sqlite3_step(res)) == SQLITE_ROW)
        {
                if (pos >= start && pos < (start + length))
                {
                        listCallback(s1, p_type, line, res);
                        line++;
                }
                
                if (pos >= (start + length))
                        break;
                
                pos++;
        }
        
        sqlite3_finalize(res);
        sqlite3_close(db);
        playQuiet = FALSE;
        return TRUE;
}

/*
 * Count the playlists of the users and return the result.
 */
int countPlaylists()
{
        USERS *act;
        int count;
        
        count = 0;
        act = userchain;
        
        while (act)
        {
                count++;
                act = act->next;
        }
        
        return count;
}

/*
 * List the playlists of the current user.
 */
int listPlaylists(int s1, int start, int length)
{
        USERS *act;
        char hv0[512], *user, *playlist;
        int pos, line;

        if (userchain == NULL)
        {
                strcpy(hv0, "ERROR:PLAYLIST:No user selected;");
                write(s1, hv0, strlen(hv0));
                return FALSE;
        }

        playQuiet = TRUE;
        strcpy(hv0, "PAGE:PLAYLIST;");
        write(s1, hv0, strlen(hv0));

        act = userchain;
        pos = line = 1;

        while (act)
        {
                if (pos < start)
                {
                        act = act->next;
                        continue;
                }

                if (pos >= (start + length))
                        break;

                user = urlencode(act->uname);
                playlist = urlencode(act->playlist);
                sprintf(hv0, "PLAYLIST:%d:%d:%s:%s;", line, act->id, user, playlist);
                write (s1, hv0, strlen(hv0));

                if (user)
                        free (user);

                if (playlist)
                        free (playlist);

                act = act->next;
                line++;
        }

        playQuiet = FALSE;
        return TRUE;
}

int listUserPlaylist(int s1, const char *user, const char *playlist, int start, int length)
{
        char query[1024], hv0[128], buffer[8192];
        char fname[256];
        sqlite3 *db;
        int rc, id, total, pos, line;
        sqlite3_stmt *res;
        char id3_title[256], id3_artist[256], id3_album[256], id3_genre[256], id3_cover[512];
        char *title, *artist, *album, *cover;
        USERS *act;

        if (playlist == NULL)
        {
                strcpy(hv0, "ERROR:LIST:Missing name of playlist;");
                write (s1, hv0, strlen(hv0));
                return FALSE;
        }

        /* Check current user */
        if (user != NULL)
        {
                if (userchain == NULL || strcmp(userchain->uname, user))
                {
                        if (!selectUser(s1, user))
                                return FALSE;
                }
        }

        /* Find the playlist */
        act = userchain;

        while (act)
        {
                if (!strcmp(act->playlist, playlist))
                        break;

                act = act->next;
        }

        if (act == NULL)
        {
                strcpy(hv0, "ERROR:LIST:Playlist not found;");
                write (s1, hv0, strlen(hv0));
                return FALSE;
        }

        strcpy(fname, configs.home);
        strcat(fname, MUSICDB);
        
        rc = sqlite3_open(fname, &db);
        
        if (rc)
        {
                syslog(LOG_WARNING, "Error opening database %s: %s", fname, sqlite3_errmsg(db));
                strcpy(query, "ERROR:USER:Error opening database;");
                write (s1, query, strlen(query));
                return FALSE;
        }

        playQuiet = TRUE;
        strcpy(hv0, "PAGE:PLAYLIST;");
        write(s1, hv0, strlen(hv0));

        sprintf(query, "select count(*) from \"main\".\"playlists\" where userid = %d", act->id);

        if (sqlite3_prepare(db, query, -1, &res, NULL) != SQLITE_OK)
        {
                syslog(LOG_DAEMON, "Error preparing SQL statement [%s]: %s", query, sqlite3_errmsg(db));
                strcpy(hv0, "ERROR:USER:Error preparing a SQL statement;");
                write(s1, hv0, strlen(hv0));
                playQuiet = FALSE;
                return FALSE;
        }
        
        if ((rc = sqlite3_step(res)) == SQLITE_ROW)
        {
                total = sqlite3_column_int(res, 0);
                sprintf(hv0, "TOTAL:%d;", total);
                write (s1, hv0, strlen(hv0));
        }

        sqlite3_finalize(res);
        strcpy (query, "select id, title, interpret, album, genre, cover from musicdb as a where ");
        strcat (query, "(select musicid from playlists as b where a.id = b.musicid and b.userid = ");
        sprintf(hv0, "%d", act->id);
        strcat (query, hv0);
        strcat (query, ")");

        if (sqlite3_prepare(db, query, -1, &res, NULL) != SQLITE_OK)
        {
                syslog(LOG_DAEMON, "Error preparing SQL statement [%s]: %s", query, sqlite3_errmsg(db));
                strcpy(hv0, "ERROR:USER:Error preparing a SQL statement;");
                write(s1, hv0, strlen(hv0));
                playQuiet = FALSE;
                return FALSE;
        }

        pos = 1;
        line = 0;
        
        while ((rc = sqlite3_step(res)) == SQLITE_ROW)
        {
                if (pos < start)
                {
                        pos++;
                        continue;
                }

                if (pos >= (start + length))
                        break;

                line++;
                memset(id3_title, 0, sizeof(id3_title));
                memset(id3_artist, 0, sizeof(id3_artist));
                memset(id3_album, 0, sizeof(id3_album));
                memset(id3_genre, 0, sizeof(id3_genre));
                memset(id3_cover, 0, sizeof(id3_cover));
                id = sqlite3_column_int(res, 0);
                strncpy(id3_title, (const char *)sqlite3_column_text(res, 1), sizeof(id3_title));
                strncpy(id3_artist, (const char *)sqlite3_column_text(res, 2), sizeof(id3_artist));
                strncpy(id3_album, (const char *)sqlite3_column_text(res, 3), sizeof(id3_album));
                strncpy(id3_genre, (const char *)sqlite3_column_text(res, 4), sizeof(id3_genre));
                strncpy(id3_cover, (const char *)sqlite3_column_text(res, 5), sizeof(id3_cover));
                title = urlencode(id3_title);
                artist = urlencode(id3_artist);
                album = urlencode(id3_album);
                cover = urlencode(id3_cover);

                if (title != NULL)
                {
                        strncpy(id3_title, title, sizeof(id3_title));
                        free(title);
                }

                if (artist != NULL)
                {
                        strncpy(id3_artist, artist, sizeof(id3_artist));
                        free(artist);
                }

                if (album != NULL)
                {
                        strncpy(id3_album, album, sizeof(id3_album));
                        free(album);
                }

                if (cover != NULL)
                {
                        strncpy(id3_cover, cover, sizeof(id3_cover));
                        free(cover);
                }

                sprintf (buffer, "LINE:PLAYLIST:%d:%d:%s:%s:%s:%s:%s;", id, line, id3_title, id3_artist, id3_album, id3_genre, id3_cover);
                write (s1, buffer, strlen(buffer));
                pos++;
        }
        
        sqlite3_finalize(res);
        sqlite3_close(db);
        playQuiet = FALSE;
        return TRUE;
}

int listUsers(int s1, int start, int length)
{
        char query[1024], hv0[256];
        char fname[256], uname[128], *un;
        sqlite3 *db;
        int rc, id, total, pos;
        sqlite3_stmt *res;
        
        rc = sqlite3_open(fname, &db);
        
        if (rc)
        {
                syslog(LOG_WARNING, "Error opening database %s: %s", fname, sqlite3_errmsg(db));
                strcpy(query, "ERROR:LIST:Error opening database;");
                write (s1, query, strlen(query));
                return FALSE;
        }
        
        sprintf(query, "select distinct count(*) from users order by uname");
        
        if (sqlite3_prepare(db, query, -1, &res, NULL) != SQLITE_OK)
        {
                syslog(LOG_DAEMON, "Error preparing SQL statement [%s]: %s", query, sqlite3_errmsg(db));
                strcpy(hv0, "ERROR:USER:Error preparing a SQL statement;");
                write(s1, hv0, strlen(hv0));
                return FALSE;
        }
        
        if ((rc = sqlite3_step(res)) == SQLITE_ROW)
        {
                total = sqlite3_column_int(res, 0);
                sprintf(hv0, "TOTAL:%d;", total);
                write (s1, hv0, strlen(hv0));
        }

        sqlite3_finalize(res);
        
        strcpy (query, "select distinct uname, id from users order by users");

        if (sqlite3_prepare(db, query, -1, &res, NULL) != SQLITE_OK)
        {
                syslog(LOG_DAEMON, "Error preparing SQL statement [%s]: %s", query, sqlite3_errmsg(db));
                strcpy(hv0, "ERROR:USER:Error preparing a SQL statement;");
                write(s1, hv0, strlen(hv0));
                return FALSE;
        }

        pos = 0;
        playQuiet = TRUE;

        while ((rc = sqlite3_step(res)) == SQLITE_ROW)
        {
                pos++;

                if (pos < start)
                        continue;

                if (pos >= (start + length))
                        break;

                strncpy(uname, (const char *)sqlite3_column_text(res, 0), sizeof(uname));
                id = sqlite3_column_int(res, 1);
                un = urlencode(uname);

                if (un)
                {
                        strncpy(uname, un, sizeof(uname));
                        free(un);
                }

                sprintf(hv0, "USERS:%d:%d:%s;", id, pos+1, uname);
                write(s1, hv0, strlen(hv0));
        }

        sqlite3_finalize(res);
        sqlite3_close(db);
        playQuiet = FALSE;
        return TRUE;
}