From 4af30c534c39dbd9faff26a8e3da3c1704a8dfa6 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 5 Jan 2013 16:08:19 -0800 Subject: [PATCH 001/116] snapshot. New game button works and game gets saved in DB. Next: display and open from DB. --- .../eehouse/android/xw4/CommsTransport.java | 18 +- xwords4/common/game.c | 73 +- xwords4/common/game.h | 4 +- xwords4/linux/Makefile | 2 + xwords4/linux/gamesdb.c | 53 + xwords4/linux/gamesdb.h | 29 + xwords4/linux/gtkask.h | 2 +- xwords4/linux/gtkaskdict.h | 2 +- xwords4/linux/gtkboard.c | 2485 +++++++++++++++++ xwords4/linux/gtkboard.h | 176 ++ xwords4/linux/gtkchat.h | 2 +- xwords4/linux/gtkconnsdlg.h | 2 +- xwords4/linux/gtkdraw.c | 2 +- xwords4/linux/gtkletterask.h | 2 +- xwords4/linux/gtkmain.c | 2455 +--------------- xwords4/linux/gtkmain.h | 148 +- xwords4/linux/gtknewgame.c | 27 +- xwords4/linux/gtknewgame.h | 2 +- xwords4/linux/gtkntilesask.h | 2 +- xwords4/linux/gtkutils.h | 2 +- xwords4/linux/linuxmain.c | 66 +- xwords4/linux/linuxmain.h | 3 + 22 files changed, 2927 insertions(+), 2630 deletions(-) create mode 100644 xwords4/linux/gamesdb.c create mode 100644 xwords4/linux/gamesdb.h create mode 100644 xwords4/linux/gtkboard.c create mode 100644 xwords4/linux/gtkboard.h diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java index ba89de474..8a7d132c8 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java @@ -377,14 +377,16 @@ public class CommsTransport implements TransportProcs, switch ( addr.conType ) { case COMMS_CONN_RELAY: - if ( NetStateCache.netAvail( m_context ) ) { - putOut( buf ); // add to queue - if ( null == m_thread ) { - m_thread = new CommsThread(); - m_thread.start(); - } - nSent = buf.length; - } + nSent = RelayService.sendPacket( m_context, addr.sms_phone, + gameID, buf ); + // if ( NetStateCache.netAvail( m_context ) ) { + // putOut( buf ); // add to queue + // if ( null == m_thread ) { + // m_thread = new CommsThread(); + // m_thread.start(); + // } + // nSent = buf.length; + // } break; case COMMS_CONN_SMS: nSent = SMSService.sendPacket( m_context, addr.sms_phone, diff --git a/xwords4/common/game.c b/xwords4/common/game.c index ae73da301..178fcc0fb 100644 --- a/xwords4/common/game.c +++ b/xwords4/common/game.c @@ -133,62 +133,67 @@ game_makeNewGame( MPFORMAL XWGame* game, CurGameInfo* gi, board_prefsChanged( game->board, cp ); } /* game_makeNewGame */ -void +XP_Bool game_reset( MPFORMAL XWGame* game, CurGameInfo* gi, XW_UtilCtxt* XP_UNUSED_STANDALONE(util), CommonPrefs* cp, const TransportProcs* procs ) { + XP_Bool result = XP_FALSE; XP_U16 ii; - XP_ASSERT( !!game->model ); - XP_ASSERT( !!gi ); + if ( !!game->model ) { + XP_ASSERT( !!game->model ); + XP_ASSERT( !!gi ); - gi->gameID = makeGameID( util ); + gi->gameID = makeGameID( util ); #ifndef XWFEATURE_STANDALONE_ONLY - XP_U16 nPlayersHere = 0; - XP_U16 nPlayersTotal = 0; - checkServerRole( gi, &nPlayersHere, &nPlayersTotal ); + XP_U16 nPlayersHere = 0; + XP_U16 nPlayersTotal = 0; + checkServerRole( gi, &nPlayersHere, &nPlayersTotal ); - if ( !!game->comms ) { - if ( gi->serverRole == SERVER_STANDALONE ) { - comms_destroy( game->comms ); - game->comms = NULL; - } else { - comms_reset( game->comms, gi->serverRole != SERVER_ISCLIENT, - nPlayersHere, nPlayersTotal ); - } - } else if ( gi->serverRole != SERVER_STANDALONE ) { - game->comms = comms_make( MPPARM(mpool) util, - gi->serverRole != SERVER_ISCLIENT, - nPlayersHere, nPlayersTotal, procs + if ( !!game->comms ) { + if ( gi->serverRole == SERVER_STANDALONE ) { + comms_destroy( game->comms ); + game->comms = NULL; + } else { + comms_reset( game->comms, gi->serverRole != SERVER_ISCLIENT, + nPlayersHere, nPlayersTotal ); + } + } else if ( gi->serverRole != SERVER_STANDALONE ) { + game->comms = comms_make( MPPARM(mpool) util, + gi->serverRole != SERVER_ISCLIENT, + nPlayersHere, nPlayersTotal, procs #ifdef SET_GAMESEED - , 0 + , 0 #endif - ); - } + ); + } #else # ifdef DEBUG - mpool = mpool; /* quash unused formal warning */ + mpool = mpool; /* quash unused formal warning */ # endif #endif - model_setSize( game->model, gi->boardSize ); - server_reset( game->server, + model_setSize( game->model, gi->boardSize ); + server_reset( game->server, #ifndef XWFEATURE_STANDALONE_ONLY - game->comms + game->comms #else - NULL + NULL #endif - ); - board_reset( game->board ); + ); + board_reset( game->board ); - for ( ii = 0; ii < gi->nPlayers; ++ii ) { - gi->players[ii].secondsUsed = 0; + for ( ii = 0; ii < gi->nPlayers; ++ii ) { + gi->players[ii].secondsUsed = 0; + } + + server_prefsChanged( game->server, cp ); + board_prefsChanged( game->board, cp ); + result = XP_TRUE; } - - server_prefsChanged( game->server, cp ); - board_prefsChanged( game->board, cp ); + return result; } /* game_reset */ #ifdef XWFEATURE_CHANGEDICT diff --git a/xwords4/common/game.h b/xwords4/common/game.h index b5021af60..bc425308f 100644 --- a/xwords4/common/game.h +++ b/xwords4/common/game.h @@ -93,8 +93,8 @@ void game_makeNewGame( MPFORMAL XWGame* game, CurGameInfo* gi, ,XP_U16 gameSeed #endif ); -void game_reset( MPFORMAL XWGame* game, CurGameInfo* gi, XW_UtilCtxt* util, - CommonPrefs* cp, const TransportProcs* procs ); +XP_Bool game_reset( MPFORMAL XWGame* game, CurGameInfo* gi, XW_UtilCtxt* util, + CommonPrefs* cp, const TransportProcs* procs ); void game_changeDict( MPFORMAL XWGame* game, CurGameInfo* gi, DictionaryCtxt* dict ); diff --git a/xwords4/linux/Makefile b/xwords4/linux/Makefile index 05543eeee..ed114e839 100644 --- a/xwords4/linux/Makefile +++ b/xwords4/linux/Makefile @@ -169,6 +169,7 @@ INCLUDES += ${EXTRAINCS} ifdef DO_GTK GTK_OBJS = \ $(BUILD_PLAT_DIR)/gtkmain.o \ + $(BUILD_PLAT_DIR)/gtkboard.o \ $(BUILD_PLAT_DIR)/gtkdraw.o \ $(BUILD_PLAT_DIR)/gtkask.o \ $(BUILD_PLAT_DIR)/gtkletterask.o \ @@ -201,6 +202,7 @@ OBJ = \ $(BUILD_PLAT_DIR)/linuxsms.o \ $(BUILD_PLAT_DIR)/linuxdict.o \ $(BUILD_PLAT_DIR)/linuxutl.o \ + $(BUILD_PLAT_DIR)/gamesdb.o \ $(CURSES_OBJS) $(GTK_OBJS) $(MAIN_OBJS) LIBS = -lm -luuid $(GPROFFLAG) diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c new file mode 100644 index 000000000..cceee713d --- /dev/null +++ b/xwords4/linux/gamesdb.c @@ -0,0 +1,53 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2000-2012 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program 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 program 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. + */ + +#include + +#include "comtypes.h" +#include "gamesdb.h" + +#define DB_NAME "games.db" + +sqlite3* +openGamesDB( void ) +{ + sqlite3* pDb = NULL; + int result = sqlite3_open( DB_NAME, &pDb ); + XP_ASSERT( SQLITE_OK == result ); + + const char* createStr = + "CREATE TABLE games ( " + "game BLOB" + ",room VARCHAR(32)" + ")"; + + result = sqlite3_exec( pDb, createStr, NULL, NULL, NULL ); + XP_LOGF( "sqlite3_exec=>%d", result ); + // XP_ASSERT( SQLITE_OK == result ); + + return pDb; +} + +void +closeGamesDB( sqlite3* pDb ) +{ + sqlite3_close( pDb ); + XP_LOGF( "%s finished", __func__ ); +} diff --git a/xwords4/linux/gamesdb.h b/xwords4/linux/gamesdb.h new file mode 100644 index 000000000..a76deb6cc --- /dev/null +++ b/xwords4/linux/gamesdb.h @@ -0,0 +1,29 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2000-2012 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program 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 program 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. + */ + +#ifndef _GAMESDB_H_ +#define _GAMESDB_H_ + +#include + +sqlite3* openGamesDB( void ); +void closeGamesDB( sqlite3* dbp ); + +#endif diff --git a/xwords4/linux/gtkask.h b/xwords4/linux/gtkask.h index d9e319eea..78ccf2e33 100644 --- a/xwords4/linux/gtkask.h +++ b/xwords4/linux/gtkask.h @@ -23,7 +23,7 @@ #ifndef _GTKASK_H_ #define _GTKASK_H_ -#include "gtkmain.h" +#include "gtkboard.h" /* Returns true for "yes" or "ok" answer, false otherwise. */ diff --git a/xwords4/linux/gtkaskdict.h b/xwords4/linux/gtkaskdict.h index 134285427..4a78a6a8e 100644 --- a/xwords4/linux/gtkaskdict.h +++ b/xwords4/linux/gtkaskdict.h @@ -23,7 +23,7 @@ #ifndef _GTKASKDICT_H_ #define _GTKASKDICT_H_ -#include "gtkmain.h" +#include "gtkboard.h" gchar* gtkaskdict( GSList* dicts, gchar* buf, gint buflen ); diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c new file mode 100644 index 000000000..695ac6ba7 --- /dev/null +++ b/xwords4/linux/gtkboard.c @@ -0,0 +1,2485 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2000-2009 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program 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 program 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. + */ + +#ifdef PLATFORM_GTK + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifndef CLIENT_ONLY +/* # include */ +#endif +#include +#include +#include +#include + +#include "main.h" +#include "linuxmain.h" +#include "linuxutl.h" +#include "linuxbt.h" +#include "linuxudp.h" +#include "linuxsms.h" +/* #include "gtkmain.h" */ + +#include "draw.h" +#include "game.h" +#include "gtkask.h" +#include "gtkchat.h" +#include "gtknewgame.h" +#include "gtkletterask.h" +#include "gtkpasswdask.h" +#include "gtkntilesask.h" +#include "gtkaskdict.h" +/* #include "undo.h" */ +#include "gtkdraw.h" +#include "memstream.h" +#include "filestream.h" + +/* static guint gtkSetupClientSocket( GtkAppGlobals* globals, int sock ); */ +static void setCtrlsForTray( GtkAppGlobals* globals ); +static void new_game( GtkWidget* widget, GtkAppGlobals* globals ); +static XP_Bool new_game_impl( GtkAppGlobals* globals, XP_Bool fireConnDlg ); +static void setZoomButtons( GtkAppGlobals* globals, XP_Bool* inOut ); +static void disenable_buttons( GtkAppGlobals* globals ); + + +#define GTK_TRAY_HT_ROWS 3 + +#if 0 +static XWStreamCtxt* +lookupClientStream( GtkAppGlobals* globals, int sock ) +{ + short i; + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + ClientStreamRec* rec = &globals->clientRecs[i]; + if ( rec->sock == sock ) { + XP_ASSERT( rec->stream != NULL ); + return rec->stream; + } + } + XP_ASSERT( i < MAX_NUM_PLAYERS ); + return NULL; +} /* lookupClientStream */ + +static void +rememberClient( GtkAppGlobals* globals, guint key, int sock, + XWStreamCtxt* stream ) +{ + short i; + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + ClientStreamRec* rec = &globals->clientRecs[i]; + if ( rec->stream == NULL ) { + XP_ASSERT( stream != NULL ); + rec->stream = stream; + rec->key = key; + rec->sock = sock; + break; + } + } + XP_ASSERT( i < MAX_NUM_PLAYERS ); +} /* rememberClient */ +#endif + +static void +gtkSetAltState( GtkAppGlobals* globals, guint state ) +{ + globals->altKeyDown + = (state & (GDK_MOD1_MASK|GDK_SHIFT_MASK|GDK_CONTROL_MASK)) != 0; +} + +static gint +button_press_event( GtkWidget* XP_UNUSED(widget), GdkEventButton *event, + GtkAppGlobals* globals ) +{ + XP_Bool redraw, handled; + + gtkSetAltState( globals, event->state ); + + if ( !globals->mouseDown ) { + globals->mouseDown = XP_TRUE; + redraw = board_handlePenDown( globals->cGlobals.game.board, + event->x, event->y, &handled ); + if ( redraw ) { + board_draw( globals->cGlobals.game.board ); + disenable_buttons( globals ); + } + } + return 1; +} /* button_press_event */ + +static gint +motion_notify_event( GtkWidget* XP_UNUSED(widget), GdkEventMotion *event, + GtkAppGlobals* globals ) +{ + XP_Bool handled; + + gtkSetAltState( globals, event->state ); + + if ( globals->mouseDown ) { + handled = board_handlePenMove( globals->cGlobals.game.board, event->x, + event->y ); + if ( handled ) { + board_draw( globals->cGlobals.game.board ); + disenable_buttons( globals ); + } + } else { + handled = XP_FALSE; + } + + return handled; +} /* motion_notify_event */ + +static gint +button_release_event( GtkWidget* XP_UNUSED(widget), GdkEventMotion *event, + GtkAppGlobals* globals ) +{ + XP_Bool redraw; + + gtkSetAltState( globals, event->state ); + + if ( globals->mouseDown ) { + redraw = board_handlePenUp( globals->cGlobals.game.board, + event->x, + event->y ); + if ( redraw ) { + board_draw( globals->cGlobals.game.board ); + disenable_buttons( globals ); + } + globals->mouseDown = XP_FALSE; + } + return 1; +} /* button_release_event */ + +#ifdef KEY_SUPPORT +static XP_Key +evtToXPKey( GdkEventKey* event, XP_Bool* movesCursorP ) +{ + XP_Key xpkey = XP_KEY_NONE; + XP_Bool movesCursor = XP_FALSE; + guint keyval = event->keyval; + + switch( keyval ) { +#ifdef KEYBOARD_NAV + case GDK_Return: + xpkey = XP_RETURN_KEY; + break; + case GDK_space: + xpkey = XP_RAISEFOCUS_KEY; + break; + + case GDK_Left: + xpkey = XP_CURSOR_KEY_LEFT; + movesCursor = XP_TRUE; + break; + case GDK_Right: + xpkey = XP_CURSOR_KEY_RIGHT; + movesCursor = XP_TRUE; + break; + case GDK_Up: + xpkey = XP_CURSOR_KEY_UP; + movesCursor = XP_TRUE; + break; + case GDK_Down: + xpkey = XP_CURSOR_KEY_DOWN; + movesCursor = XP_TRUE; + break; +#endif + case GDK_BackSpace: + XP_LOGF( "... it's a DEL" ); + xpkey = XP_CURSOR_KEY_DEL; + break; + default: + keyval = keyval & 0x00FF; /* mask out gtk stuff */ + if ( isalpha( keyval ) ) { + xpkey = toupper(keyval); + break; +#ifdef NUMBER_KEY_AS_INDEX + } else if ( isdigit( keyval ) ) { + xpkey = keyval; + break; +#endif + } + } + *movesCursorP = movesCursor; + return xpkey; +} /* evtToXPKey */ + +#ifdef KEYBOARD_NAV +static gint +key_press_event( GtkWidget* XP_UNUSED(widget), GdkEventKey* event, + GtkAppGlobals* globals ) +{ + XP_Bool handled = XP_FALSE; + XP_Bool movesCursor; + XP_Key xpkey = evtToXPKey( event, &movesCursor ); + + gtkSetAltState( globals, event->state ); + + if ( xpkey != XP_KEY_NONE ) { + XP_Bool draw = globals->keyDown ? + board_handleKeyRepeat( globals->cGlobals.game.board, xpkey, + &handled ) + : board_handleKeyDown( globals->cGlobals.game.board, xpkey, + &handled ); + if ( draw ) { + board_draw( globals->cGlobals.game.board ); + } + } + globals->keyDown = XP_TRUE; + return 1; +} +#endif + +static gint +key_release_event( GtkWidget* XP_UNUSED(widget), GdkEventKey* event, + GtkAppGlobals* globals ) +{ + XP_Bool handled = XP_FALSE; + XP_Bool movesCursor; + XP_Key xpkey = evtToXPKey( event, &movesCursor ); + + gtkSetAltState( globals, event->state ); + + if ( xpkey != XP_KEY_NONE ) { + XP_Bool draw; + draw = board_handleKeyUp( globals->cGlobals.game.board, xpkey, + &handled ); +#ifdef KEYBOARD_NAV + if ( movesCursor && !handled ) { + BoardObjectType order[] = { OBJ_SCORE, OBJ_BOARD, OBJ_TRAY }; + draw = linShiftFocus( &globals->cGlobals, xpkey, order, + NULL ) || draw; + } +#endif + if ( draw ) { + board_draw( globals->cGlobals.game.board ); + } + } + +/* XP_ASSERT( globals->keyDown ); */ +#ifdef KEYBOARD_NAV + globals->keyDown = XP_FALSE; +#endif + + return handled? 1 : 0; /* gtk will do something with the key if 0 + returned */ +} /* key_release_event */ +#endif + +#ifdef MEM_DEBUG +# define MEMPOOL globals->cGlobals.params->util->mpool, +#else +# define MEMPOOL +#endif + +static void +relay_status_gtk( void* closure, CommsRelayState state ) +{ + XP_LOGF( "%s got status: %s", __func__, CommsRelayState2Str(state) ); + GtkAppGlobals* globals = (GtkAppGlobals*)closure; + globals->cGlobals.state = state; + globals->stateChar = 'A' + COMMS_RELAYSTATE_ALLCONNECTED - state; + draw_gtk_status( globals->draw, globals->stateChar ); +} + +static void +relay_connd_gtk( void* closure, XP_UCHAR* const room, + XP_Bool XP_UNUSED(reconnect), XP_U16 devOrder, + XP_Bool allHere, XP_U16 nMissing ) +{ + XP_Bool skip = XP_FALSE; + char buf[256]; + + if ( allHere ) { + snprintf( buf, sizeof(buf), + "All expected players have joined in %s. Play!", room ); + } else { + if ( nMissing > 0 ) { + snprintf( buf, sizeof(buf), "%dth connected to relay; waiting " + "in %s for %d player[s].", devOrder, room, nMissing ); + } else { + /* an allHere message should be coming immediately, so no + notification now. */ + skip = XP_TRUE; + } + } + + if ( !skip ) { + GtkAppGlobals* globals = (GtkAppGlobals*)closure; + (void)gtkask_timeout( globals->window, buf, GTK_BUTTONS_OK, 500 ); + } +} + +static gint +invoke_new_game( gpointer data ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)data; + new_game_impl( globals, XP_FALSE ); + return 0; +} + +static gint +invoke_new_game_conns( gpointer data ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)data; + new_game_impl( globals, XP_TRUE ); + return 0; +} + +static void +relay_error_gtk( void* closure, XWREASON relayErr ) +{ + LOG_FUNC(); + GtkAppGlobals* globals = (GtkAppGlobals*)closure; + + gint (*proc)( gpointer data ) = NULL; + switch( relayErr ) { + case XWRELAY_ERROR_NO_ROOM: + case XWRELAY_ERROR_DUP_ROOM: + proc = invoke_new_game_conns; + break; + case XWRELAY_ERROR_TOO_MANY: + case XWRELAY_ERROR_BADPROTO: + proc = invoke_new_game; + break; + case XWRELAY_ERROR_DELETED: + gtkask_timeout( globals->window, + "relay says another device deleted game.", + GTK_BUTTONS_OK, 1000 ); + break; + case XWRELAY_ERROR_DEADGAME: + break; + default: + assert(0); + break; + } + + if ( !!proc ) { + (void)g_idle_add( proc, globals ); + } +} + +static void +createOrLoadObjects( GtkAppGlobals* globals ) +{ + XWStreamCtxt* stream = NULL; + XP_Bool opened = XP_FALSE; + +#ifndef XWFEATURE_STANDALONE_ONLY + DeviceRole serverRole = globals->cGlobals.params->serverRole; + XP_Bool isServer = serverRole != SERVER_ISCLIENT; +#endif + LaunchParams* params = globals->cGlobals.params; + + globals->draw = (GtkDrawCtx*)gtkDrawCtxtMake( globals->drawing_area, + globals ); + + TransportProcs procs = { + .closure = globals, + .send = LINUX_SEND, +#ifdef COMMS_HEARTBEAT + .reset = linux_reset, +#endif +#ifdef XWFEATURE_RELAY + .rstatus = relay_status_gtk, + .rconnd = relay_connd_gtk, + .rerror = relay_error_gtk, +#endif + }; + + if ( !!params->fileName && file_exists( params->fileName ) ) { + stream = streamFromFile( &globals->cGlobals, params->fileName, globals ); +#ifdef USE_SQLITE + } else if ( !!params->dbFileName && file_exists( params->dbFileName ) ) { + stream = streamFromDB( &globals->cGlobals, globals ); +#endif + } + + if ( !!stream ) { + opened = game_makeFromStream( MEMPOOL stream, &globals->cGlobals.game, + &globals->cGlobals.params->gi, + params->dict, ¶ms->dicts, params->util, + (DrawCtx*)globals->draw, + &globals->cGlobals.cp, &procs ); + + stream_destroy( stream ); + } + + if ( !opened ) { + CommsAddrRec addr; + + XP_MEMSET( &addr, 0, sizeof(addr) ); + addr.conType = params->conType; + +#ifdef XWFEATURE_RELAY + if ( addr.conType == COMMS_CONN_RELAY ) { + XP_ASSERT( !!params->connInfo.relay.relayName ); + globals->cGlobals.defaultServerName + = params->connInfo.relay.relayName; + } +#endif + + game_makeNewGame( MEMPOOL &globals->cGlobals.game, ¶ms->gi, + params->util, (DrawCtx*)globals->draw, + &globals->cGlobals.cp, &procs, params->gameSeed ); + + addr.conType = params->conType; + if ( 0 ) { +#ifdef XWFEATURE_RELAY + } else if ( addr.conType == COMMS_CONN_RELAY ) { + addr.u.ip_relay.ipAddr = 0; + addr.u.ip_relay.port = params->connInfo.relay.defaultSendPort; + addr.u.ip_relay.seeksPublicRoom = params->connInfo.relay.seeksPublicRoom; + addr.u.ip_relay.advertiseRoom = params->connInfo.relay.advertiseRoom; + XP_STRNCPY( addr.u.ip_relay.hostName, params->connInfo.relay.relayName, + sizeof(addr.u.ip_relay.hostName) - 1 ); + XP_STRNCPY( addr.u.ip_relay.invite, params->connInfo.relay.invite, + sizeof(addr.u.ip_relay.invite) - 1 ); +#endif +#ifdef XWFEATURE_BLUETOOTH + } else if ( addr.conType == COMMS_CONN_BT ) { + XP_ASSERT( sizeof(addr.u.bt.btAddr) + >= sizeof(params->connInfo.bt.hostAddr)); + XP_MEMCPY( &addr.u.bt.btAddr, ¶ms->connInfo.bt.hostAddr, + sizeof(params->connInfo.bt.hostAddr) ); +#endif +#ifdef XWFEATURE_IP_DIRECT + } else if ( addr.conType == COMMS_CONN_IP_DIRECT ) { + XP_STRNCPY( addr.u.ip.hostName_ip, params->connInfo.ip.hostName, + sizeof(addr.u.ip.hostName_ip) - 1 ); + addr.u.ip.port_ip = params->connInfo.ip.port; +#endif +#ifdef XWFEATURE_SMS + } else if ( addr.conType == COMMS_CONN_SMS ) { + XP_STRNCPY( addr.u.sms.phone, params->connInfo.sms.serverPhone, + sizeof(addr.u.sms.phone) - 1 ); + addr.u.sms.port = params->connInfo.sms.port; +#endif + } + +#ifndef XWFEATURE_STANDALONE_ONLY + /* This may trigger network activity */ + if ( !!globals->cGlobals.game.comms ) { + comms_setAddr( globals->cGlobals.game.comms, &addr ); + } +#endif + model_setDictionary( globals->cGlobals.game.model, params->dict ); + setSquareBonuses( &globals->cGlobals ); + model_setPlayerDicts( globals->cGlobals.game.model, ¶ms->dicts ); + +#ifdef XWFEATURE_SEARCHLIMIT + params->gi.allowHintRect = params->allowHintRect; +#endif + + if ( params->needsNewGame ) { + new_game_impl( globals, XP_FALSE ); +#ifndef XWFEATURE_STANDALONE_ONLY + } else if ( !isServer ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL params->vtMgr, &globals->cGlobals, CHANNEL_NONE, + sendOnClose ); + server_initClientConnection( globals->cGlobals.game.server, + stream ); +#endif + } + } + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( !!globals->cGlobals.game.comms ) { + comms_start( globals->cGlobals.game.comms ); + } +#endif + server_do( globals->cGlobals.game.server ); + + disenable_buttons( globals ); +} /* createOrLoadObjects */ + +/* Create a new backing pixmap of the appropriate size and set up contxt to + * draw using that size. + */ +static gboolean +configure_event( GtkWidget* widget, GdkEventConfigure* XP_UNUSED(event), + GtkAppGlobals* globals ) +{ + short bdWidth, bdHeight; + short timerLeft, timerTop; + gint hscale, vscale; + gint trayTop; + gint boardTop = 0; + XP_U16 netStatWidth = 0; + gint nCols = globals->cGlobals.params->gi.boardSize; + gint nRows = nCols; + + if ( globals->draw == NULL ) { + createOrLoadObjects( globals ); + } + + bdWidth = widget->allocation.width - (GTK_RIGHT_MARGIN + + GTK_BOARD_LEFT_MARGIN); + if ( globals->cGlobals.params->verticalScore ) { + bdWidth -= GTK_VERT_SCORE_WIDTH; + } + bdHeight = widget->allocation.height - (GTK_TOP_MARGIN + GTK_BOTTOM_MARGIN) + - GTK_MIN_TRAY_SCALEV - GTK_BOTTOM_MARGIN; + + hscale = bdWidth / nCols; + if ( 0 != globals->cGlobals.params->nHidden ) { + vscale = hscale; + } else { + vscale = (bdHeight / (nCols + GTK_TRAY_HT_ROWS)); /* makd tray height + 3x cell height */ + } + + if ( !globals->cGlobals.params->verticalScore ) { + boardTop += GTK_HOR_SCORE_HEIGHT; + } + + trayTop = boardTop + (vscale * nRows); + /* move tray up if part of board's meant to be hidden */ + trayTop -= vscale * globals->cGlobals.params->nHidden; + board_setPos( globals->cGlobals.game.board, GTK_BOARD_LEFT, boardTop, + hscale * nCols, vscale * nRows, hscale * 4, XP_FALSE ); + /* board_setScale( globals->cGlobals.game.board, hscale, vscale ); */ + globals->gridOn = XP_TRUE; + + if ( !!globals->cGlobals.game.comms ) { + netStatWidth = GTK_NETSTAT_WIDTH; + } + + timerTop = GTK_TIMER_TOP; + if ( globals->cGlobals.params->verticalScore ) { + timerLeft = GTK_BOARD_LEFT + (hscale*nCols) + 1; + board_setScoreboardLoc( globals->cGlobals.game.board, + timerLeft, + GTK_VERT_SCORE_TOP, + GTK_VERT_SCORE_WIDTH, + vscale*nCols, + XP_FALSE ); + + } else { + timerLeft = GTK_BOARD_LEFT + (hscale*nCols) + - GTK_TIMER_WIDTH - netStatWidth; + board_setScoreboardLoc( globals->cGlobals.game.board, + GTK_BOARD_LEFT, GTK_HOR_SCORE_TOP, + timerLeft-GTK_BOARD_LEFT, + GTK_HOR_SCORE_HEIGHT, + XP_TRUE ); + + } + + /* Still pending: do this for the vertical score case */ + if ( globals->cGlobals.game.comms ) { + globals->netStatLeft = timerLeft + GTK_TIMER_WIDTH; + globals->netStatTop = 0; + } + + board_setTimerLoc( globals->cGlobals.game.board, timerLeft, timerTop, + GTK_TIMER_WIDTH, GTK_HOR_SCORE_HEIGHT ); + + board_setTrayLoc( globals->cGlobals.game.board, GTK_TRAY_LEFT, trayTop, + hscale * nCols, vscale * GTK_TRAY_HT_ROWS + 10, + GTK_DIVIDER_WIDTH ); + + setCtrlsForTray( globals ); + + board_invalAll( globals->cGlobals.game.board ); + + XP_Bool inOut[2]; + board_zoom( globals->cGlobals.game.board, 0, inOut ); + setZoomButtons( globals, inOut ); + + return TRUE; +} /* configure_event */ + +/* Redraw the screen from the backing pixmap */ +static gint +expose_event( GtkWidget* XP_UNUSED(widget), + GdkEventExpose* XP_UNUSED(event), + GtkAppGlobals* globals ) +{ + /* + gdk_draw_rectangle( widget->window,//((GtkDrawCtx*)globals->draw)->pixmap, + widget->style->white_gc, + TRUE, + 0, 0, + widget->allocation.width, + widget->allocation.height+widget->allocation.y ); + */ + /* I want to inval only the area that's exposed, but the rect is always + empty, even when clearly shouldn't be. Need to investigate. Until + fixed, use board_invalAll to ensure board is drawn.*/ +/* board_invalRect( globals->cGlobals.game.board, (XP_Rect*)&event->area ); */ + + board_invalAll( globals->cGlobals.game.board ); + board_draw( globals->cGlobals.game.board ); + draw_gtk_status( globals->draw, globals->stateChar ); + +/* gdk_draw_pixmap( widget->window, */ +/* widget->style->fg_gc[GTK_WIDGET_STATE (widget)], */ +/* ((GtkDrawCtx*)globals->draw)->pixmap, */ +/* event->area.x, event->area.y, */ +/* event->area.x, event->area.y, */ +/* event->area.width, event->area.height ); */ + + return FALSE; +} /* expose_event */ + +#if 0 +static gint +handle_client_event( GtkWidget *widget, GdkEventClient *event, + GtkAppGlobals* globals ) +{ + XP_LOGF( "handle_client_event called: event->type = " ); + if ( event->type == GDK_CLIENT_EVENT ) { + XP_LOGF( "GDK_CLIENT_EVENT" ); + return 1; + } else { + XP_LOGF( "%d", event->type ); + return 0; + } +} /* handle_client_event */ +#endif + +static void +quit( void ) +{ + gtk_main_quit(); +} + +static void +cleanup( GtkAppGlobals* globals ) +{ + saveGame( &globals->cGlobals ); + + game_dispose( &globals->cGlobals.game ); /* takes care of the dict */ + gi_disposePlayerInfo( MEMPOOL &globals->cGlobals.params->gi ); + +#ifdef XWFEATURE_BLUETOOTH + linux_bt_close( &globals->cGlobals ); +#endif +#ifdef XWFEATURE_SMS + linux_sms_close( &globals->cGlobals ); +#endif +#ifdef XWFEATURE_IP_DIRECT + linux_udp_close( &globals->cGlobals ); +#endif +#ifdef XWFEATURE_RELAY + linux_close_socket( &globals->cGlobals ); +#endif +} /* cleanup */ + +GtkWidget* +makeAddSubmenu( GtkWidget* menubar, gchar* label ) +{ + GtkWidget* submenu; + GtkWidget* item; + + item = gtk_menu_item_new_with_label( label ); + gtk_menu_bar_append( GTK_MENU_BAR(menubar), item ); + + submenu = gtk_menu_new(); + gtk_menu_item_set_submenu( GTK_MENU_ITEM(item), submenu ); + + gtk_widget_show(item); + + return submenu; +} /* makeAddSubmenu */ + +static void +tile_values( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + if ( !!globals->cGlobals.game.server ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, + CHANNEL_NONE, + catOnClose ); + server_formatDictCounts( globals->cGlobals.game.server, stream, 5 ); + stream_putU8( stream, '\n' ); + stream_destroy( stream ); + } + +} /* tile_values */ + +static void +game_history( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + catGameHistory( &globals->cGlobals ); +} /* game_history */ + +#ifdef TEXT_MODEL +static void +dump_board( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + if ( !!globals->cGlobals.game.model ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, + CHANNEL_NONE, + catOnClose ); + model_writeToTextStream( globals->cGlobals.game.model, stream ); + stream_destroy( stream ); + } +} +#endif + +static void +final_scores( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + XP_Bool gameOver = server_getGameIsOver( globals->cGlobals.game.server ); + + if ( gameOver ) { + catFinalScores( &globals->cGlobals, -1 ); + } else { + if ( gtkask( globals->window, + "Are you sure you want to resign?", + GTK_BUTTONS_YES_NO ) ) { + globals->cGlobals.manualFinal = XP_TRUE; + server_endGame( globals->cGlobals.game.server ); + gameOver = TRUE; + } + } + + /* the end game listener will take care of printing the final scores */ +} /* final_scores */ + +static XP_Bool +new_game_impl( GtkAppGlobals* globals, XP_Bool fireConnDlg ) +{ + XP_Bool success = XP_FALSE; + CommsAddrRec addr; + + if ( !!globals->cGlobals.game.comms ) { + comms_getAddr( globals->cGlobals.game.comms, &addr ); + } else { + comms_getInitialAddr( &addr, RELAY_NAME_DEFAULT, RELAY_PORT_DEFAULT ); + } + + success = newGameDialog( globals, &addr, XP_TRUE, fireConnDlg ); + if ( success ) { + CurGameInfo* gi = &globals->cGlobals.params->gi; +#ifndef XWFEATURE_STANDALONE_ONLY + XP_Bool isClient = gi->serverRole == SERVER_ISCLIENT; +#endif + TransportProcs procs = { + .closure = globals, + .send = LINUX_SEND, +#ifdef COMMS_HEARTBEAT + .reset = linux_reset, +#endif + }; + + if ( !game_reset( MEMPOOL &globals->cGlobals.game, gi, + globals->cGlobals.params->util, + &globals->cGlobals.cp, &procs ) ) { + if ( NULL == globals->draw ) { + globals->draw = (GtkDrawCtx*)gtkDrawCtxtMake( globals->drawing_area, + globals ); + } + game_makeNewGame( MEMPOOL &globals->cGlobals.game, gi, + globals->cGlobals.params->util, + (DrawCtx*)globals->draw, + &globals->cGlobals.cp, &procs, + globals->cGlobals.params->gameSeed ); + ModelCtxt* model = globals->cGlobals.game.model; + if ( NULL == model_getDictionary( model ) ) { + DictionaryCtxt* dict = + linux_dictionary_make( MEMPOOL globals->cGlobals.params, + gi->dictName, XP_TRUE ); + model_setDictionary( model, dict ); + } + } + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( !!globals->cGlobals.game.comms ) { + comms_setAddr( globals->cGlobals.game.comms, &addr ); + } else if ( gi->serverRole != SERVER_STANDALONE ) { + XP_ASSERT(0); + } + + if ( isClient ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL globals->cGlobals.params->vtMgr, + &globals->cGlobals, CHANNEL_NONE, + sendOnClose ); + server_initClientConnection( globals->cGlobals.game.server, + stream ); + } +#endif + (void)server_do( globals->cGlobals.game.server ); /* assign tiles, etc. */ + board_invalAll( globals->cGlobals.game.board ); + board_draw( globals->cGlobals.game.board ); + } + return success; +} /* new_game_impl */ + +static void +new_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + new_game_impl( globals, XP_FALSE ); +} /* new_game */ + +static void +game_info( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + CommsAddrRec addr; + comms_getAddr( globals->cGlobals.game.comms, &addr ); + + /* Anything to do if OK is clicked? Changed names etc. already saved. Try + server_do in case one's become a robot. */ + if ( newGameDialog( globals, &addr, XP_FALSE, XP_FALSE ) ) { + if ( server_do( globals->cGlobals.game.server ) ) { + board_draw( globals->cGlobals.game.board ); + } + } +} + +static void +load_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) +{ +} /* load_game */ + +static void +save_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) +{ +} /* save_game */ + +#ifdef XWFEATURE_CHANGEDICT +static void +change_dictionary( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + LaunchParams* params = globals->cGlobals.params; + GSList* dicts = listDicts( params ); + gchar buf[265]; + gchar* name = gtkaskdict( dicts, buf, VSIZE(buf) ); + if ( !!name ) { + DictionaryCtxt* dict = + linux_dictionary_make( MPPARM(params->util->mpool) params, name, + params->useMmap ); + game_changeDict( MPPARM(params->util->mpool) &globals->cGlobals.game, + ¶ms->gi, dict ); + } + g_slist_free( dicts ); +} /* change_dictionary */ +#endif + +static void +handle_undo( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) +{ +} /* handle_undo */ + +static void +handle_redo( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) +{ +} /* handle_redo */ + +#ifdef FEATURE_TRAY_EDIT +static void +handle_trayEditToggle( GtkWidget* XP_UNUSED(widget), + GtkAppGlobals* XP_UNUSED(globals), + XP_Bool XP_UNUSED(on) ) +{ +} /* handle_trayEditToggle */ + +static void +handle_trayEditToggle_on( GtkWidget* widget, GtkAppGlobals* globals ) +{ + handle_trayEditToggle( widget, globals, XP_TRUE ); +} + +static void +handle_trayEditToggle_off( GtkWidget* widget, GtkAppGlobals* globals ) +{ + handle_trayEditToggle( widget, globals, XP_FALSE ); +} +#endif + +static void +handle_trade_cancel( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + BoardCtxt* board = globals->cGlobals.game.board; + if ( board_endTrade( board ) ) { + board_draw( board ); + } +} + +#ifndef XWFEATURE_STANDALONE_ONLY +static void +handle_resend( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + CommsCtxt* comms = globals->cGlobals.game.comms; + if ( comms != NULL ) { + comms_resendAll( comms, XP_TRUE ); + } +} /* handle_resend */ + +#ifdef XWFEATURE_COMMSACK +static void +handle_ack( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + CommsCtxt* comms = globals->cGlobals.game.comms; + if ( comms != NULL ) { + comms_ackAny( comms ); + } +} +#endif + +#ifdef DEBUG +static void +handle_commstats( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + CommsCtxt* comms = globals->cGlobals.game.comms; + + if ( !!comms ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, + CHANNEL_NONE, catOnClose ); + comms_getStats( comms, stream ); + stream_destroy( stream ); + } +} /* handle_commstats */ +#endif +#endif + +#ifdef MEM_DEBUG +static void +handle_memstats( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + XWStreamCtxt* stream = mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, + CHANNEL_NONE, catOnClose ); + mpool_stats( MEMPOOL stream ); + stream_destroy( stream ); + +} /* handle_memstats */ +#endif + +static GtkWidget* +createAddItem( GtkWidget* parent, gchar* label, + GtkSignalFunc handlerFunc, GtkAppGlobals* globals ) +{ + GtkWidget* item = gtk_menu_item_new_with_label( label ); + +/* g_print( "createAddItem called with label %s\n", label ); */ + + if ( handlerFunc != NULL ) { + g_signal_connect( GTK_OBJECT(item), "activate", + G_CALLBACK(handlerFunc), globals ); + } + + gtk_menu_append( GTK_MENU(parent), item ); + gtk_widget_show( item ); + + return item; +} /* createAddItem */ + +static GtkWidget* +makeMenus( GtkAppGlobals* globals ) +{ + GtkWidget* menubar = gtk_menu_bar_new(); + GtkWidget* fileMenu; + + fileMenu = makeAddSubmenu( menubar, "File" ); + (void)createAddItem( fileMenu, "Tile values", + GTK_SIGNAL_FUNC(tile_values), globals ); + (void)createAddItem( fileMenu, "Game history", + GTK_SIGNAL_FUNC(game_history), globals ); +#ifdef TEXT_MODEL + (void)createAddItem( fileMenu, "Dump board", + GTK_SIGNAL_FUNC(dump_board), globals ); +#endif + + (void)createAddItem( fileMenu, "Final scores", + GTK_SIGNAL_FUNC(final_scores), globals ); + + (void)createAddItem( fileMenu, "New game", + GTK_SIGNAL_FUNC(new_game), globals ); + (void)createAddItem( fileMenu, "Game info", + GTK_SIGNAL_FUNC(game_info), globals ); + + (void)createAddItem( fileMenu, "Load game", + GTK_SIGNAL_FUNC(load_game), globals ); + (void)createAddItem( fileMenu, "Save game", + GTK_SIGNAL_FUNC(save_game), globals ); +#ifdef XWFEATURE_CHANGEDICT + (void)createAddItem( fileMenu, "Change dictionary", + GTK_SIGNAL_FUNC(change_dictionary), globals ); +#endif + (void)createAddItem( fileMenu, "Cancel trade", + GTK_SIGNAL_FUNC(handle_trade_cancel), globals ); + + fileMenu = makeAddSubmenu( menubar, "Edit" ); + + (void)createAddItem( fileMenu, "Undo", + GTK_SIGNAL_FUNC(handle_undo), globals ); + (void)createAddItem( fileMenu, "Redo", + GTK_SIGNAL_FUNC(handle_redo), globals ); + +#ifdef FEATURE_TRAY_EDIT + (void)createAddItem( fileMenu, "Allow tray edit", + GTK_SIGNAL_FUNC(handle_trayEditToggle_on), globals ); + (void)createAddItem( fileMenu, "Dis-allow tray edit", + GTK_SIGNAL_FUNC(handle_trayEditToggle_off), globals ); +#endif + fileMenu = makeAddSubmenu( menubar, "Network" ); + +#ifndef XWFEATURE_STANDALONE_ONLY + (void)createAddItem( fileMenu, "Resend", + GTK_SIGNAL_FUNC(handle_resend), globals ); +#ifdef XWFEATURE_COMMSACK + (void)createAddItem( fileMenu, "ack any", + GTK_SIGNAL_FUNC(handle_ack), globals ); +#endif +# ifdef DEBUG + (void)createAddItem( fileMenu, "Stats", + GTK_SIGNAL_FUNC(handle_commstats), globals ); +# endif +#endif +#ifdef MEM_DEBUG + (void)createAddItem( fileMenu, "Mem stats", + GTK_SIGNAL_FUNC(handle_memstats), globals ); +#endif + + /* (void)createAddItem( fileMenu, "Print board", */ + /* GTK_SIGNAL_FUNC(handle_print_board), globals ); */ + + /* listAllGames( menubar, argc, argv, globals ); */ + + gtk_widget_show( menubar ); + + return menubar; +} /* makeMenus */ + +static void +disenable_buttons( GtkAppGlobals* globals ) +{ + XP_Bool canFlip = 1 < board_visTileCount( globals->cGlobals.game.board ); + gtk_widget_set_sensitive( globals->flip_button, canFlip ); + + XP_Bool canToggle = board_canTogglePending( globals->cGlobals.game.board ); + gtk_widget_set_sensitive( globals->toggle_undo_button, canToggle ); + + XP_Bool canHing = board_canHint( globals->cGlobals.game.board ); + gtk_widget_set_sensitive( globals->prevhint_button, canHing ); + gtk_widget_set_sensitive( globals->nexthint_button, canHing ); + +#ifdef XWFEATURE_CHAT + XP_Bool canChat = !!globals->cGlobals.game.comms + && comms_canChat( globals->cGlobals.game.comms ); + gtk_widget_set_sensitive( globals->chat_button, canChat ); +#endif +} + +static gboolean +handle_flip_button( GtkWidget* XP_UNUSED(widget), gpointer _globals ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)_globals; + if ( board_flip( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } + return TRUE; +} /* handle_flip_button */ + +static gboolean +handle_value_button( GtkWidget* XP_UNUSED(widget), gpointer closure ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)closure; + if ( board_toggle_showValues( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } + return TRUE; +} /* handle_value_button */ + +static void +handle_hint_button( GtkAppGlobals* globals, XP_Bool prev ) +{ + XP_Bool redo; + if ( board_requestHint( globals->cGlobals.game.board, +#ifdef XWFEATURE_SEARCHLIMIT + XP_FALSE, +#endif + prev, &redo ) ) { + board_draw( globals->cGlobals.game.board ); + disenable_buttons( globals ); + } +} /* handle_hint_button */ + +static void +handle_prevhint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + handle_hint_button( globals, XP_TRUE ); +} /* handle_prevhint_button */ + +static void +handle_nexthint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + handle_hint_button( globals, XP_FALSE ); +} /* handle_nexthint_button */ + +static void +handle_nhint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + XP_Bool redo; + + board_resetEngine( globals->cGlobals.game.board ); + if ( board_requestHint( globals->cGlobals.game.board, +#ifdef XWFEATURE_SEARCHLIMIT + XP_TRUE, +#endif + XP_FALSE, &redo ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_nhint_button */ + +static void +handle_colors_button( GtkWidget* XP_UNUSED(widget), + GtkAppGlobals* XP_UNUSED(globals) ) +{ +/* XP_Bool oldVal = board_getShowColors( globals->cGlobals.game.board ); */ +/* if ( board_setShowColors( globals->cGlobals.game.board, !oldVal ) ) { */ +/* board_draw( globals->cGlobals.game.board ); */ +/* } */ +} /* handle_colors_button */ + +static void +handle_juggle_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + if ( board_juggleTray( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_juggle_button */ + +static void +handle_undo_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + if ( server_handleUndo( globals->cGlobals.game.server, 0 ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_undo_button */ + +static void +handle_redo_button( GtkWidget* XP_UNUSED(widget), + GtkAppGlobals* XP_UNUSED(globals) ) +{ +} /* handle_redo_button */ + +static void +handle_toggle_undo( GtkWidget* XP_UNUSED(widget), + GtkAppGlobals* globals ) +{ + BoardCtxt* board = globals->cGlobals.game.board; + if ( board_redoReplacedTiles( board ) || board_replaceTiles( board ) ) { + board_draw( board ); + } +} + +static void +handle_trade_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + if ( board_beginTrade( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + disenable_buttons( globals ); + } +} /* handle_juggle_button */ + +static void +handle_done_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + if ( board_commitTurn( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + disenable_buttons( globals ); + } +} /* handle_done_button */ + +static void +setZoomButtons( GtkAppGlobals* globals, XP_Bool* inOut ) +{ + gtk_widget_set_sensitive( globals->zoomin_button, inOut[0] ); + gtk_widget_set_sensitive( globals->zoomout_button, inOut[1] ); +} + +static void +handle_zoomin_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + XP_Bool inOut[2]; + if ( board_zoom( globals->cGlobals.game.board, 1, inOut ) ) { + board_draw( globals->cGlobals.game.board ); + setZoomButtons( globals, inOut ); + } +} /* handle_done_button */ + +static void +handle_zoomout_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + XP_Bool inOut[2]; + if ( board_zoom( globals->cGlobals.game.board, -1, inOut ) ) { + board_draw( globals->cGlobals.game.board ); + setZoomButtons( globals, inOut ); + } +} /* handle_done_button */ + +#ifdef XWFEATURE_CHAT +static void +handle_chat_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + gchar* msg = gtkGetChatMessage( globals ); + if ( NULL != msg ) { + server_sendChat( globals->cGlobals.game.server, msg ); + g_free( msg ); + } +} +#endif + +static void +scroll_value_changed( GtkAdjustment *adj, GtkAppGlobals* globals ) +{ + XP_U16 newValue; + gfloat newValueF = adj->value; + + /* XP_ASSERT( newValueF >= 0.0 */ + /* && newValueF <= globals->cGlobals.params->nHidden ); */ + newValue = (XP_U16)newValueF; + + if ( board_setYOffset( globals->cGlobals.game.board, newValue ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* scroll_value_changed */ + +static void +handle_grid_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + globals->gridOn = !globals->gridOn; + + board_invalAll( globals->cGlobals.game.board ); + board_draw( globals->cGlobals.game.board ); +} /* handle_grid_button */ + +static void +handle_hide_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + BoardCtxt* board; + XP_Bool draw = XP_FALSE; + + if ( globals->cGlobals.params->nHidden > 0 ) { + gint nRows = globals->cGlobals.params->gi.boardSize; + globals->adjustment->page_size = nRows; + globals->adjustment->value = 0.0; + + gtk_signal_emit_by_name( GTK_OBJECT(globals->adjustment), "changed" ); + gtk_adjustment_value_changed( GTK_ADJUSTMENT(globals->adjustment) ); + } + + board = globals->cGlobals.game.board; + if ( TRAY_REVEALED == board_getTrayVisState( board ) ) { + draw = board_hideTray( board ); + } else { + draw = board_showTray( board ); + } + if ( draw ) { + board_draw( board ); + } +} /* handle_hide_button */ + +static void +handle_commit_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + if ( board_commitTurn( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_commit_button */ + +static void +gtkUserError( GtkAppGlobals* globals, const char* format, ... ) +{ + char buf[512]; + va_list ap; + + va_start( ap, format ); + + vsprintf( buf, format, ap ); + + (void)gtkask( globals->window, buf, GTK_BUTTONS_OK ); + + va_end(ap); +} /* gtkUserError */ + +static VTableMgr* +gtk_util_getVTManager(XW_UtilCtxt* uc) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + return globals->cGlobals.params->vtMgr; +} /* linux_util_getVTManager */ + +static XP_S16 +gtk_util_userPickTileBlank( XW_UtilCtxt* uc, XP_U16 playerNum, + const XP_UCHAR** texts, XP_U16 nTiles ) +{ + XP_S16 chosen; + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + XP_UCHAR* name = globals->cGlobals.params->gi.players[playerNum].name; + + chosen = gtkletterask( NULL, XP_FALSE, name, nTiles, texts ); + return chosen; +} + +static XP_S16 +gtk_util_userPickTileTray( XW_UtilCtxt* uc, const PickInfo* pi, + XP_U16 playerNum, const XP_UCHAR** texts, + XP_U16 nTiles ) +{ + XP_S16 chosen; + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + XP_UCHAR* name = globals->cGlobals.params->gi.players[playerNum].name; + + chosen = gtkletterask( pi, XP_TRUE, name, nTiles, texts ); + return chosen; +} /* gtk_util_userPickTile */ + +static XP_Bool +gtk_util_askPassword( XW_UtilCtxt* XP_UNUSED(uc), const XP_UCHAR* name, + XP_UCHAR* buf, XP_U16* len ) +{ + XP_Bool ok = gtkpasswdask( name, buf, len ); + return ok; +} /* gtk_util_askPassword */ + +static void +setCtrlsForTray( GtkAppGlobals* XP_UNUSED(globals) ) +{ +#if 0 + XW_TrayVisState state = + board_getTrayVisState( globals->cGlobals.game.board ); + XP_S16 nHidden = globals->cGlobals.params->nHidden; + + if ( nHidden != 0 ) { + XP_U16 pageSize = nRows; + + if ( state == TRAY_HIDDEN ) { /* we recover what tray covers */ + nHidden -= GTK_TRAY_HT_ROWS; + } + if ( nHidden > 0 ) { + pageSize -= nHidden; + } + globals->adjustment->page_size = pageSize; + + globals->adjustment->value = + board_getYOffset( globals->cGlobals.game.board ); + gtk_signal_emit_by_name( GTK_OBJECT(globals->adjustment), "changed" ); + } +#endif +} /* setCtrlsForTray */ + +static void +gtk_util_trayHiddenChange( XW_UtilCtxt* uc, XW_TrayVisState XP_UNUSED(state), + XP_U16 XP_UNUSED(nVisibleRows) ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + setCtrlsForTray( globals ); +} /* gtk_util_trayHiddenChange */ + +static void +gtk_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 maxOffset, + XP_U16 XP_UNUSED(oldOffset), + XP_U16 newOffset ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + if ( !!globals->adjustment ) { + gint nRows = globals->cGlobals.params->gi.boardSize; + globals->adjustment->page_size = nRows - maxOffset; + globals->adjustment->value = newOffset; + gtk_adjustment_value_changed( GTK_ADJUSTMENT(globals->adjustment) ); + } +} /* gtk_util_yOffsetChange */ + +static void +gtkShowFinalScores( const GtkAppGlobals* globals ) +{ + XWStreamCtxt* stream; + XP_UCHAR* text; + const CommonGlobals* cGlobals = &globals->cGlobals; + + stream = mem_stream_make( MPPARM(cGlobals->params->util->mpool) + cGlobals->params->vtMgr, + NULL, CHANNEL_NONE, NULL ); + server_writeFinalScores( cGlobals->game.server, stream ); + + text = strFromStream( stream ); + stream_destroy( stream ); + + XP_U16 timeout = cGlobals->manualFinal? 0 : 500; + (void)gtkask_timeout( globals->window, text, GTK_BUTTONS_OK, timeout ); + + free( text ); +} /* gtkShowFinalScores */ + +static void +gtk_util_informMove( XW_UtilCtxt* uc, XWStreamCtxt* expl, + XWStreamCtxt* words ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + char* question = strFromStream( !!words? words : expl ); + (void)gtkask( globals->window, question, GTK_BUTTONS_OK ); + free( question ); +} + +static void +gtk_util_informUndo( XW_UtilCtxt* uc ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + (void)gtkask_timeout( globals->window, "Remote player undid a move", + GTK_BUTTONS_OK, 500 ); +} + +static void +gtk_util_notifyGameOver( XW_UtilCtxt* uc, XP_S16 quitter ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + CommonGlobals* cGlobals = &globals->cGlobals; + + if ( cGlobals->params->printHistory ) { + catGameHistory( cGlobals ); + } + + catFinalScores( cGlobals, quitter ); + + if ( cGlobals->params->quitAfter >= 0 ) { + sleep( cGlobals->params->quitAfter ); + quit(); + } else if ( cGlobals->params->undoWhenDone ) { + server_handleUndo( cGlobals->game.server, 0 ); + board_draw( cGlobals->game.board ); + } else if ( !cGlobals->params->skipGameOver ) { + gtkShowFinalScores( globals ); + } +} /* gtk_util_notifyGameOver */ + +static void +gtk_util_informNetDict( XW_UtilCtxt* uc, XP_LangCode XP_UNUSED(lang), + const XP_UCHAR* oldName, + const XP_UCHAR* newName, const XP_UCHAR* newSum, + XWPhoniesChoice phoniesAction ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + gchar buf[512]; + + int offset = snprintf( buf, VSIZE(buf), + "dict changing from %s to %s (sum=%s).", + oldName, newName, newSum ); + if ( PHONIES_DISALLOW == phoniesAction ) { + snprintf( &buf[offset], VSIZE(buf)-offset, "%s", + "\nPHONIES_DISALLOW is set so this may lead to some surprises." ); + } + (void)gtkask( globals->window, buf, GTK_BUTTONS_OK ); +} + +/* define this to prevent user events during debugging from stopping the engine */ +/* #define DONT_ABORT_ENGINE */ + +#ifdef XWFEATURE_HILITECELL +static XP_Bool +gtk_util_hiliteCell( XW_UtilCtxt* uc, XP_U16 col, XP_U16 row ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; +#ifndef DONT_ABORT_ENGINE + gboolean pending; +#endif + + board_hiliteCellAt( globals->cGlobals.game.board, col, row ); + if ( globals->cGlobals.params->sleepOnAnchor ) { + usleep( 10000 ); + } + +#ifdef DONT_ABORT_ENGINE + return XP_TRUE; /* keep going */ +#else + pending = gdk_events_pending(); + if ( pending ) { + XP_DEBUGF( "gtk_util_hiliteCell=>%d", pending ); + } + return !pending; +#endif +} /* gtk_util_hiliteCell */ +#endif + +static XP_Bool +gtk_util_altKeyDown( XW_UtilCtxt* uc ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + return globals->altKeyDown; +} + +static XP_Bool +gtk_util_engineProgressCallback( XW_UtilCtxt* XP_UNUSED(uc) ) +{ +#ifdef DONT_ABORT_ENGINE + return XP_TRUE; /* keep going */ +#else + gboolean pending = gdk_events_pending(); + +/* XP_DEBUGF( "gdk_events_pending returned %d\n", pending ); */ + + return !pending; +#endif +} /* gtk_util_engineProgressCallback */ + +static void +cancelTimer( GtkAppGlobals* globals, XWTimerReason why ) +{ + guint src = globals->timerSources[why-1]; + if ( src != 0 ) { + g_source_remove( src ); + globals->timerSources[why-1] = 0; + } +} /* cancelTimer */ + +static gint +pen_timer_func( gpointer data ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)data; + + if ( linuxFireTimer( &globals->cGlobals, TIMER_PENDOWN ) ) { + board_draw( globals->cGlobals.game.board ); + } + + return XP_FALSE; +} /* pentimer_idle_func */ + +static gint +score_timer_func( gpointer data ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)data; + + if ( linuxFireTimer( &globals->cGlobals, TIMER_TIMERTICK ) ) { + board_draw( globals->cGlobals.game.board ); + } + + return XP_FALSE; +} /* score_timer_func */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static gint +comms_timer_func( gpointer data ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)data; + + if ( linuxFireTimer( &globals->cGlobals, TIMER_COMMS ) ) { + board_draw( globals->cGlobals.game.board ); + } + + return (gint)0; +} +#endif + +#ifdef XWFEATURE_SLOW_ROBOT +static gint +slowrob_timer_func( gpointer data ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)data; + + if ( linuxFireTimer( &globals->cGlobals, TIMER_SLOWROBOT ) ) { + board_draw( globals->cGlobals.game.board ); + } + + return (gint)0; +} +#endif + +static void +gtk_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, + XP_U16 XP_UNUSED_STANDALONE(when), + XWTimerProc proc, void* closure ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + guint newSrc; + + cancelTimer( globals, why ); + + if ( why == TIMER_PENDOWN ) { + if ( 0 != globals->timerSources[why-1] ) { + g_source_remove( globals->timerSources[why-1] ); + } + newSrc = g_timeout_add( 1000, pen_timer_func, globals ); + } else if ( why == TIMER_TIMERTICK ) { + /* one second */ + globals->scoreTimerInterval = 100 * 10000; + + (void)gettimeofday( &globals->scoreTv, NULL ); + + newSrc = g_timeout_add( 1000, score_timer_func, globals ); +#ifndef XWFEATURE_STANDALONE_ONLY + } else if ( why == TIMER_COMMS ) { + newSrc = g_timeout_add( 1000 * when, comms_timer_func, globals ); +#endif +#ifdef XWFEATURE_SLOW_ROBOT + } else if ( why == TIMER_SLOWROBOT ) { + newSrc = g_timeout_add( 1000 * when, slowrob_timer_func, globals ); +#endif + } else { + XP_ASSERT( 0 ); + } + + globals->cGlobals.timerInfo[why].proc = proc; + globals->cGlobals.timerInfo[why].closure = closure; + XP_ASSERT( newSrc != 0 ); + globals->timerSources[why-1] = newSrc; +} /* gtk_util_setTimer */ + +static void +gtk_util_clearTimer( XW_UtilCtxt* uc, XWTimerReason why ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + globals->cGlobals.timerInfo[why].proc = NULL; +} + +static gint +idle_func( gpointer data ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)data; +/* XP_DEBUGF( "idle_func called\n" ); */ + + /* remove before calling server_do. If server_do puts up a dialog that + calls gtk_main, then this idle proc will also apply to that event loop + and bad things can happen. So kill the idle proc asap. */ + gtk_idle_remove( globals->idleID ); + + if ( server_do( globals->cGlobals.game.server ) ) { + if ( !!globals->cGlobals.game.board ) { + board_draw( globals->cGlobals.game.board ); + } + } + return 0; /* 0 will stop it from being called again */ +} /* idle_func */ + +static void +gtk_util_requestTime( XW_UtilCtxt* uc ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + globals->idleID = gtk_idle_add( idle_func, globals ); +} /* gtk_util_requestTime */ + +static XP_Bool +gtk_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 player, + XP_Bool turnLost ) +{ + XP_Bool result; + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + char buf[300]; + + if ( turnLost ) { + char wordsBuf[256]; + XP_U16 i; + XP_UCHAR* name = globals->cGlobals.params->gi.players[player].name; + XP_ASSERT( !!name ); + + for ( i = 0, wordsBuf[0] = '\0'; ; ) { + char wordBuf[18]; + sprintf( wordBuf, "\"%s\"", bwi->words[i] ); + strcat( wordsBuf, wordBuf ); + if ( ++i == bwi->nWords ) { + break; + } + strcat( wordsBuf, ", " ); + } + + sprintf( buf, "Player %d (%s) played illegal word[s] %s; loses turn.", + player+1, name, wordsBuf ); + + if ( globals->cGlobals.params->skipWarnings ) { + XP_LOGF( "%s", buf ); + } else { + gtkUserError( globals, buf ); + } + result = XP_TRUE; + } else { + XP_ASSERT( bwi->nWords == 1 ); + sprintf( buf, "Word \"%s\" not in the current dictionary (%s). " + "Use it anyway?", bwi->words[0], bwi->dictName ); + result = gtkask( globals->window, buf, GTK_BUTTONS_YES_NO ); + } + + return result; +} /* gtk_util_warnIllegalWord */ + +static void +gtk_util_remSelected( XW_UtilCtxt* uc ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + XWStreamCtxt* stream; + XP_UCHAR* text; + + stream = mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, CHANNEL_NONE, NULL ); + board_formatRemainingTiles( globals->cGlobals.game.board, stream ); + text = strFromStream( stream ); + stream_destroy( stream ); + + (void)gtkask( globals->window, text, GTK_BUTTONS_OK ); + free( text ); +} + +#ifndef XWFEATURE_STANDALONE_ONLY +static XWStreamCtxt* +gtk_util_makeStreamFromAddr(XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + + XWStreamCtxt* stream = mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + &globals->cGlobals, channelNo, + sendOnClose ); + return stream; +} /* gtk_util_makeStreamFromAddr */ + +#ifdef XWFEATURE_CHAT +static void +gtk_util_showChat( XW_UtilCtxt* uc, const XP_UCHAR* const msg ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + (void)gtkask( globals->window, msg, GTK_BUTTONS_OK ); +} +#endif +#endif + +#ifdef XWFEATURE_SEARCHLIMIT +static XP_Bool +gtk_util_getTraySearchLimits( XW_UtilCtxt* XP_UNUSED(uc), + XP_U16* XP_UNUSED(min), XP_U16* max ) +{ + *max = askNTiles( MAX_TRAY_TILES, *max ); + return XP_TRUE; +} +#endif + +#ifndef XWFEATURE_MINIWIN +static void +gtk_util_bonusSquareHeld( XW_UtilCtxt* uc, XWBonusType bonus ) +{ + LOG_FUNC(); + XP_USE( uc ); + XP_USE( bonus ); +} + +static void +gtk_util_playerScoreHeld( XW_UtilCtxt* uc, XP_U16 player ) +{ + LOG_FUNC(); + + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + + XP_UCHAR scoreExpl[48] = {0}; + XP_U16 explLen = sizeof(scoreExpl); + + if ( model_getPlayersLastScore( globals->cGlobals.game.model, + player, scoreExpl, &explLen ) ) { + XP_LOGF( "got: %s", scoreExpl ); + } +} +#endif + +#ifdef XWFEATURE_BOARDWORDS +static void +gtk_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words ) +{ + XP_USE( uc ); + catOnClose( words, NULL ); + fprintf( stderr, "\n" ); +} +#endif + +static void +gtk_util_userError( XW_UtilCtxt* uc, UtilErrID id ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + XP_Bool silent; + const XP_UCHAR* message = linux_getErrString( id, &silent ); + + XP_LOGF( "%s(%d)", __func__, id ); + + if ( silent ) { + XP_LOGF( "%s", message ); + } else { + gtkUserError( globals, message ); + } +} /* gtk_util_userError */ + +static XP_Bool +gtk_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id, + XWStreamCtxt* stream ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + XP_Bool result; + char* question; + XP_Bool freeMe = XP_FALSE; + GtkButtonsType buttons = GTK_BUTTONS_YES_NO; + + switch( id ) { + + case QUERY_COMMIT_TURN: + question = strFromStream( stream ); + freeMe = XP_TRUE; + break; + case QUERY_ROBOT_TRADE: + question = strFromStream( stream ); + freeMe = XP_TRUE; + buttons = GTK_BUTTONS_OK; + break; + + default: + XP_ASSERT( 0 ); + return XP_FALSE; + } + + result = gtkask( globals->window, question, buttons ); + + if ( freeMe ) { + free( question ); + } + + return result; +} /* gtk_util_userQuery */ + +static XP_Bool +gtk_util_confirmTrade( XW_UtilCtxt* uc, + const XP_UCHAR** tiles, XP_U16 nTiles ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + char question[256]; + formatConfirmTrade( tiles, nTiles, question, sizeof(question) ); + return gtkask( globals->window, question, GTK_BUTTONS_YES_NO ); +} + +static GtkWidget* +makeShowButtonFromBitmap( void* closure, const gchar* filename, + const gchar* alt, GCallback func ) +{ + GtkWidget* widget; + GtkWidget* button; + + if ( file_exists( filename ) ) { + widget = gtk_image_new_from_file( filename ); + } else { + widget = gtk_label_new( alt ); + } + gtk_widget_show( widget ); + + button = gtk_button_new(); + gtk_container_add (GTK_CONTAINER (button), widget ); + gtk_widget_show (button); + + if ( func != NULL ) { + g_signal_connect( GTK_OBJECT(button), "clicked", func, closure ); + } + + return button; +} /* makeShowButtonFromBitmap */ + +static GtkWidget* +makeVerticalBar( GtkAppGlobals* globals, GtkWidget* XP_UNUSED(window) ) +{ + GtkWidget* vbox; + GtkWidget* button; + + vbox = gtk_vbutton_box_new(); + + button = makeShowButtonFromBitmap( globals, "../flip.xpm", "f", + G_CALLBACK(handle_flip_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + globals->flip_button = button; + + button = makeShowButtonFromBitmap( globals, "../value.xpm", "v", + G_CALLBACK(handle_value_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, "../hint.xpm", "?-", + G_CALLBACK(handle_prevhint_button) ); + globals->prevhint_button = button; + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + button = makeShowButtonFromBitmap( globals, "../hint.xpm", "?+", + G_CALLBACK(handle_nexthint_button) ); + globals->nexthint_button = button; + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, "../hintNum.xpm", "n", + G_CALLBACK(handle_nhint_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, "../colors.xpm", "c", + G_CALLBACK(handle_colors_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + /* undo and redo buttons */ + button = makeShowButtonFromBitmap( globals, "../undo.xpm", "U", + G_CALLBACK(handle_undo_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + button = makeShowButtonFromBitmap( globals, "../redo.xpm", "R", + G_CALLBACK(handle_redo_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, "", "u/r", + G_CALLBACK(handle_toggle_undo) ); + globals->toggle_undo_button = button; + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + /* the four buttons that on palm are beside the tray */ + button = makeShowButtonFromBitmap( globals, "../juggle.xpm", "j", + G_CALLBACK(handle_juggle_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, "../trade.xpm", "t", + G_CALLBACK(handle_trade_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + button = makeShowButtonFromBitmap( globals, "../done.xpm", "d", + G_CALLBACK(handle_done_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + button = makeShowButtonFromBitmap( globals, "../done.xpm", "+", + G_CALLBACK(handle_zoomin_button) ); + globals->zoomin_button = button; + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + button = makeShowButtonFromBitmap( globals, "../done.xpm", "-", + G_CALLBACK(handle_zoomout_button) ); + globals->zoomout_button = button; + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); +#ifdef XWFEATURE_CHAT + button = makeShowButtonFromBitmap( globals, "", "chat", + G_CALLBACK(handle_chat_button) ); + globals->chat_button = button; + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); +#endif + + gtk_widget_show( vbox ); + return vbox; +} /* makeVerticalBar */ + +static GtkWidget* +makeButtons( GtkAppGlobals* globals ) +{ + short i; + GtkWidget* hbox; + GtkWidget* button; + + struct { + char* name; + GCallback func; + } buttons[] = { + /* { "Flip", handle_flip_button }, */ + { "Grid", G_CALLBACK(handle_grid_button) }, + { "Hide", G_CALLBACK(handle_hide_button) }, + { "Commit", G_CALLBACK(handle_commit_button) }, + }; + + hbox = gtk_hbox_new( FALSE, 0 ); + + for ( i = 0; i < sizeof(buttons)/sizeof(*buttons); ++i ) { + button = gtk_button_new_with_label( buttons[i].name ); + gtk_widget_show( button ); + g_signal_connect( GTK_OBJECT(button), "clicked", + G_CALLBACK(buttons[i].func), globals ); + + gtk_box_pack_start( GTK_BOX(hbox), button, FALSE, TRUE, 0); + } + + gtk_widget_show( hbox ); + return hbox; +} /* makeButtons */ + +static void +setupGtkUtilCallbacks( GtkAppGlobals* globals, XW_UtilCtxt* util ) +{ + util->vtable->m_util_userError = gtk_util_userError; + util->vtable->m_util_userQuery = gtk_util_userQuery; + util->vtable->m_util_confirmTrade = gtk_util_confirmTrade; + util->vtable->m_util_getVTManager = gtk_util_getVTManager; + util->vtable->m_util_userPickTileBlank = gtk_util_userPickTileBlank; + util->vtable->m_util_userPickTileTray = gtk_util_userPickTileTray; + util->vtable->m_util_askPassword = gtk_util_askPassword; + util->vtable->m_util_trayHiddenChange = gtk_util_trayHiddenChange; + util->vtable->m_util_yOffsetChange = gtk_util_yOffsetChange; + util->vtable->m_util_informMove = gtk_util_informMove; + util->vtable->m_util_informUndo = gtk_util_informUndo; + util->vtable->m_util_notifyGameOver = gtk_util_notifyGameOver; + util->vtable->m_util_informNetDict = gtk_util_informNetDict; +#ifdef XWFEATURE_HILITECELL + util->vtable->m_util_hiliteCell = gtk_util_hiliteCell; +#endif + util->vtable->m_util_altKeyDown = gtk_util_altKeyDown; + util->vtable->m_util_engineProgressCallback = + gtk_util_engineProgressCallback; + util->vtable->m_util_setTimer = gtk_util_setTimer; + util->vtable->m_util_clearTimer = gtk_util_clearTimer; + util->vtable->m_util_requestTime = gtk_util_requestTime; + util->vtable->m_util_warnIllegalWord = gtk_util_warnIllegalWord; + util->vtable->m_util_remSelected = gtk_util_remSelected; +#ifndef XWFEATURE_STANDALONE_ONLY + util->vtable->m_util_makeStreamFromAddr = gtk_util_makeStreamFromAddr; +#endif +#ifdef XWFEATURE_CHAT + util->vtable->m_util_showChat = gtk_util_showChat; +#endif +#ifdef XWFEATURE_SEARCHLIMIT + util->vtable->m_util_getTraySearchLimits = gtk_util_getTraySearchLimits; +#endif + +#ifndef XWFEATURE_MINIWIN + util->vtable->m_util_bonusSquareHeld = gtk_util_bonusSquareHeld; + util->vtable->m_util_playerScoreHeld = gtk_util_playerScoreHeld; +#endif +#ifdef XWFEATURE_BOARDWORDS + util->vtable->m_util_cellSquareHeld = gtk_util_cellSquareHeld; +#endif + + util->closure = globals; +} /* setupGtkUtilCallbacks */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static gboolean +newConnectionInput( GIOChannel *source, + GIOCondition condition, + gpointer data ) +{ + gboolean keepSource = TRUE; + int sock = g_io_channel_unix_get_fd( source ); + GtkAppGlobals* globals = (GtkAppGlobals*)data; + + XP_LOGF( "%s(%p):condition = 0x%x", __func__, source, (int)condition ); + +/* XP_ASSERT( sock == globals->cGlobals.socket ); */ + + if ( (condition & G_IO_IN) != 0 ) { + ssize_t nRead; + unsigned char buf[512]; + CommsAddrRec* addrp = NULL; +#if defined XWFEATURE_IP_DIRECT || defined XWFEATURE_SMS + CommsAddrRec addr; +#endif + + switch ( comms_getConType( globals->cGlobals.game.comms ) ) { +#ifdef XWFEATURE_RELAY + case COMMS_CONN_RELAY: + XP_ASSERT( globals->cGlobals.socket == sock ); + nRead = linux_relay_receive( &globals->cGlobals, buf, sizeof(buf) ); + break; +#endif +#ifdef XWFEATURE_BLUETOOTH + case COMMS_CONN_BT: + nRead = linux_bt_receive( sock, buf, sizeof(buf) ); + break; +#endif +#ifdef XWFEATURE_SMS + case COMMS_CONN_SMS: + addrp = &addr; + nRead = linux_sms_receive( &globals->cGlobals, sock, + buf, sizeof(buf), addrp ); + break; +#endif +#ifdef XWFEATURE_IP_DIRECT + case COMMS_CONN_IP_DIRECT: + addrp = &addr; + nRead = linux_udp_receive( sock, buf, sizeof(buf), addrp, &globals->cGlobals ); + break; +#endif + default: + XP_ASSERT( 0 ); + } + + if ( !globals->dropIncommingMsgs && nRead > 0 ) { + XWStreamCtxt* inboundS; + XP_Bool redraw = XP_FALSE; + + inboundS = stream_from_msgbuf( &globals->cGlobals, buf, nRead ); + if ( !!inboundS ) { + XP_LOGF( "%s: got %d bytes", __func__, nRead ); + if ( comms_checkIncomingStream( globals->cGlobals.game.comms, + inboundS, addrp ) ) { + redraw = + server_receiveMessage(globals->cGlobals.game.server, + inboundS ); + if ( redraw ) { + saveGame( &globals->cGlobals ); + } + } + stream_destroy( inboundS ); + } + + /* if there's something to draw resulting from the message, we + need to give the main loop time to reflect that on the screen + before giving the server another shot. So just call the idle + proc. */ + if ( redraw ) { + gtk_util_requestTime( globals->cGlobals.params->util ); + } else { + redraw = server_do( globals->cGlobals.game.server ); + } + if ( redraw ) { + board_draw( globals->cGlobals.game.board ); + } + } else { + XP_LOGF( "errno from read: %d/%s", errno, strerror(errno) ); + } + } + + if ( (condition & (G_IO_HUP | G_IO_ERR)) != 0 ) { + XP_LOGF( "dropping socket %d", sock ); + close( sock ); +#ifdef XWFEATURE_RELAY + globals->cGlobals.socket = -1; +#endif + if ( 0 ) { +#ifdef XWFEATURE_BLUETOOTH + } else if ( COMMS_CONN_BT == globals->cGlobals.params->conType ) { + linux_bt_socketclosed( &globals->cGlobals, sock ); +#endif +#ifdef XWFEATURE_IP_DIRECT + } else if ( COMMS_CONN_IP_DIRECT == globals->cGlobals.params->conType ) { + linux_udp_socketclosed( &globals->cGlobals, sock ); +#endif + } + keepSource = FALSE; /* remove the event source */ + } + + return keepSource; /* FALSE means to remove event source */ +} /* newConnectionInput */ + +typedef struct SockInfo { + GIOChannel* channel; + guint watch; + int socket; +} SockInfo; + +static void +gtk_socket_changed( void* closure, int oldSock, int newSock, void** storage ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)closure; + SockInfo* info = (SockInfo*)*storage; + XP_LOGF( "%s(old:%d; new:%d)", __func__, oldSock, newSock ); + + if ( oldSock != -1 ) { + XP_ASSERT( info != NULL ); + g_source_remove( info->watch ); + g_io_channel_unref( info->channel ); + XP_FREE( globals->cGlobals.params->util->mpool, info ); + *storage = NULL; + XP_LOGF( "Removed socket %d from gtk's list of listened-to sockets", + oldSock ); + } + if ( newSock != -1 ) { + info = (SockInfo*)XP_MALLOC( globals->cGlobals.params->util->mpool, + sizeof(*info) ); + GIOChannel* channel = g_io_channel_unix_new( newSock ); + g_io_channel_set_close_on_unref( channel, TRUE ); + guint result = g_io_add_watch( channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI, + newConnectionInput, + globals ); + info->channel = channel; + info->watch = result; + *storage = info; + XP_LOGF( "g_io_add_watch(%d) => %d", newSock, result ); + } +#ifdef XWFEATURE_RELAY + globals->cGlobals.socket = newSock; +#endif + /* A hack for the bluetooth case. */ + CommsCtxt* comms = globals->cGlobals.game.comms; + if ( (comms != NULL) && (comms_getConType(comms) == COMMS_CONN_BT) ) { + comms_resendAll( comms, XP_FALSE ); + } + LOG_RETURN_VOID(); +} /* gtk_socket_changed */ + +static gboolean +acceptorInput( GIOChannel* source, GIOCondition condition, gpointer data ) +{ + gboolean keepSource; + CommonGlobals* globals = (CommonGlobals*)data; + LOG_FUNC(); + + if ( (condition & G_IO_IN) != 0 ) { + int listener = g_io_channel_unix_get_fd( source ); + XP_LOGF( "%s: input on socket %d", __func__, listener ); + keepSource = (*globals->acceptor)( listener, data ); + } else { + keepSource = FALSE; + } + + return keepSource; +} /* acceptorInput */ + +static void +gtk_socket_acceptor( int listener, Acceptor func, CommonGlobals* globals, + void** storage ) +{ + SockInfo* info = (SockInfo*)*storage; + GIOChannel* channel; + guint watch; + + LOG_FUNC(); + + if ( listener == -1 ) { + XP_ASSERT( !!globals->acceptor ); + globals->acceptor = NULL; + XP_ASSERT( !!info ); +#ifdef DEBUG + int oldSock = info->socket; +#endif + g_source_remove( info->watch ); + g_io_channel_unref( info->channel ); + XP_FREE( globals->params->util->mpool, info ); + *storage = NULL; + XP_LOGF( "Removed listener %d from gtk's list of listened-to sockets", oldSock ); + } else { + XP_ASSERT( !globals->acceptor || (func == globals->acceptor) ); + globals->acceptor = func; + + channel = g_io_channel_unix_new( listener ); + g_io_channel_set_close_on_unref( channel, TRUE ); + watch = g_io_add_watch( channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI, + acceptorInput, globals ); + g_io_channel_unref( channel ); /* only main loop holds it now */ + XP_LOGF( "%s: g_io_add_watch(%d) => %d", __func__, listener, watch ); + + XP_ASSERT( NULL == info ); + info = XP_MALLOC( globals->params->util->mpool, sizeof(*info) ); + info->channel = channel; + info->watch = watch; + info->socket = listener; + *storage = info; + } +} /* gtk_socket_acceptor */ + +static void +drop_msg_toggle( GtkWidget* toggle, GtkAppGlobals* globals ) +{ + globals->dropIncommingMsgs = gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(toggle) ); +} /* drop_msg_toggle */ +#endif + +static GtkAppGlobals* g_globals_for_signal; +static void +handle_sigintterm( int XP_UNUSED(sig) ) +{ + LOG_FUNC(); + gtk_main_quit(); +} + +int +board_main( LaunchParams* params ) +{ + GtkAppGlobals globals; + initGlobals( &globals, params ); + + if ( !!params->pipe && !!params->fileName ) { + read_pipe_then_close( &globals.cGlobals, NULL ); + } else { + gtk_widget_show( globals.window ); + + gtk_main(); + } + /* MONCONTROL(1); */ + + cleanup( &globals ); + + return 0; +} /* gtkmain */ + +void +initGlobals( GtkAppGlobals* globals, LaunchParams* params ) +{ + short width, height; + GtkWidget* window; + GtkWidget* drawing_area; + GtkWidget* menubar; + GtkWidget* buttonbar; + GtkWidget* vbox; + GtkWidget* hbox; +#ifndef XWFEATURE_STANDALONE_ONLY + GtkWidget* dropCheck; +#endif + + g_globals_for_signal = globals; + struct sigaction act = { .sa_handler = handle_sigintterm }; + sigaction( SIGINT, &act, NULL ); + sigaction( SIGTERM, &act, NULL ); + + memset( globals, 0, sizeof(*globals) ); + + globals->cGlobals.params = params; + globals->cGlobals.lastNTilesToUse = MAX_TRAY_TILES; +#ifndef XWFEATURE_STANDALONE_ONLY +# ifdef XWFEATURE_RELAY + globals->cGlobals.socket = -1; +# endif + + globals->cGlobals.socketChanged = gtk_socket_changed; + globals->cGlobals.socketChangedClosure = &globals; + globals->cGlobals.addAcceptor = gtk_socket_acceptor; +#endif + + globals->cGlobals.cp.showBoardArrow = XP_TRUE; + globals->cGlobals.cp.hideTileValues = params->hideValues; + globals->cGlobals.cp.skipCommitConfirm = params->skipCommitConfirm; + globals->cGlobals.cp.sortNewTiles = params->sortNewTiles; + globals->cGlobals.cp.showColors = params->showColors; + globals->cGlobals.cp.allowPeek = params->allowPeek; + globals->cGlobals.cp.showRobotScores = params->showRobotScores; +#ifdef XWFEATURE_SLOW_ROBOT + globals->cGlobals.cp.robotThinkMin = params->robotThinkMin; + globals->cGlobals.cp.robotThinkMax = params->robotThinkMax; + globals->cGlobals.cp.robotTradePct = params->robotTradePct; +#endif +#ifdef XWFEATURE_CROSSHAIRS + globals->cGlobals.cp.hideCrosshairs = params->hideCrosshairs; +#endif + + setupGtkUtilCallbacks( globals, params->util ); + + globals->window = window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + if ( !!params->fileName ) { + gtk_window_set_title( GTK_WINDOW(window), params->fileName ); + } + + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_add( GTK_CONTAINER(window), vbox ); + gtk_widget_show( vbox ); + + g_signal_connect( G_OBJECT (window), "destroy", + G_CALLBACK( quit ), &globals ); + + menubar = makeMenus( globals ); + gtk_box_pack_start( GTK_BOX(vbox), menubar, FALSE, TRUE, 0); + +#ifndef XWFEATURE_STANDALONE_ONLY + dropCheck = gtk_check_button_new_with_label( "drop incoming messages" ); + g_signal_connect( GTK_OBJECT(dropCheck), + "toggled", G_CALLBACK(drop_msg_toggle), &globals ); + gtk_box_pack_start( GTK_BOX(vbox), dropCheck, FALSE, TRUE, 0); + gtk_widget_show( dropCheck ); +#endif + + buttonbar = makeButtons( globals ); + gtk_box_pack_start( GTK_BOX(vbox), buttonbar, FALSE, TRUE, 0); + + drawing_area = gtk_drawing_area_new(); + globals->drawing_area = drawing_area; + gtk_widget_show( drawing_area ); + + width = GTK_HOR_SCORE_WIDTH + GTK_TIMER_WIDTH + GTK_TIMER_PAD; + if ( globals->cGlobals.params->verticalScore ) { + width += GTK_VERT_SCORE_WIDTH; + } + height = 196; + if ( globals->cGlobals.params->nHidden == 0 ) { + height += GTK_MIN_SCALE * GTK_TRAY_HT_ROWS; + } + + gtk_widget_set_size_request( GTK_WIDGET(drawing_area), width, height ); + + hbox = gtk_hbox_new( FALSE, 0 ); + gtk_box_pack_start( GTK_BOX (hbox), drawing_area, TRUE, TRUE, 0); + + /* install scrollbar even if not needed -- since zooming can make it + needed later */ + GtkWidget* vscrollbar; + gint nRows = globals->cGlobals.params->gi.boardSize; + globals->adjustment = (GtkAdjustment*) + gtk_adjustment_new( 0, 0, nRows, 1, 2, + nRows - globals->cGlobals.params->nHidden ); + vscrollbar = gtk_vscrollbar_new( globals->adjustment ); + g_signal_connect( GTK_OBJECT(globals->adjustment), "value_changed", + G_CALLBACK(scroll_value_changed), globals ); + gtk_widget_show( vscrollbar ); + gtk_box_pack_start( GTK_BOX(hbox), vscrollbar, TRUE, TRUE, 0 ); + + gtk_box_pack_start( GTK_BOX (hbox), + makeVerticalBar( globals, window ), + FALSE, TRUE, 0 ); + gtk_widget_show( hbox ); + + gtk_box_pack_start( GTK_BOX(vbox), hbox/* drawing_area */, TRUE, TRUE, 0); + + g_signal_connect( GTK_OBJECT(drawing_area), "expose_event", + G_CALLBACK(expose_event), globals ); + g_signal_connect( GTK_OBJECT(drawing_area),"configure_event", + G_CALLBACK(configure_event), globals ); + g_signal_connect( GTK_OBJECT(drawing_area), "button_press_event", + G_CALLBACK(button_press_event), globals ); + g_signal_connect( GTK_OBJECT(drawing_area), "motion_notify_event", + G_CALLBACK(motion_notify_event), globals ); + g_signal_connect( GTK_OBJECT(drawing_area), "button_release_event", + G_CALLBACK(button_release_event), globals ); + + setOneSecondTimer( &globals->cGlobals ); + +#ifdef KEY_SUPPORT +# ifdef KEYBOARD_NAV + g_signal_connect( GTK_OBJECT(window), "key_press_event", + G_CALLBACK(key_press_event), globals ); +# endif + g_signal_connect( GTK_OBJECT(window), "key_release_event", + G_CALLBACK(key_release_event), globals ); +#endif + + gtk_widget_set_events( drawing_area, GDK_EXPOSURE_MASK + | GDK_LEAVE_NOTIFY_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_POINTER_MOTION_MASK + | GDK_BUTTON_RELEASE_MASK +#ifdef KEY_SUPPORT +# ifdef KEYBOARD_NAV + | GDK_KEY_PRESS_MASK +# endif + | GDK_KEY_RELEASE_MASK +#endif +/* | GDK_POINTER_MOTION_HINT_MASK */ + ); +} /* initGlobals */ + +void +freeGlobals( GtkAppGlobals* globals ) +{ + cleanup( globals ); +} + +XP_Bool +makeNewGame( GtkAppGlobals* globals ) +{ + return new_game_impl( globals, XP_FALSE ); +} + +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkboard.h b/xwords4/linux/gtkboard.h new file mode 100644 index 000000000..d8263d428 --- /dev/null +++ b/xwords4/linux/gtkboard.h @@ -0,0 +1,176 @@ +/* -*- mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* Copyright 1997 - 2005 by Eric House (xwords@eehouse.org) All rights + * reserved. + * + * This program 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 program 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. + */ + +#ifndef _GTKBOARD_H_ +#define _GTKBOARD_H_ + +#ifdef PLATFORM_GTK +#include +#include +#include +#include + +#include "draw.h" +#include "main.h" +#include "game.h" +#include "dictnry.h" + +enum { + LAYOUT_BOARD + ,LAYOUT_SMALL + ,LAYOUT_LARGE + ,LAYOUT_NLAYOUTS +}; + +#define MAX_SCORE_LEN 31 + +typedef struct GtkDrawCtx { + DrawCtxVTable* vtable; + +/* GdkDrawable* pixmap; */ + GtkWidget* drawing_area; + struct GtkAppGlobals* globals; + +#ifdef USE_CAIRO + cairo_t* cr; +#else + GdkGC* drawGC; +#endif + + GdkColor black; + GdkColor white; + GdkColor red; /* for pending tiles */ + GdkColor tileBack; /* for pending tiles */ + GdkColor cursor; + GdkColor bonusColors[4]; + GdkColor playerColors[MAX_NUM_PLAYERS]; + + /* new for gtk 2.0 */ + PangoContext* pangoContext; + GList* fontsPerSize; + + struct { + XP_UCHAR str[MAX_SCORE_LEN+1]; + XP_U16 fontHt; + } scoreCache[MAX_NUM_PLAYERS]; + + XP_U16 trayOwner; + XP_U16 cellWidth; + XP_U16 cellHeight; + + XP_Bool scoreIsVertical; +} GtkDrawCtx; + +typedef struct ClientStreamRec { + XWStreamCtxt* stream; + guint key; + int sock; +} ClientStreamRec; + +typedef struct GtkAppGlobals { + CommonGlobals cGlobals; + GtkWidget* window; + GtkDrawCtx* draw; + +/* GdkPixmap* pixmap; */ + GtkWidget* drawing_area; + + GtkWidget* flip_button; + GtkWidget* zoomin_button; + GtkWidget* zoomout_button; + GtkWidget* toggle_undo_button; + GtkWidget* prevhint_button; + GtkWidget* nexthint_button; + +#ifdef XWFEATURE_CHAT + GtkWidget* chat_button; +#endif + + EngineCtxt* engine; + + guint idleID; + + struct timeval scoreTv; /* for timer */ + XP_U32 scoreTimerInterval; + + GtkAdjustment* adjustment; + + ClientStreamRec clientRecs[MAX_NUM_PLAYERS]; + + guint timerSources[NUM_TIMERS_PLUS_ONE - 1]; + +#ifndef XWFEATURE_STANDALONE_ONLY + XP_U16 netStatLeft, netStatTop; + XP_UCHAR stateChar; +#endif + + XP_Bool gridOn; + XP_Bool dropIncommingMsgs; + XP_Bool mouseDown; + XP_Bool altKeyDown; +#ifdef KEYBOARD_NAV + XP_Bool keyDown; +#endif +} GtkAppGlobals; + +/* DictionaryCtxt* gtk_dictionary_make(); */ +#define GTK_MIN_SCALE 12 /* was 14 */ + +#define GTK_MIN_TRAY_SCALEH 24 +#define GTK_MIN_TRAY_SCALEV GTK_MIN_TRAY_SCALEH +#define GTK_TRAYPAD_WIDTH 2 + +#define GTK_TOP_MARGIN 0 /* was 2 */ +#define GTK_BOARD_LEFT_MARGIN 2 +#define GTK_TRAY_LEFT_MARGIN 2 +#define GTK_SCORE_BOARD_PADDING 0 + +#define GTK_HOR_SCORE_LEFT (GTK_BOARD_LEFT_MARGIN) +#define GTK_HOR_SCORE_HEIGHT 12 +#define GTK_TIMER_HEIGHT GTK_HOR_SCORE_HEIGHT +#define GTK_HOR_SCORE_TOP (GTK_TOP_MARGIN) +#define GTK_TIMER_PAD 10 +#define GTK_VERT_SCORE_TOP (GTK_TIMER_HEIGHT + GTK_TIMER_PAD) +#define GTK_VERT_SCORE_HEIGHT ((MIN_SCALE*MAX_COLS) - GTK_TIMER_HEIGHT - \ + GTK_TIMER_PAD) +#define GTK_TIMER_WIDTH 40 +#define GTK_NETSTAT_WIDTH 20 +#define GTK_TIMER_TOP GTK_HOR_SCORE_TOP +#define GTK_HOR_SCORE_WIDTH ((GTK_MIN_SCALE*20)-GTK_TIMER_PAD) +#define GTK_VERT_SCORE_WIDTH 40 + +#define GTK_BOARD_TOP (GTK_SCORE_TOP + GTK_SCORE_HEIGHT \ + + GTK_SCORE_BOARD_PADDING ) +#define GTK_BOARD_LEFT (GTK_BOARD_LEFT_MARGIN) + +#define GTK_TRAY_LEFT GTK_TRAY_LEFT_MARGIN + +#define GTK_DIVIDER_WIDTH 5 + +#define GTK_BOTTOM_MARGIN GTK_TOP_MARGIN +#define GTK_RIGHT_MARGIN GTK_BOARD_LEFT_MARGIN + +void initGlobals( GtkAppGlobals* globals, LaunchParams* params ); +void freeGlobals( GtkAppGlobals* globals ); +XP_Bool makeNewGame( GtkAppGlobals* globals ); + + +#endif /* PLATFORM_GTK */ + +#endif diff --git a/xwords4/linux/gtkchat.h b/xwords4/linux/gtkchat.h index 1f171eafe..0c3b80828 100644 --- a/xwords4/linux/gtkchat.h +++ b/xwords4/linux/gtkchat.h @@ -23,7 +23,7 @@ #ifdef PLATFORM_GTK #include -#include "gtkmain.h" +#include "gtkboard.h" gchar* gtkGetChatMessage( GtkAppGlobals* globals ); diff --git a/xwords4/linux/gtkconnsdlg.h b/xwords4/linux/gtkconnsdlg.h index 8581b22a5..31a5d7325 100644 --- a/xwords4/linux/gtkconnsdlg.h +++ b/xwords4/linux/gtkconnsdlg.h @@ -24,7 +24,7 @@ #ifndef _GTKCONNSDLG_H_ #define _GTKCONNSDLG_H_ -#include "gtkmain.h" +#include "gtkboard.h" gboolean gtkConnsDlg( GtkAppGlobals* globals, CommsAddrRec* addr, DeviceRole role, XP_Bool readOnly ); diff --git a/xwords4/linux/gtkdraw.c b/xwords4/linux/gtkdraw.c index 7aa67c379..e352e7abf 100644 --- a/xwords4/linux/gtkdraw.c +++ b/xwords4/linux/gtkdraw.c @@ -26,7 +26,7 @@ #include -#include "gtkmain.h" +#include "gtkboard.h" #include "draw.h" #include "board.h" #include "linuxmain.h" diff --git a/xwords4/linux/gtkletterask.h b/xwords4/linux/gtkletterask.h index ff7c3f02b..474279a97 100644 --- a/xwords4/linux/gtkletterask.h +++ b/xwords4/linux/gtkletterask.h @@ -24,7 +24,7 @@ #ifndef _GTKLETTERASK_H_ #define _GTKLETTERASK_H_ -#include "gtkmain.h" +#include "gtkboard.h" XP_S16 gtkletterask( const PickInfo* pi, XP_Bool forTray, const XP_UCHAR* name, diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index 6be62cea4..842557aef 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -1,6 +1,7 @@ /* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ /* - * Copyright 2000-2009 by Eric House (xwords@eehouse.org). All rights reserved. + * Copyright 2000-2013 by Eric House (xwords@eehouse.org). All rights + * reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -19,2435 +20,85 @@ #ifdef PLATFORM_GTK -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#ifndef CLIENT_ONLY -/* # include */ -#endif -#include -#include -#include -#include - #include "main.h" +#include "gamesdb.h" +#include "gtkboard.h" #include "linuxmain.h" -#include "linuxutl.h" -#include "linuxbt.h" -#include "linuxudp.h" -#include "linuxsms.h" -/* #include "gtkmain.h" */ -#include "draw.h" -#include "game.h" -#include "gtkask.h" -#include "gtkchat.h" -#include "gtknewgame.h" -#include "gtkletterask.h" -#include "gtkpasswdask.h" -#include "gtkntilesask.h" -#include "gtkaskdict.h" -/* #include "undo.h" */ -#include "gtkdraw.h" -#include "memstream.h" -#include "filestream.h" +enum { NAME_ITEM, N_ITEMS }; -/* static guint gtkSetupClientSocket( GtkAppGlobals* globals, int sock ); */ -static void setCtrlsForTray( GtkAppGlobals* globals ); -static void new_game( GtkWidget* widget, GtkAppGlobals* globals ); -static void new_game_impl( GtkAppGlobals* globals, XP_Bool fireConnDlg ); -static void setZoomButtons( GtkAppGlobals* globals, XP_Bool* inOut ); -static void disenable_buttons( GtkAppGlobals* globals ); +typedef struct _GTKGamesGlobals { + sqlite3* pDb; + GtkAppGlobals globals; + LaunchParams* params; +} GTKGamesGlobals; -#define GTK_TRAY_HT_ROWS 3 - -#if 0 -static XWStreamCtxt* -lookupClientStream( GtkAppGlobals* globals, int sock ) -{ - short i; - for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { - ClientStreamRec* rec = &globals->clientRecs[i]; - if ( rec->sock == sock ) { - XP_ASSERT( rec->stream != NULL ); - return rec->stream; - } - } - XP_ASSERT( i < MAX_NUM_PLAYERS ); - return NULL; -} /* lookupClientStream */ - static void -rememberClient( GtkAppGlobals* globals, guint key, int sock, - XWStreamCtxt* stream ) +init_games_list( GtkWidget* list ) { - short i; - for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { - ClientStreamRec* rec = &globals->clientRecs[i]; - if ( rec->stream == NULL ) { - XP_ASSERT( stream != NULL ); - rec->stream = stream; - rec->key = key; - rec->sock = sock; - break; - } - } - XP_ASSERT( i < MAX_NUM_PLAYERS ); -} /* rememberClient */ -#endif - -static void -gtkSetAltState( GtkAppGlobals* globals, guint state ) -{ - globals->altKeyDown - = (state & (GDK_MOD1_MASK|GDK_SHIFT_MASK|GDK_CONTROL_MASK)) != 0; + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn* column = + gtk_tree_view_column_new_with_attributes( "Games", renderer, + "text", NAME_ITEM, NULL ); + gtk_tree_view_append_column( GTK_TREE_VIEW(list), column ); + GtkListStore* store = gtk_list_store_new( N_ITEMS, G_TYPE_STRING ); + gtk_tree_view_set_model( GTK_TREE_VIEW(list), GTK_TREE_MODEL(store) ); + g_object_unref( store ); } -static gint -button_press_event( GtkWidget* XP_UNUSED(widget), GdkEventButton *event, - GtkAppGlobals* globals ) +static void +handle_newgame_button( GtkWidget* XP_UNUSED(widget), void* closure ) { - XP_Bool redraw, handled; - - gtkSetAltState( globals, event->state ); - - if ( !globals->mouseDown ) { - globals->mouseDown = XP_TRUE; - redraw = board_handlePenDown( globals->cGlobals.game.board, - event->x, event->y, &handled ); - if ( redraw ) { - board_draw( globals->cGlobals.game.board ); - disenable_buttons( globals ); - } - } - return 1; -} /* button_press_event */ - -static gint -motion_notify_event( GtkWidget* XP_UNUSED(widget), GdkEventMotion *event, - GtkAppGlobals* globals ) -{ - XP_Bool handled; - - gtkSetAltState( globals, event->state ); - - if ( globals->mouseDown ) { - handled = board_handlePenMove( globals->cGlobals.game.board, event->x, - event->y ); - if ( handled ) { - board_draw( globals->cGlobals.game.board ); - disenable_buttons( globals ); - } + GTKGamesGlobals* gg = (GTKGamesGlobals*)closure; + XP_LOGF( "%s called", __func__ ); + GtkAppGlobals* globals = malloc( sizeof(*globals) ); + initGlobals( globals, gg->params ); + if ( !makeNewGame( globals ) ) { + freeGlobals( globals ); } else { - handled = XP_FALSE; - } - - return handled; -} /* motion_notify_event */ - -static gint -button_release_event( GtkWidget* XP_UNUSED(widget), GdkEventMotion *event, - GtkAppGlobals* globals ) -{ - XP_Bool redraw; - - gtkSetAltState( globals, event->state ); - - if ( globals->mouseDown ) { - redraw = board_handlePenUp( globals->cGlobals.game.board, - event->x, - event->y ); - if ( redraw ) { - board_draw( globals->cGlobals.game.board ); - disenable_buttons( globals ); - } - globals->mouseDown = XP_FALSE; - } - return 1; -} /* button_release_event */ - -#ifdef KEY_SUPPORT -static XP_Key -evtToXPKey( GdkEventKey* event, XP_Bool* movesCursorP ) -{ - XP_Key xpkey = XP_KEY_NONE; - XP_Bool movesCursor = XP_FALSE; - guint keyval = event->keyval; - - switch( keyval ) { -#ifdef KEYBOARD_NAV - case GDK_Return: - xpkey = XP_RETURN_KEY; - break; - case GDK_space: - xpkey = XP_RAISEFOCUS_KEY; - break; - - case GDK_Left: - xpkey = XP_CURSOR_KEY_LEFT; - movesCursor = XP_TRUE; - break; - case GDK_Right: - xpkey = XP_CURSOR_KEY_RIGHT; - movesCursor = XP_TRUE; - break; - case GDK_Up: - xpkey = XP_CURSOR_KEY_UP; - movesCursor = XP_TRUE; - break; - case GDK_Down: - xpkey = XP_CURSOR_KEY_DOWN; - movesCursor = XP_TRUE; - break; -#endif - case GDK_BackSpace: - XP_LOGF( "... it's a DEL" ); - xpkey = XP_CURSOR_KEY_DEL; - break; - default: - keyval = keyval & 0x00FF; /* mask out gtk stuff */ - if ( isalpha( keyval ) ) { - xpkey = toupper(keyval); - break; -#ifdef NUMBER_KEY_AS_INDEX - } else if ( isdigit( keyval ) ) { - xpkey = keyval; - break; -#endif - } - } - *movesCursorP = movesCursor; - return xpkey; -} /* evtToXPKey */ - -#ifdef KEYBOARD_NAV -static gint -key_press_event( GtkWidget* XP_UNUSED(widget), GdkEventKey* event, - GtkAppGlobals* globals ) -{ - XP_Bool handled = XP_FALSE; - XP_Bool movesCursor; - XP_Key xpkey = evtToXPKey( event, &movesCursor ); - - gtkSetAltState( globals, event->state ); - - if ( xpkey != XP_KEY_NONE ) { - XP_Bool draw = globals->keyDown ? - board_handleKeyRepeat( globals->cGlobals.game.board, xpkey, - &handled ) - : board_handleKeyDown( globals->cGlobals.game.board, xpkey, - &handled ); - if ( draw ) { - board_draw( globals->cGlobals.game.board ); - } - } - globals->keyDown = XP_TRUE; - return 1; -} -#endif - -static gint -key_release_event( GtkWidget* XP_UNUSED(widget), GdkEventKey* event, - GtkAppGlobals* globals ) -{ - XP_Bool handled = XP_FALSE; - XP_Bool movesCursor; - XP_Key xpkey = evtToXPKey( event, &movesCursor ); - - gtkSetAltState( globals, event->state ); - - if ( xpkey != XP_KEY_NONE ) { - XP_Bool draw; - draw = board_handleKeyUp( globals->cGlobals.game.board, xpkey, - &handled ); -#ifdef KEYBOARD_NAV - if ( movesCursor && !handled ) { - BoardObjectType order[] = { OBJ_SCORE, OBJ_BOARD, OBJ_TRAY }; - draw = linShiftFocus( &globals->cGlobals, xpkey, order, - NULL ) || draw; - } -#endif - if ( draw ) { - board_draw( globals->cGlobals.game.board ); - } - } - -/* XP_ASSERT( globals->keyDown ); */ -#ifdef KEYBOARD_NAV - globals->keyDown = XP_FALSE; -#endif - - return handled? 1 : 0; /* gtk will do something with the key if 0 - returned */ -} /* key_release_event */ -#endif - -#ifdef MEM_DEBUG -# define MEMPOOL globals->cGlobals.params->util->mpool, -#else -# define MEMPOOL -#endif - -static void -relay_status_gtk( void* closure, CommsRelayState state ) -{ - XP_LOGF( "%s got status: %s", __func__, CommsRelayState2Str(state) ); - GtkAppGlobals* globals = (GtkAppGlobals*)closure; - globals->cGlobals.state = state; - globals->stateChar = 'A' + COMMS_RELAYSTATE_ALLCONNECTED - state; - draw_gtk_status( globals->draw, globals->stateChar ); -} - -static void -relay_connd_gtk( void* closure, XP_UCHAR* const room, - XP_Bool XP_UNUSED(reconnect), XP_U16 devOrder, - XP_Bool allHere, XP_U16 nMissing ) -{ - XP_Bool skip = XP_FALSE; - char buf[256]; - - if ( allHere ) { - snprintf( buf, sizeof(buf), - "All expected players have joined in %s. Play!", room ); - } else { - if ( nMissing > 0 ) { - snprintf( buf, sizeof(buf), "%dth connected to relay; waiting " - "in %s for %d player[s].", devOrder, room, nMissing ); - } else { - /* an allHere message should be coming immediately, so no - notification now. */ - skip = XP_TRUE; - } - } - - if ( !skip ) { - GtkAppGlobals* globals = (GtkAppGlobals*)closure; - (void)gtkask_timeout( globals->window, buf, GTK_BUTTONS_OK, 500 ); + GtkWidget* gameWindow = globals->window; + gtk_widget_show( gameWindow ); } } -static gint -invoke_new_game( gpointer data ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)data; - new_game_impl( globals, XP_FALSE ); - return 0; -} - -static gint -invoke_new_game_conns( gpointer data ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)data; - new_game_impl( globals, XP_TRUE ); - return 0; -} - -static void -relay_error_gtk( void* closure, XWREASON relayErr ) -{ - LOG_FUNC(); - GtkAppGlobals* globals = (GtkAppGlobals*)closure; - - gint (*proc)( gpointer data ) = NULL; - switch( relayErr ) { - case XWRELAY_ERROR_NO_ROOM: - case XWRELAY_ERROR_DUP_ROOM: - proc = invoke_new_game_conns; - break; - case XWRELAY_ERROR_TOO_MANY: - case XWRELAY_ERROR_BADPROTO: - proc = invoke_new_game; - break; - case XWRELAY_ERROR_DELETED: - gtkask_timeout( globals->window, - "relay says another device deleted game.", - GTK_BUTTONS_OK, 1000 ); - break; - case XWRELAY_ERROR_DEADGAME: - break; - default: - assert(0); - break; - } - - if ( !!proc ) { - (void)g_idle_add( proc, globals ); - } -} - -static void -createOrLoadObjects( GtkAppGlobals* globals ) -{ - XWStreamCtxt* stream = NULL; - XP_Bool opened = XP_FALSE; - -#ifndef XWFEATURE_STANDALONE_ONLY - DeviceRole serverRole = globals->cGlobals.params->serverRole; - XP_Bool isServer = serverRole != SERVER_ISCLIENT; -#endif - LaunchParams* params = globals->cGlobals.params; - - globals->draw = (GtkDrawCtx*)gtkDrawCtxtMake( globals->drawing_area, - globals ); - - TransportProcs procs = { - .closure = globals, - .send = LINUX_SEND, -#ifdef COMMS_HEARTBEAT - .reset = linux_reset, -#endif -#ifdef XWFEATURE_RELAY - .rstatus = relay_status_gtk, - .rconnd = relay_connd_gtk, - .rerror = relay_error_gtk, -#endif - }; - - if ( !!params->fileName && file_exists( params->fileName ) ) { - stream = streamFromFile( &globals->cGlobals, params->fileName, globals ); -#ifdef USE_SQLITE - } else if ( !!params->dbFileName && file_exists( params->dbFileName ) ) { - stream = streamFromDB( &globals->cGlobals, globals ); -#endif - } - - if ( !!stream ) { - opened = game_makeFromStream( MEMPOOL stream, &globals->cGlobals.game, - &globals->cGlobals.params->gi, - params->dict, ¶ms->dicts, params->util, - (DrawCtx*)globals->draw, - &globals->cGlobals.cp, &procs ); - - stream_destroy( stream ); - } - - if ( !opened ) { - CommsAddrRec addr; - - XP_MEMSET( &addr, 0, sizeof(addr) ); - addr.conType = params->conType; - -#ifdef XWFEATURE_RELAY - if ( addr.conType == COMMS_CONN_RELAY ) { - XP_ASSERT( !!params->connInfo.relay.relayName ); - globals->cGlobals.defaultServerName - = params->connInfo.relay.relayName; - } -#endif - - game_makeNewGame( MEMPOOL &globals->cGlobals.game, ¶ms->gi, - params->util, (DrawCtx*)globals->draw, - &globals->cGlobals.cp, &procs, params->gameSeed ); - - addr.conType = params->conType; - if ( 0 ) { -#ifdef XWFEATURE_RELAY - } else if ( addr.conType == COMMS_CONN_RELAY ) { - addr.u.ip_relay.ipAddr = 0; - addr.u.ip_relay.port = params->connInfo.relay.defaultSendPort; - addr.u.ip_relay.seeksPublicRoom = params->connInfo.relay.seeksPublicRoom; - addr.u.ip_relay.advertiseRoom = params->connInfo.relay.advertiseRoom; - XP_STRNCPY( addr.u.ip_relay.hostName, params->connInfo.relay.relayName, - sizeof(addr.u.ip_relay.hostName) - 1 ); - XP_STRNCPY( addr.u.ip_relay.invite, params->connInfo.relay.invite, - sizeof(addr.u.ip_relay.invite) - 1 ); -#endif -#ifdef XWFEATURE_BLUETOOTH - } else if ( addr.conType == COMMS_CONN_BT ) { - XP_ASSERT( sizeof(addr.u.bt.btAddr) - >= sizeof(params->connInfo.bt.hostAddr)); - XP_MEMCPY( &addr.u.bt.btAddr, ¶ms->connInfo.bt.hostAddr, - sizeof(params->connInfo.bt.hostAddr) ); -#endif -#ifdef XWFEATURE_IP_DIRECT - } else if ( addr.conType == COMMS_CONN_IP_DIRECT ) { - XP_STRNCPY( addr.u.ip.hostName_ip, params->connInfo.ip.hostName, - sizeof(addr.u.ip.hostName_ip) - 1 ); - addr.u.ip.port_ip = params->connInfo.ip.port; -#endif -#ifdef XWFEATURE_SMS - } else if ( addr.conType == COMMS_CONN_SMS ) { - XP_STRNCPY( addr.u.sms.phone, params->connInfo.sms.serverPhone, - sizeof(addr.u.sms.phone) - 1 ); - addr.u.sms.port = params->connInfo.sms.port; -#endif - } - -#ifndef XWFEATURE_STANDALONE_ONLY - /* This may trigger network activity */ - if ( !!globals->cGlobals.game.comms ) { - comms_setAddr( globals->cGlobals.game.comms, &addr ); - } -#endif - model_setDictionary( globals->cGlobals.game.model, params->dict ); - setSquareBonuses( &globals->cGlobals ); - model_setPlayerDicts( globals->cGlobals.game.model, ¶ms->dicts ); - -#ifdef XWFEATURE_SEARCHLIMIT - params->gi.allowHintRect = params->allowHintRect; -#endif - - if ( params->needsNewGame ) { - new_game_impl( globals, XP_FALSE ); -#ifndef XWFEATURE_STANDALONE_ONLY - } else if ( !isServer ) { - XWStreamCtxt* stream = - mem_stream_make( MEMPOOL params->vtMgr, &globals->cGlobals, CHANNEL_NONE, - sendOnClose ); - server_initClientConnection( globals->cGlobals.game.server, - stream ); -#endif - } - } - -#ifndef XWFEATURE_STANDALONE_ONLY - if ( !!globals->cGlobals.game.comms ) { - comms_start( globals->cGlobals.game.comms ); - } -#endif - server_do( globals->cGlobals.game.server ); - - disenable_buttons( globals ); -} /* createOrLoadObjects */ - -/* Create a new backing pixmap of the appropriate size and set up contxt to - * draw using that size. - */ -static gboolean -configure_event( GtkWidget* widget, GdkEventConfigure* XP_UNUSED(event), - GtkAppGlobals* globals ) -{ - short bdWidth, bdHeight; - short timerLeft, timerTop; - gint hscale, vscale; - gint trayTop; - gint boardTop = 0; - XP_U16 netStatWidth = 0; - gint nCols = globals->cGlobals.params->gi.boardSize; - gint nRows = nCols; - - if ( globals->draw == NULL ) { - createOrLoadObjects( globals ); - } - - bdWidth = widget->allocation.width - (GTK_RIGHT_MARGIN - + GTK_BOARD_LEFT_MARGIN); - if ( globals->cGlobals.params->verticalScore ) { - bdWidth -= GTK_VERT_SCORE_WIDTH; - } - bdHeight = widget->allocation.height - (GTK_TOP_MARGIN + GTK_BOTTOM_MARGIN) - - GTK_MIN_TRAY_SCALEV - GTK_BOTTOM_MARGIN; - - hscale = bdWidth / nCols; - if ( 0 != globals->cGlobals.params->nHidden ) { - vscale = hscale; - } else { - vscale = (bdHeight / (nCols + GTK_TRAY_HT_ROWS)); /* makd tray height - 3x cell height */ - } - - if ( !globals->cGlobals.params->verticalScore ) { - boardTop += GTK_HOR_SCORE_HEIGHT; - } - - trayTop = boardTop + (vscale * nRows); - /* move tray up if part of board's meant to be hidden */ - trayTop -= vscale * globals->cGlobals.params->nHidden; - board_setPos( globals->cGlobals.game.board, GTK_BOARD_LEFT, boardTop, - hscale * nCols, vscale * nRows, hscale * 4, XP_FALSE ); - /* board_setScale( globals->cGlobals.game.board, hscale, vscale ); */ - globals->gridOn = XP_TRUE; - - if ( !!globals->cGlobals.game.comms ) { - netStatWidth = GTK_NETSTAT_WIDTH; - } - - timerTop = GTK_TIMER_TOP; - if ( globals->cGlobals.params->verticalScore ) { - timerLeft = GTK_BOARD_LEFT + (hscale*nCols) + 1; - board_setScoreboardLoc( globals->cGlobals.game.board, - timerLeft, - GTK_VERT_SCORE_TOP, - GTK_VERT_SCORE_WIDTH, - vscale*nCols, - XP_FALSE ); - - } else { - timerLeft = GTK_BOARD_LEFT + (hscale*nCols) - - GTK_TIMER_WIDTH - netStatWidth; - board_setScoreboardLoc( globals->cGlobals.game.board, - GTK_BOARD_LEFT, GTK_HOR_SCORE_TOP, - timerLeft-GTK_BOARD_LEFT, - GTK_HOR_SCORE_HEIGHT, - XP_TRUE ); - - } - - /* Still pending: do this for the vertical score case */ - if ( globals->cGlobals.game.comms ) { - globals->netStatLeft = timerLeft + GTK_TIMER_WIDTH; - globals->netStatTop = 0; - } - - board_setTimerLoc( globals->cGlobals.game.board, timerLeft, timerTop, - GTK_TIMER_WIDTH, GTK_HOR_SCORE_HEIGHT ); - - board_setTrayLoc( globals->cGlobals.game.board, GTK_TRAY_LEFT, trayTop, - hscale * nCols, vscale * GTK_TRAY_HT_ROWS + 10, - GTK_DIVIDER_WIDTH ); - - setCtrlsForTray( globals ); - - board_invalAll( globals->cGlobals.game.board ); - - XP_Bool inOut[2]; - board_zoom( globals->cGlobals.game.board, 0, inOut ); - setZoomButtons( globals, inOut ); - - return TRUE; -} /* configure_event */ - -/* Redraw the screen from the backing pixmap */ -static gint -expose_event( GtkWidget* XP_UNUSED(widget), - GdkEventExpose* XP_UNUSED(event), - GtkAppGlobals* globals ) -{ - /* - gdk_draw_rectangle( widget->window,//((GtkDrawCtx*)globals->draw)->pixmap, - widget->style->white_gc, - TRUE, - 0, 0, - widget->allocation.width, - widget->allocation.height+widget->allocation.y ); - */ - /* I want to inval only the area that's exposed, but the rect is always - empty, even when clearly shouldn't be. Need to investigate. Until - fixed, use board_invalAll to ensure board is drawn.*/ -/* board_invalRect( globals->cGlobals.game.board, (XP_Rect*)&event->area ); */ - - board_invalAll( globals->cGlobals.game.board ); - board_draw( globals->cGlobals.game.board ); - draw_gtk_status( globals->draw, globals->stateChar ); - -/* gdk_draw_pixmap( widget->window, */ -/* widget->style->fg_gc[GTK_WIDGET_STATE (widget)], */ -/* ((GtkDrawCtx*)globals->draw)->pixmap, */ -/* event->area.x, event->area.y, */ -/* event->area.x, event->area.y, */ -/* event->area.width, event->area.height ); */ - - return FALSE; -} /* expose_event */ - -#if 0 -static gint -handle_client_event( GtkWidget *widget, GdkEventClient *event, - GtkAppGlobals* globals ) -{ - XP_LOGF( "handle_client_event called: event->type = " ); - if ( event->type == GDK_CLIENT_EVENT ) { - XP_LOGF( "GDK_CLIENT_EVENT" ); - return 1; - } else { - XP_LOGF( "%d", event->type ); - return 0; - } -} /* handle_client_event */ -#endif - -static void -quit( void ) -{ - gtk_main_quit(); -} - -static void -cleanup( GtkAppGlobals* globals ) -{ - saveGame( &globals->cGlobals ); - - game_dispose( &globals->cGlobals.game ); /* takes care of the dict */ - gi_disposePlayerInfo( MEMPOOL &globals->cGlobals.params->gi ); - -#ifdef XWFEATURE_BLUETOOTH - linux_bt_close( &globals->cGlobals ); -#endif -#ifdef XWFEATURE_SMS - linux_sms_close( &globals->cGlobals ); -#endif -#ifdef XWFEATURE_IP_DIRECT - linux_udp_close( &globals->cGlobals ); -#endif -#ifdef XWFEATURE_RELAY - linux_close_socket( &globals->cGlobals ); -#endif -} /* cleanup */ - -GtkWidget* -makeAddSubmenu( GtkWidget* menubar, gchar* label ) -{ - GtkWidget* submenu; - GtkWidget* item; - - item = gtk_menu_item_new_with_label( label ); - gtk_menu_bar_append( GTK_MENU_BAR(menubar), item ); - - submenu = gtk_menu_new(); - gtk_menu_item_set_submenu( GTK_MENU_ITEM(item), submenu ); - - gtk_widget_show(item); - - return submenu; -} /* makeAddSubmenu */ - -static void -tile_values( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - if ( !!globals->cGlobals.game.server ) { - XWStreamCtxt* stream = - mem_stream_make( MEMPOOL - globals->cGlobals.params->vtMgr, - globals, - CHANNEL_NONE, - catOnClose ); - server_formatDictCounts( globals->cGlobals.game.server, stream, 5 ); - stream_putU8( stream, '\n' ); - stream_destroy( stream ); - } - -} /* tile_values */ - -static void -game_history( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - catGameHistory( &globals->cGlobals ); -} /* game_history */ - -#ifdef TEXT_MODEL -static void -dump_board( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - if ( !!globals->cGlobals.game.model ) { - XWStreamCtxt* stream = - mem_stream_make( MEMPOOL - globals->cGlobals.params->vtMgr, - globals, - CHANNEL_NONE, - catOnClose ); - model_writeToTextStream( globals->cGlobals.game.model, stream ); - stream_destroy( stream ); - } -} -#endif - -static void -final_scores( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - XP_Bool gameOver = server_getGameIsOver( globals->cGlobals.game.server ); - - if ( gameOver ) { - catFinalScores( &globals->cGlobals, -1 ); - } else { - if ( gtkask( globals->window, - "Are you sure you want to resign?", - GTK_BUTTONS_YES_NO ) ) { - globals->cGlobals.manualFinal = XP_TRUE; - server_endGame( globals->cGlobals.game.server ); - gameOver = TRUE; - } - } - - /* the end game listener will take care of printing the final scores */ -} /* final_scores */ - -static void -new_game_impl( GtkAppGlobals* globals, XP_Bool fireConnDlg ) -{ - CommsAddrRec addr; - - if ( !!globals->cGlobals.game.comms ) { - comms_getAddr( globals->cGlobals.game.comms, &addr ); - } else { - comms_getInitialAddr( &addr, RELAY_NAME_DEFAULT, RELAY_PORT_DEFAULT ); - } - - if ( newGameDialog( globals, &addr, XP_TRUE, fireConnDlg ) ) { - CurGameInfo* gi = &globals->cGlobals.params->gi; -#ifndef XWFEATURE_STANDALONE_ONLY - XP_Bool isClient = gi->serverRole == SERVER_ISCLIENT; -#endif - TransportProcs procs = { - .closure = globals, - .send = LINUX_SEND, -#ifdef COMMS_HEARTBEAT - .reset = linux_reset, -#endif - }; - - game_reset( MEMPOOL &globals->cGlobals.game, gi, - globals->cGlobals.params->util, - &globals->cGlobals.cp, &procs ); - -#ifndef XWFEATURE_STANDALONE_ONLY - if ( !!globals->cGlobals.game.comms ) { - comms_setAddr( globals->cGlobals.game.comms, &addr ); - } else if ( gi->serverRole != SERVER_STANDALONE ) { - XP_ASSERT(0); - } - - if ( isClient ) { - XWStreamCtxt* stream = - mem_stream_make( MEMPOOL globals->cGlobals.params->vtMgr, - &globals->cGlobals, CHANNEL_NONE, - sendOnClose ); - server_initClientConnection( globals->cGlobals.game.server, - stream ); - } -#endif - (void)server_do( globals->cGlobals.game.server ); /* assign tiles, etc. */ - board_invalAll( globals->cGlobals.game.board ); - board_draw( globals->cGlobals.game.board ); - } - -} /* new_game_impl */ - -static void -new_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - new_game_impl( globals, XP_FALSE ); -} /* new_game */ - -static void -game_info( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - CommsAddrRec addr; - comms_getAddr( globals->cGlobals.game.comms, &addr ); - - /* Anything to do if OK is clicked? Changed names etc. already saved. Try - server_do in case one's become a robot. */ - if ( newGameDialog( globals, &addr, XP_FALSE, XP_FALSE ) ) { - if ( server_do( globals->cGlobals.game.server ) ) { - board_draw( globals->cGlobals.game.board ); - } - } -} - -static void -load_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) -{ -} /* load_game */ - -static void -save_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) -{ -} /* save_game */ - -#ifdef XWFEATURE_CHANGEDICT -static void -change_dictionary( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - LaunchParams* params = globals->cGlobals.params; - GSList* dicts = listDicts( params ); - gchar buf[265]; - gchar* name = gtkaskdict( dicts, buf, VSIZE(buf) ); - if ( !!name ) { - DictionaryCtxt* dict = - linux_dictionary_make( MPPARM(params->util->mpool) params, name, - params->useMmap ); - game_changeDict( MPPARM(params->util->mpool) &globals->cGlobals.game, - ¶ms->gi, dict ); - } - g_slist_free( dicts ); -} /* change_dictionary */ -#endif - -static void -handle_undo( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) -{ -} /* handle_undo */ - -static void -handle_redo( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) -{ -} /* handle_redo */ - -#ifdef FEATURE_TRAY_EDIT -static void -handle_trayEditToggle( GtkWidget* XP_UNUSED(widget), - GtkAppGlobals* XP_UNUSED(globals), - XP_Bool XP_UNUSED(on) ) -{ -} /* handle_trayEditToggle */ - -static void -handle_trayEditToggle_on( GtkWidget* widget, GtkAppGlobals* globals ) -{ - handle_trayEditToggle( widget, globals, XP_TRUE ); -} - -static void -handle_trayEditToggle_off( GtkWidget* widget, GtkAppGlobals* globals ) -{ - handle_trayEditToggle( widget, globals, XP_FALSE ); -} -#endif - -static void -handle_trade_cancel( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - BoardCtxt* board = globals->cGlobals.game.board; - if ( board_endTrade( board ) ) { - board_draw( board ); - } -} - -#ifndef XWFEATURE_STANDALONE_ONLY -static void -handle_resend( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - CommsCtxt* comms = globals->cGlobals.game.comms; - if ( comms != NULL ) { - comms_resendAll( comms, XP_TRUE ); - } -} /* handle_resend */ - -#ifdef XWFEATURE_COMMSACK -static void -handle_ack( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - CommsCtxt* comms = globals->cGlobals.game.comms; - if ( comms != NULL ) { - comms_ackAny( comms ); - } -} -#endif - -#ifdef DEBUG -static void -handle_commstats( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - CommsCtxt* comms = globals->cGlobals.game.comms; - - if ( !!comms ) { - XWStreamCtxt* stream = - mem_stream_make( MEMPOOL - globals->cGlobals.params->vtMgr, - globals, - CHANNEL_NONE, catOnClose ); - comms_getStats( comms, stream ); - stream_destroy( stream ); - } -} /* handle_commstats */ -#endif -#endif - -#ifdef MEM_DEBUG -static void -handle_memstats( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - XWStreamCtxt* stream = mem_stream_make( MEMPOOL - globals->cGlobals.params->vtMgr, - globals, - CHANNEL_NONE, catOnClose ); - mpool_stats( MEMPOOL stream ); - stream_destroy( stream ); - -} /* handle_memstats */ -#endif - -static GtkWidget* -createAddItem( GtkWidget* parent, gchar* label, - GtkSignalFunc handlerFunc, GtkAppGlobals* globals ) -{ - GtkWidget* item = gtk_menu_item_new_with_label( label ); - -/* g_print( "createAddItem called with label %s\n", label ); */ - - if ( handlerFunc != NULL ) { - g_signal_connect( GTK_OBJECT(item), "activate", - G_CALLBACK(handlerFunc), globals ); - } - - gtk_menu_append( GTK_MENU(parent), item ); - gtk_widget_show( item ); - - return item; -} /* createAddItem */ - static GtkWidget* -makeMenus( GtkAppGlobals* globals, int XP_UNUSED(argc), - char** XP_UNUSED(argv) ) +makeGamesWindow( GTKGamesGlobals* gg ) { - GtkWidget* menubar = gtk_menu_bar_new(); - GtkWidget* fileMenu; + GtkWidget* window; - fileMenu = makeAddSubmenu( menubar, "File" ); - (void)createAddItem( fileMenu, "Tile values", - GTK_SIGNAL_FUNC(tile_values), globals ); - (void)createAddItem( fileMenu, "Game history", - GTK_SIGNAL_FUNC(game_history), globals ); -#ifdef TEXT_MODEL - (void)createAddItem( fileMenu, "Dump board", - GTK_SIGNAL_FUNC(dump_board), globals ); -#endif - - (void)createAddItem( fileMenu, "Final scores", - GTK_SIGNAL_FUNC(final_scores), globals ); - - (void)createAddItem( fileMenu, "New game", - GTK_SIGNAL_FUNC(new_game), globals ); - (void)createAddItem( fileMenu, "Game info", - GTK_SIGNAL_FUNC(game_info), globals ); - - (void)createAddItem( fileMenu, "Load game", - GTK_SIGNAL_FUNC(load_game), globals ); - (void)createAddItem( fileMenu, "Save game", - GTK_SIGNAL_FUNC(save_game), globals ); -#ifdef XWFEATURE_CHANGEDICT - (void)createAddItem( fileMenu, "Change dictionary", - GTK_SIGNAL_FUNC(change_dictionary), globals ); -#endif - (void)createAddItem( fileMenu, "Cancel trade", - GTK_SIGNAL_FUNC(handle_trade_cancel), globals ); - - fileMenu = makeAddSubmenu( menubar, "Edit" ); - - (void)createAddItem( fileMenu, "Undo", - GTK_SIGNAL_FUNC(handle_undo), globals ); - (void)createAddItem( fileMenu, "Redo", - GTK_SIGNAL_FUNC(handle_redo), globals ); - -#ifdef FEATURE_TRAY_EDIT - (void)createAddItem( fileMenu, "Allow tray edit", - GTK_SIGNAL_FUNC(handle_trayEditToggle_on), globals ); - (void)createAddItem( fileMenu, "Dis-allow tray edit", - GTK_SIGNAL_FUNC(handle_trayEditToggle_off), globals ); -#endif - fileMenu = makeAddSubmenu( menubar, "Network" ); - -#ifndef XWFEATURE_STANDALONE_ONLY - (void)createAddItem( fileMenu, "Resend", - GTK_SIGNAL_FUNC(handle_resend), globals ); -#ifdef XWFEATURE_COMMSACK - (void)createAddItem( fileMenu, "ack any", - GTK_SIGNAL_FUNC(handle_ack), globals ); -#endif -# ifdef DEBUG - (void)createAddItem( fileMenu, "Stats", - GTK_SIGNAL_FUNC(handle_commstats), globals ); -# endif -#endif -#ifdef MEM_DEBUG - (void)createAddItem( fileMenu, "Mem stats", - GTK_SIGNAL_FUNC(handle_memstats), globals ); -#endif - - /* (void)createAddItem( fileMenu, "Print board", */ - /* GTK_SIGNAL_FUNC(handle_print_board), globals ); */ - - /* listAllGames( menubar, argc, argv, globals ); */ - - gtk_widget_show( menubar ); - - return menubar; -} /* makeMenus */ - -static void -disenable_buttons( GtkAppGlobals* globals ) -{ - XP_Bool canFlip = 1 < board_visTileCount( globals->cGlobals.game.board ); - gtk_widget_set_sensitive( globals->flip_button, canFlip ); - - XP_Bool canToggle = board_canTogglePending( globals->cGlobals.game.board ); - gtk_widget_set_sensitive( globals->toggle_undo_button, canToggle ); - - XP_Bool canHing = board_canHint( globals->cGlobals.game.board ); - gtk_widget_set_sensitive( globals->prevhint_button, canHing ); - gtk_widget_set_sensitive( globals->nexthint_button, canHing ); - -#ifdef XWFEATURE_CHAT - XP_Bool canChat = !!globals->cGlobals.game.comms - && comms_canChat( globals->cGlobals.game.comms ); - gtk_widget_set_sensitive( globals->chat_button, canChat ); -#endif -} - -static gboolean -handle_flip_button( GtkWidget* XP_UNUSED(widget), gpointer _globals ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)_globals; - if ( board_flip( globals->cGlobals.game.board ) ) { - board_draw( globals->cGlobals.game.board ); - } - return TRUE; -} /* handle_flip_button */ - -static gboolean -handle_value_button( GtkWidget* XP_UNUSED(widget), gpointer closure ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)closure; - if ( board_toggle_showValues( globals->cGlobals.game.board ) ) { - board_draw( globals->cGlobals.game.board ); - } - return TRUE; -} /* handle_value_button */ - -static void -handle_hint_button( GtkAppGlobals* globals, XP_Bool prev ) -{ - XP_Bool redo; - if ( board_requestHint( globals->cGlobals.game.board, -#ifdef XWFEATURE_SEARCHLIMIT - XP_FALSE, -#endif - prev, &redo ) ) { - board_draw( globals->cGlobals.game.board ); - disenable_buttons( globals ); - } -} /* handle_hint_button */ - -static void -handle_prevhint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - handle_hint_button( globals, XP_TRUE ); -} /* handle_prevhint_button */ - -static void -handle_nexthint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - handle_hint_button( globals, XP_FALSE ); -} /* handle_nexthint_button */ - -static void -handle_nhint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - XP_Bool redo; - - board_resetEngine( globals->cGlobals.game.board ); - if ( board_requestHint( globals->cGlobals.game.board, -#ifdef XWFEATURE_SEARCHLIMIT - XP_TRUE, -#endif - XP_FALSE, &redo ) ) { - board_draw( globals->cGlobals.game.board ); - } -} /* handle_nhint_button */ - -static void -handle_colors_button( GtkWidget* XP_UNUSED(widget), - GtkAppGlobals* XP_UNUSED(globals) ) -{ -/* XP_Bool oldVal = board_getShowColors( globals->cGlobals.game.board ); */ -/* if ( board_setShowColors( globals->cGlobals.game.board, !oldVal ) ) { */ -/* board_draw( globals->cGlobals.game.board ); */ -/* } */ -} /* handle_colors_button */ - -static void -handle_juggle_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - if ( board_juggleTray( globals->cGlobals.game.board ) ) { - board_draw( globals->cGlobals.game.board ); - } -} /* handle_juggle_button */ - -static void -handle_undo_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - if ( server_handleUndo( globals->cGlobals.game.server, 0 ) ) { - board_draw( globals->cGlobals.game.board ); - } -} /* handle_undo_button */ - -static void -handle_redo_button( GtkWidget* XP_UNUSED(widget), - GtkAppGlobals* XP_UNUSED(globals) ) -{ -} /* handle_redo_button */ - -static void -handle_toggle_undo( GtkWidget* XP_UNUSED(widget), - GtkAppGlobals* globals ) -{ - BoardCtxt* board = globals->cGlobals.game.board; - if ( board_redoReplacedTiles( board ) || board_replaceTiles( board ) ) { - board_draw( board ); - } -} - -static void -handle_trade_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - if ( board_beginTrade( globals->cGlobals.game.board ) ) { - board_draw( globals->cGlobals.game.board ); - disenable_buttons( globals ); - } -} /* handle_juggle_button */ - -static void -handle_done_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - if ( board_commitTurn( globals->cGlobals.game.board ) ) { - board_draw( globals->cGlobals.game.board ); - disenable_buttons( globals ); - } -} /* handle_done_button */ - -static void -setZoomButtons( GtkAppGlobals* globals, XP_Bool* inOut ) -{ - gtk_widget_set_sensitive( globals->zoomin_button, inOut[0] ); - gtk_widget_set_sensitive( globals->zoomout_button, inOut[1] ); -} - -static void -handle_zoomin_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - XP_Bool inOut[2]; - if ( board_zoom( globals->cGlobals.game.board, 1, inOut ) ) { - board_draw( globals->cGlobals.game.board ); - setZoomButtons( globals, inOut ); - } -} /* handle_done_button */ - -static void -handle_zoomout_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - XP_Bool inOut[2]; - if ( board_zoom( globals->cGlobals.game.board, -1, inOut ) ) { - board_draw( globals->cGlobals.game.board ); - setZoomButtons( globals, inOut ); - } -} /* handle_done_button */ - -#ifdef XWFEATURE_CHAT -static void -handle_chat_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - gchar* msg = gtkGetChatMessage( globals ); - if ( NULL != msg ) { - server_sendChat( globals->cGlobals.game.server, msg ); - g_free( msg ); - } -} -#endif - -static void -scroll_value_changed( GtkAdjustment *adj, GtkAppGlobals* globals ) -{ - XP_U16 newValue; - gfloat newValueF = adj->value; - - /* XP_ASSERT( newValueF >= 0.0 */ - /* && newValueF <= globals->cGlobals.params->nHidden ); */ - newValue = (XP_U16)newValueF; - - if ( board_setYOffset( globals->cGlobals.game.board, newValue ) ) { - board_draw( globals->cGlobals.game.board ); - } -} /* scroll_value_changed */ - -static void -handle_grid_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - globals->gridOn = !globals->gridOn; - - board_invalAll( globals->cGlobals.game.board ); - board_draw( globals->cGlobals.game.board ); -} /* handle_grid_button */ - -static void -handle_hide_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - BoardCtxt* board; - XP_Bool draw = XP_FALSE; - - if ( globals->cGlobals.params->nHidden > 0 ) { - gint nRows = globals->cGlobals.params->gi.boardSize; - globals->adjustment->page_size = nRows; - globals->adjustment->value = 0.0; - - gtk_signal_emit_by_name( GTK_OBJECT(globals->adjustment), "changed" ); - gtk_adjustment_value_changed( GTK_ADJUSTMENT(globals->adjustment) ); - } - - board = globals->cGlobals.game.board; - if ( TRAY_REVEALED == board_getTrayVisState( board ) ) { - draw = board_hideTray( board ); - } else { - draw = board_showTray( board ); - } - if ( draw ) { - board_draw( board ); - } -} /* handle_hide_button */ - -static void -handle_commit_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - if ( board_commitTurn( globals->cGlobals.game.board ) ) { - board_draw( globals->cGlobals.game.board ); - } -} /* handle_commit_button */ - -static void -gtkUserError( GtkAppGlobals* globals, const char* format, ... ) -{ - char buf[512]; - va_list ap; - - va_start( ap, format ); - - vsprintf( buf, format, ap ); - - (void)gtkask( globals->window, buf, GTK_BUTTONS_OK ); - - va_end(ap); -} /* gtkUserError */ - -static VTableMgr* -gtk_util_getVTManager(XW_UtilCtxt* uc) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - return globals->cGlobals.params->vtMgr; -} /* linux_util_getVTManager */ - -static XP_S16 -gtk_util_userPickTileBlank( XW_UtilCtxt* uc, XP_U16 playerNum, - const XP_UCHAR** texts, XP_U16 nTiles ) -{ - XP_S16 chosen; - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - XP_UCHAR* name = globals->cGlobals.params->gi.players[playerNum].name; - - chosen = gtkletterask( NULL, XP_FALSE, name, nTiles, texts ); - return chosen; -} - -static XP_S16 -gtk_util_userPickTileTray( XW_UtilCtxt* uc, const PickInfo* pi, - XP_U16 playerNum, const XP_UCHAR** texts, - XP_U16 nTiles ) -{ - XP_S16 chosen; - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - XP_UCHAR* name = globals->cGlobals.params->gi.players[playerNum].name; - - chosen = gtkletterask( pi, XP_TRUE, name, nTiles, texts ); - return chosen; -} /* gtk_util_userPickTile */ - -static XP_Bool -gtk_util_askPassword( XW_UtilCtxt* XP_UNUSED(uc), const XP_UCHAR* name, - XP_UCHAR* buf, XP_U16* len ) -{ - XP_Bool ok = gtkpasswdask( name, buf, len ); - return ok; -} /* gtk_util_askPassword */ - -static void -setCtrlsForTray( GtkAppGlobals* XP_UNUSED(globals) ) -{ -#if 0 - XW_TrayVisState state = - board_getTrayVisState( globals->cGlobals.game.board ); - XP_S16 nHidden = globals->cGlobals.params->nHidden; - - if ( nHidden != 0 ) { - XP_U16 pageSize = nRows; - - if ( state == TRAY_HIDDEN ) { /* we recover what tray covers */ - nHidden -= GTK_TRAY_HT_ROWS; - } - if ( nHidden > 0 ) { - pageSize -= nHidden; - } - globals->adjustment->page_size = pageSize; - - globals->adjustment->value = - board_getYOffset( globals->cGlobals.game.board ); - gtk_signal_emit_by_name( GTK_OBJECT(globals->adjustment), "changed" ); - } -#endif -} /* setCtrlsForTray */ - -static void -gtk_util_trayHiddenChange( XW_UtilCtxt* uc, XW_TrayVisState XP_UNUSED(state), - XP_U16 XP_UNUSED(nVisibleRows) ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - setCtrlsForTray( globals ); -} /* gtk_util_trayHiddenChange */ - -static void -gtk_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 maxOffset, - XP_U16 XP_UNUSED(oldOffset), - XP_U16 newOffset ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - if ( !!globals->adjustment ) { - gint nRows = globals->cGlobals.params->gi.boardSize; - globals->adjustment->page_size = nRows - maxOffset; - globals->adjustment->value = newOffset; - gtk_adjustment_value_changed( GTK_ADJUSTMENT(globals->adjustment) ); - } -} /* gtk_util_yOffsetChange */ - -static void -gtkShowFinalScores( const GtkAppGlobals* globals ) -{ - XWStreamCtxt* stream; - XP_UCHAR* text; - const CommonGlobals* cGlobals = &globals->cGlobals; - - stream = mem_stream_make( MPPARM(cGlobals->params->util->mpool) - cGlobals->params->vtMgr, - NULL, CHANNEL_NONE, NULL ); - server_writeFinalScores( cGlobals->game.server, stream ); - - text = strFromStream( stream ); - stream_destroy( stream ); - - XP_U16 timeout = cGlobals->manualFinal? 0 : 500; - (void)gtkask_timeout( globals->window, text, GTK_BUTTONS_OK, timeout ); - - free( text ); -} /* gtkShowFinalScores */ - -static void -gtk_util_informMove( XW_UtilCtxt* uc, XWStreamCtxt* expl, - XWStreamCtxt* words ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - char* question = strFromStream( !!words? words : expl ); - (void)gtkask( globals->window, question, GTK_BUTTONS_OK ); - free( question ); -} - -static void -gtk_util_informUndo( XW_UtilCtxt* uc ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - (void)gtkask_timeout( globals->window, "Remote player undid a move", - GTK_BUTTONS_OK, 500 ); -} - -static void -gtk_util_notifyGameOver( XW_UtilCtxt* uc, XP_S16 quitter ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - CommonGlobals* cGlobals = &globals->cGlobals; - - if ( cGlobals->params->printHistory ) { - catGameHistory( cGlobals ); - } - - catFinalScores( cGlobals, quitter ); - - if ( cGlobals->params->quitAfter >= 0 ) { - sleep( cGlobals->params->quitAfter ); - quit(); - } else if ( cGlobals->params->undoWhenDone ) { - server_handleUndo( cGlobals->game.server, 0 ); - board_draw( cGlobals->game.board ); - } else if ( !cGlobals->params->skipGameOver ) { - gtkShowFinalScores( globals ); - } -} /* gtk_util_notifyGameOver */ - -static void -gtk_util_informNetDict( XW_UtilCtxt* uc, XP_LangCode XP_UNUSED(lang), - const XP_UCHAR* oldName, - const XP_UCHAR* newName, const XP_UCHAR* newSum, - XWPhoniesChoice phoniesAction ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - gchar buf[512]; - - int offset = snprintf( buf, VSIZE(buf), - "dict changing from %s to %s (sum=%s).", - oldName, newName, newSum ); - if ( PHONIES_DISALLOW == phoniesAction ) { - snprintf( &buf[offset], VSIZE(buf)-offset, "%s", - "\nPHONIES_DISALLOW is set so this may lead to some surprises." ); - } - (void)gtkask( globals->window, buf, GTK_BUTTONS_OK ); -} - -/* define this to prevent user events during debugging from stopping the engine */ -/* #define DONT_ABORT_ENGINE */ - -#ifdef XWFEATURE_HILITECELL -static XP_Bool -gtk_util_hiliteCell( XW_UtilCtxt* uc, XP_U16 col, XP_U16 row ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; -#ifndef DONT_ABORT_ENGINE - gboolean pending; -#endif - - board_hiliteCellAt( globals->cGlobals.game.board, col, row ); - if ( globals->cGlobals.params->sleepOnAnchor ) { - usleep( 10000 ); - } - -#ifdef DONT_ABORT_ENGINE - return XP_TRUE; /* keep going */ -#else - pending = gdk_events_pending(); - if ( pending ) { - XP_DEBUGF( "gtk_util_hiliteCell=>%d", pending ); - } - return !pending; -#endif -} /* gtk_util_hiliteCell */ -#endif - -static XP_Bool -gtk_util_altKeyDown( XW_UtilCtxt* uc ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - return globals->altKeyDown; -} - -static XP_Bool -gtk_util_engineProgressCallback( XW_UtilCtxt* XP_UNUSED(uc) ) -{ -#ifdef DONT_ABORT_ENGINE - return XP_TRUE; /* keep going */ -#else - gboolean pending = gdk_events_pending(); - -/* XP_DEBUGF( "gdk_events_pending returned %d\n", pending ); */ - - return !pending; -#endif -} /* gtk_util_engineProgressCallback */ - -static void -cancelTimer( GtkAppGlobals* globals, XWTimerReason why ) -{ - guint src = globals->timerSources[why-1]; - if ( src != 0 ) { - g_source_remove( src ); - globals->timerSources[why-1] = 0; - } -} /* cancelTimer */ - -static gint -pen_timer_func( gpointer data ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)data; - - if ( linuxFireTimer( &globals->cGlobals, TIMER_PENDOWN ) ) { - board_draw( globals->cGlobals.game.board ); - } - - return XP_FALSE; -} /* pentimer_idle_func */ - -static gint -score_timer_func( gpointer data ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)data; - - if ( linuxFireTimer( &globals->cGlobals, TIMER_TIMERTICK ) ) { - board_draw( globals->cGlobals.game.board ); - } - - return XP_FALSE; -} /* score_timer_func */ - -#ifndef XWFEATURE_STANDALONE_ONLY -static gint -comms_timer_func( gpointer data ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)data; - - if ( linuxFireTimer( &globals->cGlobals, TIMER_COMMS ) ) { - board_draw( globals->cGlobals.game.board ); - } - - return (gint)0; -} -#endif - -#ifdef XWFEATURE_SLOW_ROBOT -static gint -slowrob_timer_func( gpointer data ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)data; - - if ( linuxFireTimer( &globals->cGlobals, TIMER_SLOWROBOT ) ) { - board_draw( globals->cGlobals.game.board ); - } - - return (gint)0; -} -#endif - -static void -gtk_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, - XP_U16 XP_UNUSED_STANDALONE(when), - XWTimerProc proc, void* closure ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - guint newSrc; - - cancelTimer( globals, why ); - - if ( why == TIMER_PENDOWN ) { - if ( 0 != globals->timerSources[why-1] ) { - g_source_remove( globals->timerSources[why-1] ); - } - newSrc = g_timeout_add( 1000, pen_timer_func, globals ); - } else if ( why == TIMER_TIMERTICK ) { - /* one second */ - globals->scoreTimerInterval = 100 * 10000; - - (void)gettimeofday( &globals->scoreTv, NULL ); - - newSrc = g_timeout_add( 1000, score_timer_func, globals ); -#ifndef XWFEATURE_STANDALONE_ONLY - } else if ( why == TIMER_COMMS ) { - newSrc = g_timeout_add( 1000 * when, comms_timer_func, globals ); -#endif -#ifdef XWFEATURE_SLOW_ROBOT - } else if ( why == TIMER_SLOWROBOT ) { - newSrc = g_timeout_add( 1000 * when, slowrob_timer_func, globals ); -#endif - } else { - XP_ASSERT( 0 ); - } - - globals->cGlobals.timerInfo[why].proc = proc; - globals->cGlobals.timerInfo[why].closure = closure; - XP_ASSERT( newSrc != 0 ); - globals->timerSources[why-1] = newSrc; -} /* gtk_util_setTimer */ - -static void -gtk_util_clearTimer( XW_UtilCtxt* uc, XWTimerReason why ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - globals->cGlobals.timerInfo[why].proc = NULL; -} - -static gint -idle_func( gpointer data ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)data; -/* XP_DEBUGF( "idle_func called\n" ); */ - - /* remove before calling server_do. If server_do puts up a dialog that - calls gtk_main, then this idle proc will also apply to that event loop - and bad things can happen. So kill the idle proc asap. */ - gtk_idle_remove( globals->idleID ); - - if ( server_do( globals->cGlobals.game.server ) ) { - if ( !!globals->cGlobals.game.board ) { - board_draw( globals->cGlobals.game.board ); - } - } - return 0; /* 0 will stop it from being called again */ -} /* idle_func */ - -static void -gtk_util_requestTime( XW_UtilCtxt* uc ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - globals->idleID = gtk_idle_add( idle_func, globals ); -} /* gtk_util_requestTime */ - -static XP_Bool -gtk_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 player, - XP_Bool turnLost ) -{ - XP_Bool result; - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - char buf[300]; - - if ( turnLost ) { - char wordsBuf[256]; - XP_U16 i; - XP_UCHAR* name = globals->cGlobals.params->gi.players[player].name; - XP_ASSERT( !!name ); - - for ( i = 0, wordsBuf[0] = '\0'; ; ) { - char wordBuf[18]; - sprintf( wordBuf, "\"%s\"", bwi->words[i] ); - strcat( wordsBuf, wordBuf ); - if ( ++i == bwi->nWords ) { - break; - } - strcat( wordsBuf, ", " ); - } - - sprintf( buf, "Player %d (%s) played illegal word[s] %s; loses turn.", - player+1, name, wordsBuf ); - - if ( globals->cGlobals.params->skipWarnings ) { - XP_LOGF( "%s", buf ); - } else { - gtkUserError( globals, buf ); - } - result = XP_TRUE; - } else { - XP_ASSERT( bwi->nWords == 1 ); - sprintf( buf, "Word \"%s\" not in the current dictionary (%s). " - "Use it anyway?", bwi->words[0], bwi->dictName ); - result = gtkask( globals->window, buf, GTK_BUTTONS_YES_NO ); - } - - return result; -} /* gtk_util_warnIllegalWord */ - -static void -gtk_util_remSelected( XW_UtilCtxt* uc ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - XWStreamCtxt* stream; - XP_UCHAR* text; - - stream = mem_stream_make( MEMPOOL - globals->cGlobals.params->vtMgr, - globals, CHANNEL_NONE, NULL ); - board_formatRemainingTiles( globals->cGlobals.game.board, stream ); - text = strFromStream( stream ); - stream_destroy( stream ); - - (void)gtkask( globals->window, text, GTK_BUTTONS_OK ); - free( text ); -} - -#ifndef XWFEATURE_STANDALONE_ONLY -static XWStreamCtxt* -gtk_util_makeStreamFromAddr(XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - - XWStreamCtxt* stream = mem_stream_make( MEMPOOL - globals->cGlobals.params->vtMgr, - &globals->cGlobals, channelNo, - sendOnClose ); - return stream; -} /* gtk_util_makeStreamFromAddr */ - -#ifdef XWFEATURE_CHAT -static void -gtk_util_showChat( XW_UtilCtxt* uc, const XP_UCHAR* const msg ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - (void)gtkask( globals->window, msg, GTK_BUTTONS_OK ); -} -#endif -#endif - -#ifdef XWFEATURE_SEARCHLIMIT -static XP_Bool -gtk_util_getTraySearchLimits( XW_UtilCtxt* XP_UNUSED(uc), - XP_U16* XP_UNUSED(min), XP_U16* max ) -{ - *max = askNTiles( MAX_TRAY_TILES, *max ); - return XP_TRUE; -} -#endif - -#ifndef XWFEATURE_MINIWIN -static void -gtk_util_bonusSquareHeld( XW_UtilCtxt* uc, XWBonusType bonus ) -{ - LOG_FUNC(); - XP_USE( uc ); - XP_USE( bonus ); -} - -static void -gtk_util_playerScoreHeld( XW_UtilCtxt* uc, XP_U16 player ) -{ - LOG_FUNC(); - - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - - XP_UCHAR scoreExpl[48] = {0}; - XP_U16 explLen = sizeof(scoreExpl); - - if ( model_getPlayersLastScore( globals->cGlobals.game.model, - player, scoreExpl, &explLen ) ) { - XP_LOGF( "got: %s", scoreExpl ); - } -} -#endif - -#ifdef XWFEATURE_BOARDWORDS -static void -gtk_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words ) -{ - XP_USE( uc ); - catOnClose( words, NULL ); - fprintf( stderr, "\n" ); -} -#endif - -static void -gtk_util_userError( XW_UtilCtxt* uc, UtilErrID id ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - XP_Bool silent; - const XP_UCHAR* message = linux_getErrString( id, &silent ); - - XP_LOGF( "%s(%d)", __func__, id ); - - if ( silent ) { - XP_LOGF( "%s", message ); - } else { - gtkUserError( globals, message ); - } -} /* gtk_util_userError */ - -static XP_Bool -gtk_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id, - XWStreamCtxt* stream ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - XP_Bool result; - char* question; - XP_Bool freeMe = XP_FALSE; - GtkButtonsType buttons = GTK_BUTTONS_YES_NO; - - switch( id ) { - - case QUERY_COMMIT_TURN: - question = strFromStream( stream ); - freeMe = XP_TRUE; - break; - case QUERY_ROBOT_TRADE: - question = strFromStream( stream ); - freeMe = XP_TRUE; - buttons = GTK_BUTTONS_OK; - break; - - default: - XP_ASSERT( 0 ); - return XP_FALSE; - } - - result = gtkask( globals->window, question, buttons ); - - if ( freeMe ) { - free( question ); - } - - return result; -} /* gtk_util_userQuery */ - -static XP_Bool -gtk_util_confirmTrade( XW_UtilCtxt* uc, - const XP_UCHAR** tiles, XP_U16 nTiles ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - char question[256]; - formatConfirmTrade( tiles, nTiles, question, sizeof(question) ); - return gtkask( globals->window, question, GTK_BUTTONS_YES_NO ); -} - -static GtkWidget* -makeShowButtonFromBitmap( void* closure, const gchar* filename, - const gchar* alt, GCallback func ) -{ - GtkWidget* widget; - GtkWidget* button; - - if ( file_exists( filename ) ) { - widget = gtk_image_new_from_file( filename ); - } else { - widget = gtk_label_new( alt ); - } - gtk_widget_show( widget ); - - button = gtk_button_new(); - gtk_container_add (GTK_CONTAINER (button), widget ); - gtk_widget_show (button); - - if ( func != NULL ) { - g_signal_connect( GTK_OBJECT(button), "clicked", func, closure ); - } - - return button; -} /* makeShowButtonFromBitmap */ - -static GtkWidget* -makeVerticalBar( GtkAppGlobals* globals, GtkWidget* XP_UNUSED(window) ) -{ - GtkWidget* vbox; - GtkWidget* button; - - vbox = gtk_vbutton_box_new(); - - button = makeShowButtonFromBitmap( globals, "../flip.xpm", "f", - G_CALLBACK(handle_flip_button) ); - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - globals->flip_button = button; - - button = makeShowButtonFromBitmap( globals, "../value.xpm", "v", - G_CALLBACK(handle_value_button) ); - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - - button = makeShowButtonFromBitmap( globals, "../hint.xpm", "?-", - G_CALLBACK(handle_prevhint_button) ); - globals->prevhint_button = button; - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - button = makeShowButtonFromBitmap( globals, "../hint.xpm", "?+", - G_CALLBACK(handle_nexthint_button) ); - globals->nexthint_button = button; - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - - button = makeShowButtonFromBitmap( globals, "../hintNum.xpm", "n", - G_CALLBACK(handle_nhint_button) ); - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - - button = makeShowButtonFromBitmap( globals, "../colors.xpm", "c", - G_CALLBACK(handle_colors_button) ); - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - - /* undo and redo buttons */ - button = makeShowButtonFromBitmap( globals, "../undo.xpm", "U", - G_CALLBACK(handle_undo_button) ); - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - button = makeShowButtonFromBitmap( globals, "../redo.xpm", "R", - G_CALLBACK(handle_redo_button) ); - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - - button = makeShowButtonFromBitmap( globals, "", "u/r", - G_CALLBACK(handle_toggle_undo) ); - globals->toggle_undo_button = button; - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - - /* the four buttons that on palm are beside the tray */ - button = makeShowButtonFromBitmap( globals, "../juggle.xpm", "j", - G_CALLBACK(handle_juggle_button) ); - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - - button = makeShowButtonFromBitmap( globals, "../trade.xpm", "t", - G_CALLBACK(handle_trade_button) ); - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - button = makeShowButtonFromBitmap( globals, "../done.xpm", "d", - G_CALLBACK(handle_done_button) ); - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - button = makeShowButtonFromBitmap( globals, "../done.xpm", "+", - G_CALLBACK(handle_zoomin_button) ); - globals->zoomin_button = button; - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - button = makeShowButtonFromBitmap( globals, "../done.xpm", "-", - G_CALLBACK(handle_zoomout_button) ); - globals->zoomout_button = button; - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); -#ifdef XWFEATURE_CHAT - button = makeShowButtonFromBitmap( globals, "", "chat", - G_CALLBACK(handle_chat_button) ); - globals->chat_button = button; - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); -#endif + window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + GtkWidget* vbox = gtk_vbox_new( FALSE, 0 ); + gtk_container_add( GTK_CONTAINER(window), vbox ); gtk_widget_show( vbox ); - return vbox; -} /* makeVerticalBar */ + GtkWidget* list = gtk_tree_view_new(); + gtk_container_add( GTK_CONTAINER(vbox), list ); + init_games_list( list ); + GtkWidget* button = gtk_button_new_with_label( "New Game" ); + gtk_container_add( GTK_CONTAINER(vbox), button ); + g_signal_connect( GTK_OBJECT(button), "clicked", + G_CALLBACK(handle_newgame_button), gg ); + gtk_widget_show( button ); -static GtkWidget* -makeButtons( GtkAppGlobals* globals, int XP_UNUSED(argc), - char** XP_UNUSED(argv) ) -{ - short i; - GtkWidget* hbox; - GtkWidget* button; - - struct { - char* name; - GCallback func; - } buttons[] = { - /* { "Flip", handle_flip_button }, */ - { "Grid", G_CALLBACK(handle_grid_button) }, - { "Hide", G_CALLBACK(handle_hide_button) }, - { "Commit", G_CALLBACK(handle_commit_button) }, - }; - - hbox = gtk_hbox_new( FALSE, 0 ); - - for ( i = 0; i < sizeof(buttons)/sizeof(*buttons); ++i ) { - button = gtk_button_new_with_label( buttons[i].name ); - gtk_widget_show( button ); - g_signal_connect( GTK_OBJECT(button), "clicked", - G_CALLBACK(buttons[i].func), globals ); - - gtk_box_pack_start( GTK_BOX(hbox), button, FALSE, TRUE, 0); - } - - gtk_widget_show( hbox ); - return hbox; -} /* makeButtons */ - -static void -setupGtkUtilCallbacks( GtkAppGlobals* globals, XW_UtilCtxt* util ) -{ - util->vtable->m_util_userError = gtk_util_userError; - util->vtable->m_util_userQuery = gtk_util_userQuery; - util->vtable->m_util_confirmTrade = gtk_util_confirmTrade; - util->vtable->m_util_getVTManager = gtk_util_getVTManager; - util->vtable->m_util_userPickTileBlank = gtk_util_userPickTileBlank; - util->vtable->m_util_userPickTileTray = gtk_util_userPickTileTray; - util->vtable->m_util_askPassword = gtk_util_askPassword; - util->vtable->m_util_trayHiddenChange = gtk_util_trayHiddenChange; - util->vtable->m_util_yOffsetChange = gtk_util_yOffsetChange; - util->vtable->m_util_informMove = gtk_util_informMove; - util->vtable->m_util_informUndo = gtk_util_informUndo; - util->vtable->m_util_notifyGameOver = gtk_util_notifyGameOver; - util->vtable->m_util_informNetDict = gtk_util_informNetDict; -#ifdef XWFEATURE_HILITECELL - util->vtable->m_util_hiliteCell = gtk_util_hiliteCell; -#endif - util->vtable->m_util_altKeyDown = gtk_util_altKeyDown; - util->vtable->m_util_engineProgressCallback = - gtk_util_engineProgressCallback; - util->vtable->m_util_setTimer = gtk_util_setTimer; - util->vtable->m_util_clearTimer = gtk_util_clearTimer; - util->vtable->m_util_requestTime = gtk_util_requestTime; - util->vtable->m_util_warnIllegalWord = gtk_util_warnIllegalWord; - util->vtable->m_util_remSelected = gtk_util_remSelected; -#ifndef XWFEATURE_STANDALONE_ONLY - util->vtable->m_util_makeStreamFromAddr = gtk_util_makeStreamFromAddr; -#endif -#ifdef XWFEATURE_CHAT - util->vtable->m_util_showChat = gtk_util_showChat; -#endif -#ifdef XWFEATURE_SEARCHLIMIT - util->vtable->m_util_getTraySearchLimits = gtk_util_getTraySearchLimits; -#endif - -#ifndef XWFEATURE_MINIWIN - util->vtable->m_util_bonusSquareHeld = gtk_util_bonusSquareHeld; - util->vtable->m_util_playerScoreHeld = gtk_util_playerScoreHeld; -#endif -#ifdef XWFEATURE_BOARDWORDS - util->vtable->m_util_cellSquareHeld = gtk_util_cellSquareHeld; -#endif - - util->closure = globals; -} /* setupGtkUtilCallbacks */ - -#ifndef XWFEATURE_STANDALONE_ONLY -static gboolean -newConnectionInput( GIOChannel *source, - GIOCondition condition, - gpointer data ) -{ - gboolean keepSource = TRUE; - int sock = g_io_channel_unix_get_fd( source ); - GtkAppGlobals* globals = (GtkAppGlobals*)data; - - XP_LOGF( "%s(%p):condition = 0x%x", __func__, source, (int)condition ); - -/* XP_ASSERT( sock == globals->cGlobals.socket ); */ - - if ( (condition & G_IO_IN) != 0 ) { - ssize_t nRead; - unsigned char buf[512]; - CommsAddrRec* addrp = NULL; -#if defined XWFEATURE_IP_DIRECT || defined XWFEATURE_SMS - CommsAddrRec addr; -#endif - - switch ( comms_getConType( globals->cGlobals.game.comms ) ) { -#ifdef XWFEATURE_RELAY - case COMMS_CONN_RELAY: - XP_ASSERT( globals->cGlobals.socket == sock ); - nRead = linux_relay_receive( &globals->cGlobals, buf, sizeof(buf) ); - break; -#endif -#ifdef XWFEATURE_BLUETOOTH - case COMMS_CONN_BT: - nRead = linux_bt_receive( sock, buf, sizeof(buf) ); - break; -#endif -#ifdef XWFEATURE_SMS - case COMMS_CONN_SMS: - addrp = &addr; - nRead = linux_sms_receive( &globals->cGlobals, sock, - buf, sizeof(buf), addrp ); - break; -#endif -#ifdef XWFEATURE_IP_DIRECT - case COMMS_CONN_IP_DIRECT: - addrp = &addr; - nRead = linux_udp_receive( sock, buf, sizeof(buf), addrp, &globals->cGlobals ); - break; -#endif - default: - XP_ASSERT( 0 ); - } - - if ( !globals->dropIncommingMsgs && nRead > 0 ) { - XWStreamCtxt* inboundS; - XP_Bool redraw = XP_FALSE; - - inboundS = stream_from_msgbuf( &globals->cGlobals, buf, nRead ); - if ( !!inboundS ) { - XP_LOGF( "%s: got %d bytes", __func__, nRead ); - if ( comms_checkIncomingStream( globals->cGlobals.game.comms, - inboundS, addrp ) ) { - redraw = - server_receiveMessage(globals->cGlobals.game.server, - inboundS ); - if ( redraw ) { - saveGame( &globals->cGlobals ); - } - } - stream_destroy( inboundS ); - } - - /* if there's something to draw resulting from the message, we - need to give the main loop time to reflect that on the screen - before giving the server another shot. So just call the idle - proc. */ - if ( redraw ) { - gtk_util_requestTime( globals->cGlobals.params->util ); - } else { - redraw = server_do( globals->cGlobals.game.server ); - } - if ( redraw ) { - board_draw( globals->cGlobals.game.board ); - } - } else { - XP_LOGF( "errno from read: %d/%s", errno, strerror(errno) ); - } - } - - if ( (condition & (G_IO_HUP | G_IO_ERR)) != 0 ) { - XP_LOGF( "dropping socket %d", sock ); - close( sock ); -#ifdef XWFEATURE_RELAY - globals->cGlobals.socket = -1; -#endif - if ( 0 ) { -#ifdef XWFEATURE_BLUETOOTH - } else if ( COMMS_CONN_BT == globals->cGlobals.params->conType ) { - linux_bt_socketclosed( &globals->cGlobals, sock ); -#endif -#ifdef XWFEATURE_IP_DIRECT - } else if ( COMMS_CONN_IP_DIRECT == globals->cGlobals.params->conType ) { - linux_udp_socketclosed( &globals->cGlobals, sock ); -#endif - } - keepSource = FALSE; /* remove the event source */ - } - - return keepSource; /* FALSE means to remove event source */ -} /* newConnectionInput */ - -typedef struct SockInfo { - GIOChannel* channel; - guint watch; - int socket; -} SockInfo; - -static void -gtk_socket_changed( void* closure, int oldSock, int newSock, void** storage ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)closure; - SockInfo* info = (SockInfo*)*storage; - XP_LOGF( "%s(old:%d; new:%d)", __func__, oldSock, newSock ); - - if ( oldSock != -1 ) { - XP_ASSERT( info != NULL ); - g_source_remove( info->watch ); - g_io_channel_unref( info->channel ); - XP_FREE( globals->cGlobals.params->util->mpool, info ); - *storage = NULL; - XP_LOGF( "Removed socket %d from gtk's list of listened-to sockets", - oldSock ); - } - if ( newSock != -1 ) { - info = (SockInfo*)XP_MALLOC( globals->cGlobals.params->util->mpool, - sizeof(*info) ); - GIOChannel* channel = g_io_channel_unix_new( newSock ); - g_io_channel_set_close_on_unref( channel, TRUE ); - guint result = g_io_add_watch( channel, - G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI, - newConnectionInput, - globals ); - info->channel = channel; - info->watch = result; - *storage = info; - XP_LOGF( "g_io_add_watch(%d) => %d", newSock, result ); - } -#ifdef XWFEATURE_RELAY - globals->cGlobals.socket = newSock; -#endif - /* A hack for the bluetooth case. */ - CommsCtxt* comms = globals->cGlobals.game.comms; - if ( (comms != NULL) && (comms_getConType(comms) == COMMS_CONN_BT) ) { - comms_resendAll( comms, XP_FALSE ); - } - LOG_RETURN_VOID(); -} /* gtk_socket_changed */ - -static gboolean -acceptorInput( GIOChannel* source, GIOCondition condition, gpointer data ) -{ - gboolean keepSource; - CommonGlobals* globals = (CommonGlobals*)data; - LOG_FUNC(); - - if ( (condition & G_IO_IN) != 0 ) { - int listener = g_io_channel_unix_get_fd( source ); - XP_LOGF( "%s: input on socket %d", __func__, listener ); - keepSource = (*globals->acceptor)( listener, data ); - } else { - keepSource = FALSE; - } - - return keepSource; -} /* acceptorInput */ - -static void -gtk_socket_acceptor( int listener, Acceptor func, CommonGlobals* globals, - void** storage ) -{ - SockInfo* info = (SockInfo*)*storage; - GIOChannel* channel; - guint watch; - - LOG_FUNC(); - - if ( listener == -1 ) { - XP_ASSERT( !!globals->acceptor ); - globals->acceptor = NULL; - XP_ASSERT( !!info ); -#ifdef DEBUG - int oldSock = info->socket; -#endif - g_source_remove( info->watch ); - g_io_channel_unref( info->channel ); - XP_FREE( globals->params->util->mpool, info ); - *storage = NULL; - XP_LOGF( "Removed listener %d from gtk's list of listened-to sockets", oldSock ); - } else { - XP_ASSERT( !globals->acceptor || (func == globals->acceptor) ); - globals->acceptor = func; - - channel = g_io_channel_unix_new( listener ); - g_io_channel_set_close_on_unref( channel, TRUE ); - watch = g_io_add_watch( channel, - G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI, - acceptorInput, globals ); - g_io_channel_unref( channel ); /* only main loop holds it now */ - XP_LOGF( "%s: g_io_add_watch(%d) => %d", __func__, listener, watch ); - - XP_ASSERT( NULL == info ); - info = XP_MALLOC( globals->params->util->mpool, sizeof(*info) ); - info->channel = channel; - info->watch = watch; - info->socket = listener; - *storage = info; - } -} /* gtk_socket_acceptor */ - -static void -drop_msg_toggle( GtkWidget* toggle, GtkAppGlobals* globals ) -{ - globals->dropIncommingMsgs = gtk_toggle_button_get_active( - GTK_TOGGLE_BUTTON(toggle) ); -} /* drop_msg_toggle */ -#endif - -static GtkAppGlobals* g_globals_for_signal; -static void -handle_sigintterm( int XP_UNUSED(sig) ) -{ - LOG_FUNC(); - gtk_main_quit(); + gtk_widget_show( window ); + return window; } int -gtkmain( LaunchParams* params, int argc, char *argv[] ) +gtkmain( LaunchParams* params ) { - short width, height; - GtkWidget* window; - GtkWidget* drawing_area; - GtkWidget* menubar; - GtkWidget* buttonbar; - GtkWidget* vbox; - GtkWidget* hbox; - GtkAppGlobals globals; -#ifndef XWFEATURE_STANDALONE_ONLY - GtkWidget* dropCheck; -#endif + GTKGamesGlobals gg = {0}; + gg.params = params; + XP_LOGF( "%s: I'M HERE!!! (calling makeGamesDB())", __func__ ); + gg.pDb = openGamesDB(); - g_globals_for_signal = &globals; - struct sigaction act = { .sa_handler = handle_sigintterm }; - sigaction( SIGINT, &act, NULL ); - sigaction( SIGTERM, &act, NULL ); + (void)makeGamesWindow( &gg ); + gtk_main(); - memset( &globals, 0, sizeof(globals) ); - - globals.cGlobals.params = params; - globals.cGlobals.lastNTilesToUse = MAX_TRAY_TILES; -#ifndef XWFEATURE_STANDALONE_ONLY -# ifdef XWFEATURE_RELAY - globals.cGlobals.socket = -1; -# endif - - globals.cGlobals.socketChanged = gtk_socket_changed; - globals.cGlobals.socketChangedClosure = &globals; - globals.cGlobals.addAcceptor = gtk_socket_acceptor; -#endif - - globals.cGlobals.cp.showBoardArrow = XP_TRUE; - globals.cGlobals.cp.hideTileValues = params->hideValues; - globals.cGlobals.cp.skipCommitConfirm = params->skipCommitConfirm; - globals.cGlobals.cp.sortNewTiles = params->sortNewTiles; - globals.cGlobals.cp.showColors = params->showColors; - globals.cGlobals.cp.allowPeek = params->allowPeek; - globals.cGlobals.cp.showRobotScores = params->showRobotScores; -#ifdef XWFEATURE_SLOW_ROBOT - globals.cGlobals.cp.robotThinkMin = params->robotThinkMin; - globals.cGlobals.cp.robotThinkMax = params->robotThinkMax; - globals.cGlobals.cp.robotTradePct = params->robotTradePct; -#endif -#ifdef XWFEATURE_CROSSHAIRS - globals.cGlobals.cp.hideCrosshairs = params->hideCrosshairs; -#endif - - setupGtkUtilCallbacks( &globals, params->util ); - - /* Now set up the gtk stuff. This is necessary before we make the - draw ctxt */ - gtk_init( &argc, &argv ); - - globals.window = window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); - if ( !!params->fileName ) { - gtk_window_set_title( GTK_WINDOW(window), params->fileName ); - } - - vbox = gtk_vbox_new (FALSE, 0); - gtk_container_add( GTK_CONTAINER(window), vbox ); - gtk_widget_show( vbox ); - - g_signal_connect( G_OBJECT (window), "destroy", - G_CALLBACK( quit ), &globals ); - - menubar = makeMenus( &globals, argc, argv ); - gtk_box_pack_start( GTK_BOX(vbox), menubar, FALSE, TRUE, 0); - -#ifndef XWFEATURE_STANDALONE_ONLY - dropCheck = gtk_check_button_new_with_label( "drop incoming messages" ); - g_signal_connect( GTK_OBJECT(dropCheck), - "toggled", G_CALLBACK(drop_msg_toggle), &globals ); - gtk_box_pack_start( GTK_BOX(vbox), dropCheck, FALSE, TRUE, 0); - gtk_widget_show( dropCheck ); -#endif - - buttonbar = makeButtons( &globals, argc, argv ); - gtk_box_pack_start( GTK_BOX(vbox), buttonbar, FALSE, TRUE, 0); - - drawing_area = gtk_drawing_area_new(); - globals.drawing_area = drawing_area; - gtk_widget_show( drawing_area ); - - width = GTK_HOR_SCORE_WIDTH + GTK_TIMER_WIDTH + GTK_TIMER_PAD; - if ( globals.cGlobals.params->verticalScore ) { - width += GTK_VERT_SCORE_WIDTH; - } - height = 196; - if ( globals.cGlobals.params->nHidden == 0 ) { - height += GTK_MIN_SCALE * GTK_TRAY_HT_ROWS; - } - - gtk_widget_set_size_request( GTK_WIDGET(drawing_area), width, height ); - - hbox = gtk_hbox_new( FALSE, 0 ); - gtk_box_pack_start( GTK_BOX (hbox), drawing_area, TRUE, TRUE, 0); - - /* install scrollbar even if not needed -- since zooming can make it - needed later */ - GtkWidget* vscrollbar; - gint nRows = globals.cGlobals.params->gi.boardSize; - globals.adjustment = (GtkAdjustment*) - gtk_adjustment_new( 0, 0, nRows, 1, 2, - nRows - globals.cGlobals.params->nHidden ); - vscrollbar = gtk_vscrollbar_new( globals.adjustment ); - g_signal_connect( GTK_OBJECT(globals.adjustment), "value_changed", - G_CALLBACK(scroll_value_changed), &globals ); - gtk_widget_show( vscrollbar ); - gtk_box_pack_start( GTK_BOX(hbox), vscrollbar, TRUE, TRUE, 0 ); - - gtk_box_pack_start( GTK_BOX (hbox), - makeVerticalBar( &globals, window ), - FALSE, TRUE, 0 ); - gtk_widget_show( hbox ); - - gtk_box_pack_start( GTK_BOX(vbox), hbox/* drawing_area */, TRUE, TRUE, 0); - - g_signal_connect( GTK_OBJECT(drawing_area), "expose_event", - G_CALLBACK(expose_event), &globals ); - g_signal_connect( GTK_OBJECT(drawing_area),"configure_event", - G_CALLBACK(configure_event), &globals ); - g_signal_connect( GTK_OBJECT(drawing_area), "button_press_event", - G_CALLBACK(button_press_event), &globals ); - g_signal_connect( GTK_OBJECT(drawing_area), "motion_notify_event", - G_CALLBACK(motion_notify_event), &globals ); - g_signal_connect( GTK_OBJECT(drawing_area), "button_release_event", - G_CALLBACK(button_release_event), &globals ); - - setOneSecondTimer( &globals.cGlobals ); - -#ifdef KEY_SUPPORT -# ifdef KEYBOARD_NAV - g_signal_connect( GTK_OBJECT(window), "key_press_event", - G_CALLBACK(key_press_event), &globals ); -# endif - g_signal_connect( GTK_OBJECT(window), "key_release_event", - G_CALLBACK(key_release_event), &globals ); -#endif - - gtk_widget_set_events( drawing_area, GDK_EXPOSURE_MASK - | GDK_LEAVE_NOTIFY_MASK - | GDK_BUTTON_PRESS_MASK - | GDK_POINTER_MOTION_MASK - | GDK_BUTTON_RELEASE_MASK -#ifdef KEY_SUPPORT -# ifdef KEYBOARD_NAV - | GDK_KEY_PRESS_MASK -# endif - | GDK_KEY_RELEASE_MASK -#endif -/* | GDK_POINTER_MOTION_HINT_MASK */ - ); - - if ( !!params->pipe && !!params->fileName ) { - read_pipe_then_close( &globals.cGlobals, NULL ); - } else { - gtk_widget_show( window ); - - gtk_main(); - } - /* MONCONTROL(1); */ - - cleanup( &globals ); + closeGamesDB( gg.pDb ); + XP_LOGF( "%s: I'M BACK!!!", __func__ ); return 0; } /* gtkmain */ diff --git a/xwords4/linux/gtkmain.h b/xwords4/linux/gtkmain.h index ca06ea8b3..2d5c7ce52 100644 --- a/xwords4/linux/gtkmain.h +++ b/xwords4/linux/gtkmain.h @@ -20,154 +20,8 @@ #ifndef _GTKMAIN_H_ #define _GTKMAIN_H_ -#ifdef PLATFORM_GTK -#include -#include -#include -#include - -#include "draw.h" #include "main.h" -#include "game.h" -#include "dictnry.h" -enum { - LAYOUT_BOARD - ,LAYOUT_SMALL - ,LAYOUT_LARGE - ,LAYOUT_NLAYOUTS -}; - -#define MAX_SCORE_LEN 31 - -typedef struct GtkDrawCtx { - DrawCtxVTable* vtable; - -/* GdkDrawable* pixmap; */ - GtkWidget* drawing_area; - struct GtkAppGlobals* globals; - -#ifdef USE_CAIRO - cairo_t* cr; -#else - GdkGC* drawGC; -#endif - - GdkColor black; - GdkColor white; - GdkColor red; /* for pending tiles */ - GdkColor tileBack; /* for pending tiles */ - GdkColor cursor; - GdkColor bonusColors[4]; - GdkColor playerColors[MAX_NUM_PLAYERS]; - - /* new for gtk 2.0 */ - PangoContext* pangoContext; - GList* fontsPerSize; - - struct { - XP_UCHAR str[MAX_SCORE_LEN+1]; - XP_U16 fontHt; - } scoreCache[MAX_NUM_PLAYERS]; - - XP_U16 trayOwner; - XP_U16 cellWidth; - XP_U16 cellHeight; - - XP_Bool scoreIsVertical; -} GtkDrawCtx; - -typedef struct ClientStreamRec { - XWStreamCtxt* stream; - guint key; - int sock; -} ClientStreamRec; - -typedef struct GtkAppGlobals { - CommonGlobals cGlobals; - GtkWidget* window; - GtkDrawCtx* draw; - -/* GdkPixmap* pixmap; */ - GtkWidget* drawing_area; - - GtkWidget* flip_button; - GtkWidget* zoomin_button; - GtkWidget* zoomout_button; - GtkWidget* toggle_undo_button; - GtkWidget* prevhint_button; - GtkWidget* nexthint_button; - -#ifdef XWFEATURE_CHAT - GtkWidget* chat_button; -#endif - - EngineCtxt* engine; - - guint idleID; - - struct timeval scoreTv; /* for timer */ - XP_U32 scoreTimerInterval; - - GtkAdjustment* adjustment; - - ClientStreamRec clientRecs[MAX_NUM_PLAYERS]; - - guint timerSources[NUM_TIMERS_PLUS_ONE - 1]; - -#ifndef XWFEATURE_STANDALONE_ONLY - XP_U16 netStatLeft, netStatTop; - XP_UCHAR stateChar; -#endif - - XP_Bool gridOn; - XP_Bool dropIncommingMsgs; - XP_Bool mouseDown; - XP_Bool altKeyDown; -#ifdef KEYBOARD_NAV - XP_Bool keyDown; -#endif -} GtkAppGlobals; - -/* DictionaryCtxt* gtk_dictionary_make(); */ -int gtkmain( LaunchParams* params, int argc, char *argv[] ); - -#define GTK_MIN_SCALE 12 /* was 14 */ - -#define GTK_MIN_TRAY_SCALEH 24 -#define GTK_MIN_TRAY_SCALEV GTK_MIN_TRAY_SCALEH -#define GTK_TRAYPAD_WIDTH 2 - -#define GTK_TOP_MARGIN 0 /* was 2 */ -#define GTK_BOARD_LEFT_MARGIN 2 -#define GTK_TRAY_LEFT_MARGIN 2 -#define GTK_SCORE_BOARD_PADDING 0 - -#define GTK_HOR_SCORE_LEFT (GTK_BOARD_LEFT_MARGIN) -#define GTK_HOR_SCORE_HEIGHT 12 -#define GTK_TIMER_HEIGHT GTK_HOR_SCORE_HEIGHT -#define GTK_HOR_SCORE_TOP (GTK_TOP_MARGIN) -#define GTK_TIMER_PAD 10 -#define GTK_VERT_SCORE_TOP (GTK_TIMER_HEIGHT + GTK_TIMER_PAD) -#define GTK_VERT_SCORE_HEIGHT ((MIN_SCALE*MAX_COLS) - GTK_TIMER_HEIGHT - \ - GTK_TIMER_PAD) -#define GTK_TIMER_WIDTH 40 -#define GTK_NETSTAT_WIDTH 20 -#define GTK_TIMER_TOP GTK_HOR_SCORE_TOP -#define GTK_HOR_SCORE_WIDTH ((GTK_MIN_SCALE*20)-GTK_TIMER_PAD) -#define GTK_VERT_SCORE_WIDTH 40 - -#define GTK_BOARD_TOP (GTK_SCORE_TOP + GTK_SCORE_HEIGHT \ - + GTK_SCORE_BOARD_PADDING ) -#define GTK_BOARD_LEFT (GTK_BOARD_LEFT_MARGIN) - -#define GTK_TRAY_LEFT GTK_TRAY_LEFT_MARGIN - -#define GTK_DIVIDER_WIDTH 5 - -#define GTK_BOTTOM_MARGIN GTK_TOP_MARGIN -#define GTK_RIGHT_MARGIN GTK_BOARD_LEFT_MARGIN - -#endif /* PLATFORM_GTK */ +int gtkmain( LaunchParams* params ); #endif diff --git a/xwords4/linux/gtknewgame.c b/xwords4/linux/gtknewgame.c index ad1f29c7a..b9f6fc04d 100644 --- a/xwords4/linux/gtknewgame.c +++ b/xwords4/linux/gtknewgame.c @@ -22,13 +22,14 @@ #include #include "linuxutl.h" +#include "linuxmain.h" #include "gtknewgame.h" #include "strutils.h" #include "nwgamest.h" #include "gtkconnsdlg.h" #include "gtkutils.h" -#define MAX_SIZE_CHOICES 10 +#define MAX_SIZE_CHOICES 32 typedef struct GtkNewGameState { GtkAppGlobals* globals; @@ -43,6 +44,7 @@ typedef struct GtkNewGameState { XP_Bool fireConnDlg; gboolean isNewGame; short nCols; /* for board size */ + gchar* dict; #ifndef XWFEATURE_STANDALONE_ONLY GtkWidget* remoteChecks[MAX_NUM_PLAYERS]; @@ -153,6 +155,14 @@ size_combo_changed( GtkComboBox* combo, gpointer gp ) } } /* size_combo_changed */ +static void +dict_combo_changed( GtkComboBox* combo, gpointer gp ) +{ + GtkNewGameState* state = (GtkNewGameState*)gp; + state->dict = gtk_combo_box_get_active_text( GTK_COMBO_BOX(combo) ); + XP_LOGF( "got dict: %s", state->dict ); +} /* size_combo_changed */ + static void handle_ok( GtkWidget* XP_UNUSED(widget), gpointer closure ) { @@ -199,6 +209,7 @@ makeNewGameDialog( GtkNewGameState* state ) #endif GtkWidget* nPlayersCombo; GtkWidget* boardSizeCombo; + GtkWidget* dictCombo; CurGameInfo* gi; short ii; @@ -334,6 +345,17 @@ makeNewGameDialog( GtkNewGameState* state ) gtk_box_pack_start( GTK_BOX(hbox), gtk_label_new("Dictionary: "), FALSE, TRUE, 0 ); + dictCombo = gtk_combo_box_new_text(); + g_signal_connect( GTK_OBJECT(dictCombo), "changed", + G_CALLBACK(dict_combo_changed), state ); + gtk_widget_show( dictCombo ); + gtk_box_pack_start( GTK_BOX(hbox), dictCombo, FALSE, TRUE, 0 ); + + GSList* dicts = listDicts( state->globals->cGlobals.params ); + for ( GSList* iter = dicts; !!iter; iter = iter->next ) { + gtk_combo_box_append_text( GTK_COMBO_BOX(dictCombo), iter->data ); + } + g_slist_free( dicts ); if ( !!gi->dictName ) { gtk_box_pack_start( GTK_BOX(hbox), @@ -558,6 +580,9 @@ newGameDialog( GtkAppGlobals* globals, CommsAddrRec* addr, XP_Bool isNewGame, if ( newg_store( state.newGameCtxt, &globals->cGlobals.params->gi, XP_TRUE ) ) { globals->cGlobals.params->gi.boardSize = state.nCols; + replaceStringIfDifferent( globals->cGlobals.params->util->mpool, + &globals->cGlobals.params->gi.dictName, + state.dict ); } else { /* Do it again if we warned user of inconsistency. */ state.revert = XP_TRUE; diff --git a/xwords4/linux/gtknewgame.h b/xwords4/linux/gtknewgame.h index 5d156333e..3a97bd2ac 100644 --- a/xwords4/linux/gtknewgame.h +++ b/xwords4/linux/gtknewgame.h @@ -24,7 +24,7 @@ #ifndef _GTKNEWGAME_H_ #define _GTKNEWGAME_H_ -#include "gtkmain.h" +#include "gtkboard.h" gboolean newGameDialog( GtkAppGlobals* globals, CommsAddrRec* addr, XP_Bool isNewGame, XP_Bool fireConnDlg ); diff --git a/xwords4/linux/gtkntilesask.h b/xwords4/linux/gtkntilesask.h index c4c1548a3..bdf1427cd 100644 --- a/xwords4/linux/gtkntilesask.h +++ b/xwords4/linux/gtkntilesask.h @@ -23,7 +23,7 @@ #ifndef _GTKNTILESASK_H_ #define _GTKNTILESASK_H_ -#include "gtkmain.h" +#include "gtkboard.h" XP_U16 askNTiles( XP_U16 nTilesMax, XP_U16 deflt ); diff --git a/xwords4/linux/gtkutils.h b/xwords4/linux/gtkutils.h index 04eb4c8a8..e80f8c3b5 100644 --- a/xwords4/linux/gtkutils.h +++ b/xwords4/linux/gtkutils.h @@ -24,7 +24,7 @@ #ifndef _GTKUTILS_H_ #define _GTKUTILS_H_ -#include "gtkmain.h" +#include "gtkboard.h" GtkWidget* makeButton( char* text, GCallback func, gpointer data ); GtkWidget* makeLabeledField( const char* labelText, GtkWidget** field ); diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 82b7d5c04..81bb34651 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -61,6 +61,7 @@ # include "cursesmain.h" #endif #ifdef PLATFORM_GTK +# include "gtkboard.h" # include "gtkmain.h" #endif #include "model.h" @@ -1214,6 +1215,7 @@ static void tmp_noop_sigintterm( int XP_UNUSED(sig) ) { LOG_FUNC(); + exit(0); } #ifdef XWFEATURE_WALKDICT @@ -1505,6 +1507,39 @@ listDicts( const LaunchParams *params ) return result; } +void +initParams( LaunchParams* params ) +{ + memset( params, 0, sizeof(*params) ); + + params->util = malloc( sizeof(params->util) ); + + XP_MEMSET( params->util, 0, sizeof(params->util) ); + +#ifdef MEM_DEBUG + params->util->mpool = mpool_make(); +#endif + + params->vtMgr = make_vtablemgr(MPPARM_NOCOMMA(params->util->mpool)); + linux_util_vt_init( MPPARM(params->util->mpool) params->util ); +#ifndef XWFEATURE_STANDALONE_ONLY + params->util->vtable->m_util_informMissing = linux_util_informMissing; + params->util->vtable->m_util_addrChange = linux_util_addrChange; + params->util->vtable->m_util_setIsServer = linux_util_setIsServer; +#endif +} + +void +freeParams( LaunchParams* params ) +{ + vtmgr_destroy( MPPARM(params->util->mpool) params->vtMgr ); + linux_util_vt_destroy( params->util ); + + mpool_destroy( params->util->mpool ); + + free( params->util ); +} + int main( int argc, char** argv ) { @@ -1560,17 +1595,7 @@ main( int argc, char** argv ) } #endif - memset( &mainParams, 0, sizeof(mainParams) ); - - mainParams.util = malloc( sizeof(*mainParams.util) ); - XP_MEMSET( mainParams.util, 0, sizeof(*mainParams.util) ); - -#ifdef MEM_DEBUG - mainParams.util->mpool = mpool_make(); -#endif - - mainParams.vtMgr = make_vtablemgr(MPPARM_NOCOMMA(mainParams.util->mpool)); - + initParams( &mainParams ); /* fprintf( stdout, "press to start\n" ); */ /* (void)fgetc( stdin ); */ @@ -2074,14 +2099,6 @@ main( int argc, char** argv ) mainParams.util->gameInfo = &mainParams.gi; - linux_util_vt_init( MPPARM(mainParams.util->mpool) mainParams.util ); - -#ifndef XWFEATURE_STANDALONE_ONLY - mainParams.util->vtable->m_util_informMissing = linux_util_informMissing; - mainParams.util->vtable->m_util_addrChange = linux_util_addrChange; - mainParams.util->vtable->m_util_setIsServer = linux_util_setIsServer; -#endif - srandom( seed ); /* init linux random number generator */ XP_LOGF( "seeded srandom with %d", seed ); @@ -2114,19 +2131,14 @@ main( int argc, char** argv ) #endif } else if ( !useCurses ) { #if defined PLATFORM_GTK - gtkmain( &mainParams, argc, argv ); + gtk_init( &argc, &argv ); + gtkmain( &mainParams ); #endif } else { usage( argv[0], "rtfm" ); } - vtmgr_destroy( MPPARM(mainParams.util->mpool) mainParams.vtMgr ); - - linux_util_vt_destroy( mainParams.util ); - - mpool_destroy( mainParams.util->mpool ); - - free( mainParams.util ); + freeParams( &mainParams ); XP_LOGF( "%s exiting main", argv[0] ); return 0; diff --git a/xwords4/linux/linuxmain.h b/xwords4/linux/linuxmain.h index 44cf263e5..e4d4eb526 100644 --- a/xwords4/linux/linuxmain.h +++ b/xwords4/linux/linuxmain.h @@ -94,4 +94,7 @@ void setOneSecondTimer( CommonGlobals* cGlobals ); # define setOneSecondTimer( cGlobals ) #endif +void initParams( LaunchParams* params ); +void freeParams( LaunchParams* params ); + #endif From e0dc83ed70e23488a1cac3c1600d38415814b5e7 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 5 Jan 2013 16:08:47 -0800 Subject: [PATCH 002/116] rest of what I just meant to commit. --- xwords4/common/mempool.c | 18 +++--- xwords4/linux/gamesdb.c | 47 +++++++++++++-- xwords4/linux/gamesdb.h | 4 ++ xwords4/linux/gtkboard.c | 119 ++++++++++++++++++++++--------------- xwords4/linux/gtkmain.c | 43 ++++++++++++-- xwords4/linux/gtknewgame.c | 46 +++++++------- xwords4/linux/gtknewgame.h | 9 +-- xwords4/linux/linuxmain.c | 32 +++++----- xwords4/linux/linuxmain.h | 4 +- xwords4/linux/main.h | 4 ++ 10 files changed, 217 insertions(+), 109 deletions(-) diff --git a/xwords4/common/mempool.c b/xwords4/common/mempool.c index 799cce0a8..206c4de04 100644 --- a/xwords4/common/mempool.c +++ b/xwords4/common/mempool.c @@ -78,19 +78,21 @@ checkIsText( MemPoolEntry* entry ) { unsigned char* txt = (unsigned char*)entry->ptr; XP_U32 len = entry->size; + char* result = NULL; - while ( len-- ) { - unsigned char c = *txt++; - if ( c < 32 || c > 127 ) { - if ( len == 0 && c == '\0' ) { - return (char*)entry->ptr; - } else { - return (char*)NULL; + if ( 0 < len ) { + while ( len-- ) { + unsigned char c = *txt++; + if ( c < 32 || c > 127 ) { + if ( len == 0 && c == '\0' ) { + result = (char*)entry->ptr; + } + break; } } } - return (char*)NULL; + return result; } /* checkIsText */ #endif diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c index cceee713d..105d45999 100644 --- a/xwords4/linux/gamesdb.c +++ b/xwords4/linux/gamesdb.c @@ -1,6 +1,6 @@ -/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* -*-mode: compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ /* - * Copyright 2000-2012 by Eric House (xwords@eehouse.org). All rights + * Copyright 2000-2013 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or @@ -18,10 +18,8 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include - -#include "comtypes.h" #include "gamesdb.h" +#include "main.h" #define DB_NAME "games.db" @@ -51,3 +49,42 @@ closeGamesDB( sqlite3* pDb ) sqlite3_close( pDb ); XP_LOGF( "%s finished", __func__ ); } + +void +writeToDB( XWStreamCtxt* stream, void* closure ) +{ + int result; + CommonGlobals* cGlobals = (CommonGlobals*)closure; + sqlite3_int64 rowid = cGlobals->rowid; + sqlite3* pDb = cGlobals->pDb; + XP_U16 len = stream_getSize( stream ); + + sqlite3_stmt* stmt = NULL; + if ( 0 == rowid ) { /* new row; need to insert blob first */ + const char* txt = "INSERT INTO games (game) VALUES (?)"; + result = sqlite3_prepare_v2( pDb, txt, -1, &stmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_bind_zeroblob( stmt, 1 /*col 0 ??*/, len ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( stmt ); + XP_ASSERT( SQLITE_DONE == result ); + + rowid = sqlite3_last_insert_rowid( pDb ); + XP_LOGF( "%s: new rowid: %lld", __func__, rowid ); + cGlobals->rowid = rowid; + sqlite3_finalize( stmt ); + } + + sqlite3_blob* blob; + result = sqlite3_blob_open( pDb, "main", "games", "game", + rowid, 1 /*flags: writeable*/, &blob ); + XP_ASSERT( SQLITE_OK == result ); + const XP_U8* ptr = stream_getPtr( stream ); + result = sqlite3_blob_write( blob, ptr, len, 0 ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_blob_close( blob ); + XP_ASSERT( SQLITE_OK == result ); + if ( !!stmt ) { + sqlite3_finalize( stmt ); + } +} diff --git a/xwords4/linux/gamesdb.h b/xwords4/linux/gamesdb.h index a76deb6cc..5a7c6c15b 100644 --- a/xwords4/linux/gamesdb.h +++ b/xwords4/linux/gamesdb.h @@ -23,7 +23,11 @@ #include +#include "comtypes.h" + sqlite3* openGamesDB( void ); void closeGamesDB( sqlite3* dbp ); +void writeToDB( XWStreamCtxt* stream, void* closure ); + #endif diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 695ac6ba7..87267d110 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -395,8 +395,6 @@ createOrLoadObjects( GtkAppGlobals* globals ) XP_Bool opened = XP_FALSE; #ifndef XWFEATURE_STANDALONE_ONLY - DeviceRole serverRole = globals->cGlobals.params->serverRole; - XP_Bool isServer = serverRole != SERVER_ISCLIENT; #endif LaunchParams* params = globals->cGlobals.params; @@ -500,15 +498,20 @@ createOrLoadObjects( GtkAppGlobals* globals ) params->gi.allowHintRect = params->allowHintRect; #endif + if ( params->needsNewGame ) { new_game_impl( globals, XP_FALSE ); #ifndef XWFEATURE_STANDALONE_ONLY - } else if ( !isServer ) { - XWStreamCtxt* stream = - mem_stream_make( MEMPOOL params->vtMgr, &globals->cGlobals, CHANNEL_NONE, - sendOnClose ); - server_initClientConnection( globals->cGlobals.game.server, - stream ); + } else { + DeviceRole serverRole = globals->cGlobals.params->gi.serverRole; + if ( serverRole == SERVER_ISCLIENT ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL params->vtMgr, + &globals->cGlobals, CHANNEL_NONE, + sendOnClose ); + server_initClientConnection( globals->cGlobals.game.server, + stream ); + } #endif } } @@ -670,9 +673,12 @@ handle_client_event( GtkWidget *widget, GdkEventClient *event, #endif static void -quit( void ) +destroy_window( GtkWidget* XP_UNUSED(widget), gpointer data ) { - gtk_main_quit(); + LOG_FUNC(); + GtkAppGlobals* globals = (GtkAppGlobals*)data; + saveGame( &globals->cGlobals ); + // gtk_main_quit(); } static void @@ -786,9 +792,9 @@ new_game_impl( GtkAppGlobals* globals, XP_Bool fireConnDlg ) comms_getInitialAddr( &addr, RELAY_NAME_DEFAULT, RELAY_PORT_DEFAULT ); } - success = newGameDialog( globals, &addr, XP_TRUE, fireConnDlg ); + CurGameInfo* gi = &globals->cGlobals.params->gi; + success = newGameDialog( globals, gi, &addr, XP_TRUE, fireConnDlg ); if ( success ) { - CurGameInfo* gi = &globals->cGlobals.params->gi; #ifndef XWFEATURE_STANDALONE_ONLY XP_Bool isClient = gi->serverRole == SERVER_ISCLIENT; #endif @@ -803,22 +809,22 @@ new_game_impl( GtkAppGlobals* globals, XP_Bool fireConnDlg ) if ( !game_reset( MEMPOOL &globals->cGlobals.game, gi, globals->cGlobals.params->util, &globals->cGlobals.cp, &procs ) ) { - if ( NULL == globals->draw ) { - globals->draw = (GtkDrawCtx*)gtkDrawCtxtMake( globals->drawing_area, - globals ); - } - game_makeNewGame( MEMPOOL &globals->cGlobals.game, gi, - globals->cGlobals.params->util, - (DrawCtx*)globals->draw, - &globals->cGlobals.cp, &procs, - globals->cGlobals.params->gameSeed ); - ModelCtxt* model = globals->cGlobals.game.model; - if ( NULL == model_getDictionary( model ) ) { - DictionaryCtxt* dict = - linux_dictionary_make( MEMPOOL globals->cGlobals.params, - gi->dictName, XP_TRUE ); - model_setDictionary( model, dict ); - } + /* if ( NULL == globals->draw ) { */ + /* globals->draw = (GtkDrawCtx*)gtkDrawCtxtMake( globals->drawing_area, */ + /* globals ); */ + /* } */ + /* game_makeNewGame( MEMPOOL &globals->cGlobals.game, gi, */ + /* globals->cGlobals.params->util, */ + /* (DrawCtx*)globals->draw, */ + /* &globals->cGlobals.cp, &procs, */ + /* globals->cGlobals.params->gameSeed ); */ + /* ModelCtxt* model = globals->cGlobals.game.model; */ + /* if ( NULL == model_getDictionary( model ) ) { */ + /* DictionaryCtxt* dict = */ + /* linux_dictionary_make( MEMPOOL globals->cGlobals.params, */ + /* gi->dictName, XP_TRUE ); */ + /* model_setDictionary( model, dict ); */ + /* } */ } #ifndef XWFEATURE_STANDALONE_ONLY @@ -858,7 +864,8 @@ game_info( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) /* Anything to do if OK is clicked? Changed names etc. already saved. Try server_do in case one's become a robot. */ - if ( newGameDialog( globals, &addr, XP_FALSE, XP_FALSE ) ) { + CurGameInfo* gi = &globals->cGlobals.params->gi; + if ( newGameDialog( globals, gi, &addr, XP_FALSE, XP_FALSE ) ) { if ( server_do( globals->cGlobals.game.server ) ) { board_draw( globals->cGlobals.game.board ); } @@ -868,11 +875,13 @@ game_info( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) static void load_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) { + XP_ASSERT(0); } /* load_game */ static void save_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) { + XP_ASSERT(0); } /* save_game */ #ifdef XWFEATURE_CHANGEDICT @@ -1478,7 +1487,7 @@ gtk_util_notifyGameOver( XW_UtilCtxt* uc, XP_S16 quitter ) if ( cGlobals->params->quitAfter >= 0 ) { sleep( cGlobals->params->quitAfter ); - quit(); + destroy_window( NULL, globals ); } else if ( cGlobals->params->undoWhenDone ) { server_handleUndo( cGlobals->game.server, 0 ); board_draw( cGlobals->game.board ); @@ -2298,25 +2307,25 @@ handle_sigintterm( int XP_UNUSED(sig) ) gtk_main_quit(); } -int -board_main( LaunchParams* params ) -{ - GtkAppGlobals globals; - initGlobals( &globals, params ); +/* int */ +/* board_main( LaunchParams* params ) */ +/* { */ +/* GtkAppGlobals globals; */ +/* initGlobals( &globals, params ); */ - if ( !!params->pipe && !!params->fileName ) { - read_pipe_then_close( &globals.cGlobals, NULL ); - } else { - gtk_widget_show( globals.window ); +/* if ( !!params->pipe && !!params->fileName ) { */ +/* read_pipe_then_close( &globals.cGlobals, NULL ); */ +/* } else { */ +/* gtk_widget_show( globals.window ); */ - gtk_main(); - } - /* MONCONTROL(1); */ +/* gtk_main(); */ +/* } */ +/* /\* MONCONTROL(1); *\/ */ - cleanup( &globals ); +/* cleanup( &globals ); */ - return 0; -} /* gtkmain */ +/* return 0; */ +/* } */ void initGlobals( GtkAppGlobals* globals, LaunchParams* params ) @@ -2379,7 +2388,7 @@ initGlobals( GtkAppGlobals* globals, LaunchParams* params ) gtk_widget_show( vbox ); g_signal_connect( G_OBJECT (window), "destroy", - G_CALLBACK( quit ), &globals ); + G_CALLBACK( destroy_window ), globals ); menubar = makeMenus( globals ); gtk_box_pack_start( GTK_BOX(vbox), menubar, FALSE, TRUE, 0); @@ -2387,7 +2396,7 @@ initGlobals( GtkAppGlobals* globals, LaunchParams* params ) #ifndef XWFEATURE_STANDALONE_ONLY dropCheck = gtk_check_button_new_with_label( "drop incoming messages" ); g_signal_connect( GTK_OBJECT(dropCheck), - "toggled", G_CALLBACK(drop_msg_toggle), &globals ); + "toggled", G_CALLBACK(drop_msg_toggle), globals ); gtk_box_pack_start( GTK_BOX(vbox), dropCheck, FALSE, TRUE, 0); gtk_widget_show( dropCheck ); #endif @@ -2479,7 +2488,21 @@ freeGlobals( GtkAppGlobals* globals ) XP_Bool makeNewGame( GtkAppGlobals* globals ) { - return new_game_impl( globals, XP_FALSE ); + CommsAddrRec addr; + if ( !!globals->cGlobals.game.comms ) { + comms_getAddr( globals->cGlobals.game.comms, &addr ); + } else { + comms_getInitialAddr( &addr, RELAY_NAME_DEFAULT, RELAY_PORT_DEFAULT ); + } + + CurGameInfo* gi = &globals->cGlobals.params->gi; + XP_Bool success = newGameDialog( globals, gi, &addr, XP_TRUE, XP_FALSE ); + if ( success && !!gi->dictName && !globals->cGlobals.params->dict ) { + globals->cGlobals.params->dict = + linux_dictionary_make( MEMPOOL globals->cGlobals.params, + gi->dictName, XP_TRUE ); + } + return success; } #endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index 842557aef..4560e3641 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -25,22 +25,20 @@ #include "gtkboard.h" #include "linuxmain.h" -enum { NAME_ITEM, N_ITEMS }; +enum { ROW_ITEM, N_ITEMS }; typedef struct _GTKGamesGlobals { sqlite3* pDb; - GtkAppGlobals globals; LaunchParams* params; } GTKGamesGlobals; - static void init_games_list( GtkWidget* list ) { GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes( "Games", renderer, - "text", NAME_ITEM, NULL ); + "row", ROW_ITEM, NULL ); gtk_tree_view_append_column( GTK_TREE_VIEW(list), column ); GtkListStore* store = gtk_list_store_new( N_ITEMS, G_TYPE_STRING ); gtk_tree_view_set_model( GTK_TREE_VIEW(list), GTK_TREE_MODEL(store) ); @@ -53,33 +51,66 @@ handle_newgame_button( GtkWidget* XP_UNUSED(widget), void* closure ) GTKGamesGlobals* gg = (GTKGamesGlobals*)closure; XP_LOGF( "%s called", __func__ ); GtkAppGlobals* globals = malloc( sizeof(*globals) ); + gg->params->needsNewGame = XP_FALSE; initGlobals( globals, gg->params ); if ( !makeNewGame( globals ) ) { freeGlobals( globals ); } else { GtkWidget* gameWindow = globals->window; + globals->cGlobals.pDb = gg->pDb; gtk_widget_show( gameWindow ); } } +static void +handle_quit_button( GtkWidget* XP_UNUSED(widget), gpointer data ) +{ + GTKGamesGlobals* gg = (GTKGamesGlobals*)data; + gg = gg; + gtk_main_quit(); +} + +static void +handle_destroy( GtkWidget* XP_UNUSED(widget), gpointer data ) +{ + LOG_FUNC(); + GTKGamesGlobals* gg = (GTKGamesGlobals*)data; + gg = gg; + gtk_main_quit(); +} + + static GtkWidget* makeGamesWindow( GTKGamesGlobals* gg ) { GtkWidget* window; window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); - + g_signal_connect( G_OBJECT(window), "destroy", + G_CALLBACK(handle_destroy), gg ); + GtkWidget* vbox = gtk_vbox_new( FALSE, 0 ); gtk_container_add( GTK_CONTAINER(window), vbox ); gtk_widget_show( vbox ); GtkWidget* list = gtk_tree_view_new(); gtk_container_add( GTK_CONTAINER(vbox), list ); init_games_list( list ); + gtk_widget_show( list ); + + GtkWidget* hbox = gtk_hbox_new( FALSE, 0 ); + gtk_widget_show( hbox ); + gtk_container_add( GTK_CONTAINER(vbox), hbox ); GtkWidget* button = gtk_button_new_with_label( "New Game" ); - gtk_container_add( GTK_CONTAINER(vbox), button ); + gtk_container_add( GTK_CONTAINER(hbox), button ); g_signal_connect( GTK_OBJECT(button), "clicked", G_CALLBACK(handle_newgame_button), gg ); gtk_widget_show( button ); + + button = gtk_button_new_with_label( "Quit" ); + gtk_container_add( GTK_CONTAINER(hbox), button ); + g_signal_connect( GTK_OBJECT(button), "clicked", + G_CALLBACK(handle_quit_button), gg ); + gtk_widget_show( button ); gtk_widget_show( window ); return window; diff --git a/xwords4/linux/gtknewgame.c b/xwords4/linux/gtknewgame.c index b9f6fc04d..d5acd03cd 100644 --- a/xwords4/linux/gtknewgame.c +++ b/xwords4/linux/gtknewgame.c @@ -1,6 +1,6 @@ -/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE"; -*- */ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ /* - * Copyright 2001-2008 by Eric House (xwords@eehouse.org). All rights + * Copyright 2001-2013 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or @@ -33,6 +33,7 @@ typedef struct GtkNewGameState { GtkAppGlobals* globals; + CurGameInfo* gi; NewGameCtx* newGameCtxt; CommsAddrRec addr; @@ -248,7 +249,7 @@ makeNewGameDialog( GtkNewGameState* state ) nPlayersCombo = gtk_combo_box_new_text(); state->nPlayersCombo = nPlayersCombo; - gi = &state->globals->cGlobals.params->gi; + gi = state->gi; for ( ii = 0; ii < MAX_NUM_PLAYERS; ++ii ) { char buf[2] = { ii + '1', '\0' }; @@ -352,16 +353,21 @@ makeNewGameDialog( GtkNewGameState* state ) gtk_box_pack_start( GTK_BOX(hbox), dictCombo, FALSE, TRUE, 0 ); GSList* dicts = listDicts( state->globals->cGlobals.params ); - for ( GSList* iter = dicts; !!iter; iter = iter->next ) { - gtk_combo_box_append_text( GTK_COMBO_BOX(dictCombo), iter->data ); + GSList* iter; + for ( iter = dicts, ii = 0; !!iter; iter = iter->next, ++ii ) { + const gchar* name = iter->data; + gtk_combo_box_append_text( GTK_COMBO_BOX(dictCombo), name ); + if ( !!gi->dictName && !strcmp( name, gi->dictName ) ) { + gtk_combo_box_set_active( GTK_COMBO_BOX(dictCombo), ii ); + } } g_slist_free( dicts ); - if ( !!gi->dictName ) { - gtk_box_pack_start( GTK_BOX(hbox), - gtk_label_new(gi->dictName), - FALSE, TRUE, 0 ); - } + /* if ( !!gi->dictName ) { */ + /* gtk_box_pack_start( GTK_BOX(hbox), */ + /* gtk_label_new(gi->dictName), */ + /* FALSE, TRUE, 0 ); */ + /* } */ gtk_widget_show( hbox ); @@ -538,13 +544,14 @@ gtk_newgame_attr_set( void* closure, NewGameAttr attr, NGValue value ) } gboolean -newGameDialog( GtkAppGlobals* globals, CommsAddrRec* addr, XP_Bool isNewGame, - XP_Bool fireConnDlg ) +newGameDialog( GtkAppGlobals* globals, CurGameInfo* gi, CommsAddrRec* addr, + XP_Bool isNewGame, XP_Bool fireConnDlg ) { GtkNewGameState state; XP_MEMSET( &state, 0, sizeof(state) ); state.globals = globals; + state.gi = gi; state.newGameCtxt = newg_make( MPPARM(globals->cGlobals.params ->util->mpool) isNewGame, @@ -564,25 +571,22 @@ newGameDialog( GtkAppGlobals* globals, CommsAddrRec* addr, XP_Bool isNewGame, state.revert = FALSE; state.loaded = XP_FALSE; - state.nCols = globals->cGlobals.params->gi.boardSize; - state.role = globals->cGlobals.params->gi.serverRole; + state.nCols = gi->boardSize; + state.role = gi->serverRole; XP_MEMCPY( &state.addr, addr, sizeof(state.addr) ); dialog = makeNewGameDialog( &state ); - newg_load( state.newGameCtxt, - &globals->cGlobals.params->gi ); + newg_load( state.newGameCtxt, gi ); state.loaded = XP_TRUE; gtk_main(); if ( !state.cancelled && !state.revert ) { - if ( newg_store( state.newGameCtxt, &globals->cGlobals.params->gi, - XP_TRUE ) ) { - globals->cGlobals.params->gi.boardSize = state.nCols; + if ( newg_store( state.newGameCtxt, gi, XP_TRUE ) ) { + gi->boardSize = state.nCols; replaceStringIfDifferent( globals->cGlobals.params->util->mpool, - &globals->cGlobals.params->gi.dictName, - state.dict ); + &gi->dictName, state.dict ); } else { /* Do it again if we warned user of inconsistency. */ state.revert = XP_TRUE; diff --git a/xwords4/linux/gtknewgame.h b/xwords4/linux/gtknewgame.h index 3a97bd2ac..dad9e6074 100644 --- a/xwords4/linux/gtknewgame.h +++ b/xwords4/linux/gtknewgame.h @@ -1,6 +1,6 @@ -/* -*- compile-command: "make MEMDEBUG=TRUE"; -*- */ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ /* - * Copyright 2000-2009 by Eric House (xwords@eehouse.org). All rights + * Copyright 2000-2013 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or @@ -26,8 +26,9 @@ #include "gtkboard.h" -gboolean newGameDialog( GtkAppGlobals* globals, CommsAddrRec* addr, - XP_Bool isNewGame, XP_Bool fireConnDlg ); +gboolean newGameDialog( GtkAppGlobals* globals, CurGameInfo* gi, + CommsAddrRec* addr, XP_Bool isNewGame, + XP_Bool fireConnDlg ); #endif /* _GTKNEWGAME_H_ */ #endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 81bb34651..bbb4cd3e1 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -57,6 +57,7 @@ #include "linuxudp.h" #include "dictiter.h" #include "main.h" +#include "gamesdb.h" #ifdef PLATFORM_NCURSES # include "cursesmain.h" #endif @@ -130,7 +131,8 @@ streamFromDB( CommonGlobals* cGlobals, void* closure ) XP_U8 buf[size]; res = sqlite3_blob_read( ppBlob, buf, size, 0 ); if ( SQLITE_OK == res ) { - stream = mem_stream_make( MPPARM(params->util->mpool) params->vtMgr, + stream = mem_stream_make( MPPARM(params->util->mpool) + params->vtMgr, closure, CHANNEL_NONE, NULL ); stream_putBytes( stream, buf, size ); } @@ -255,23 +257,26 @@ strFromStream( XWStreamCtxt* stream ) void saveGame( CommonGlobals* cGlobals ) { - if ( !!cGlobals->params->fileName ) { + LOG_FUNC(); + if ( !!cGlobals->params->fileName || !!cGlobals->pDb ) { XP_Bool doSave = XP_TRUE; - if ( 0 < cGlobals->params->saveFailPct - /* don't fail to save first time! */ - && file_exists( cGlobals->params->fileName ) ) { + XP_Bool newGame = !file_exists( cGlobals->params->fileName ) + || 0 == cGlobals->rowid; + /* don't fail to save first time! */ + if ( 0 < cGlobals->params->saveFailPct && !newGame ) { XP_U16 pct = XP_RANDOM() % 100; doSave = pct >= cGlobals->params->saveFailPct; } if ( doSave ) { XWStreamCtxt* outStream; - + MemStreamCloseCallback onClose = !!cGlobals->pDb? + writeToDB : writeToFile; outStream = mem_stream_make_sized( MPPARM(cGlobals->params->util->mpool) cGlobals->params->vtMgr, cGlobals->lastStreamSize, - cGlobals, 0, writeToFile ); + cGlobals, 0, onClose ); stream_open( outStream ); game_saveToStream( &cGlobals->game, @@ -1507,14 +1512,13 @@ listDicts( const LaunchParams *params ) return result; } -void +static void initParams( LaunchParams* params ) { memset( params, 0, sizeof(*params) ); - params->util = malloc( sizeof(params->util) ); - - XP_MEMSET( params->util, 0, sizeof(params->util) ); + params->util = calloc( 1, sizeof(*params->util) ); + /* XP_MEMSET( params->util, 0, sizeof(params->util) ); */ #ifdef MEM_DEBUG params->util->mpool = mpool_make(); @@ -1529,11 +1533,11 @@ initParams( LaunchParams* params ) #endif } -void +static void freeParams( LaunchParams* params ) { - vtmgr_destroy( MPPARM(params->util->mpool) params->vtMgr ); linux_util_vt_destroy( params->util ); + vtmgr_destroy( MPPARM(params->util->mpool) params->vtMgr ); mpool_destroy( params->util->mpool ); @@ -1596,8 +1600,6 @@ main( int argc, char** argv ) #endif initParams( &mainParams ); - /* fprintf( stdout, "press to start\n" ); */ - /* (void)fgetc( stdin ); */ /* defaults */ #ifdef XWFEATURE_RELAY diff --git a/xwords4/linux/linuxmain.h b/xwords4/linux/linuxmain.h index e4d4eb526..f6cb25219 100644 --- a/xwords4/linux/linuxmain.h +++ b/xwords4/linux/linuxmain.h @@ -94,7 +94,7 @@ void setOneSecondTimer( CommonGlobals* cGlobals ); # define setOneSecondTimer( cGlobals ) #endif -void initParams( LaunchParams* params ); -void freeParams( LaunchParams* params ); +/* void initParams( LaunchParams* params ); */ +/* void freeParams( LaunchParams* params ); */ #endif diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index 028ea0b0a..ecf34680d 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -25,6 +25,8 @@ # include /* for bdaddr_t, which should move */ #endif +#include + #include "comtypes.h" #include "util.h" #include "game.h" @@ -167,9 +169,11 @@ struct CommonGlobals { CommonPrefs cp; XWGame game; + sqlite3_int64 rowid; XP_U16 lastNTilesToUse; XP_U16 lastStreamSize; XP_Bool manualFinal; /* use asked for final scores */ + sqlite3* pDb; SocketChangedFunc socketChanged; void* socketChangedClosure; From 8238d45350ff5533f76ab624d0c573d8259f2819 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 5 Jan 2013 19:37:05 -0800 Subject: [PATCH 003/116] ignore the db --- xwords4/linux/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/xwords4/linux/.gitignore b/xwords4/linux/.gitignore index 767e1d9ae..b77239cb6 100644 --- a/xwords4/linux/.gitignore +++ b/xwords4/linux/.gitignore @@ -6,3 +6,4 @@ obj_linux_rel log_*_*.txt discon_ok2.sh_logs test_backsend.sh_* +games.db From a8014a855bc511f56846cd02b150e34b12065e17 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 5 Jan 2013 19:40:29 -0800 Subject: [PATCH 004/116] now games are displayed in lists. Next: open them. --- xwords4/linux/gamesdb.c | 40 +++++++++++++++++++++++++++++++++ xwords4/linux/gamesdb.h | 8 +++++++ xwords4/linux/gtkaskdict.c | 4 ++-- xwords4/linux/gtkmain.c | 46 ++++++++++++++++++++++++++++++-------- xwords4/linux/main.h | 5 +++++ 5 files changed, 92 insertions(+), 11 deletions(-) diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c index 105d45999..de8589ae3 100644 --- a/xwords4/linux/gamesdb.c +++ b/xwords4/linux/gamesdb.c @@ -88,3 +88,43 @@ writeToDB( XWStreamCtxt* stream, void* closure ) sqlite3_finalize( stmt ); } } + +GSList* +listGames( GTKGamesGlobals* gg ) +{ + GSList* list = NULL; + + sqlite3_stmt *ppStmt; + int result = sqlite3_prepare_v2( gg->pDb, + "SELECT rowid FROM games ORDER BY rowid", -1, + &ppStmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + while ( NULL != ppStmt ) { + switch( sqlite3_step( ppStmt ) ) { + case SQLITE_ROW: /* have data */ + { + sqlite3_int64* data = g_malloc( sizeof( *data ) ); + *data = sqlite3_column_int64( ppStmt, 0 ); + XP_LOGF( "%s: got a row; id=%lld", __func__, *data ); + list = g_slist_append( list, data ); + } + break; + case SQLITE_DONE: + sqlite3_finalize( ppStmt ); + ppStmt = NULL; + break; + default: + XP_ASSERT( 0 ); + break; + } + } + return list; +} + +void +getGameName( GTKGamesGlobals* XP_UNUSED(gg), const sqlite3_int64* rowid, + XP_UCHAR* buf, XP_U16 len ) +{ + snprintf( buf, len, "Game %lld", *rowid ); + LOG_RETURNF( "%s", buf ); +} diff --git a/xwords4/linux/gamesdb.h b/xwords4/linux/gamesdb.h index 5a7c6c15b..5a6a4d067 100644 --- a/xwords4/linux/gamesdb.h +++ b/xwords4/linux/gamesdb.h @@ -22,7 +22,9 @@ #define _GAMESDB_H_ #include +#include +#include "main.h" #include "comtypes.h" sqlite3* openGamesDB( void ); @@ -30,4 +32,10 @@ void closeGamesDB( sqlite3* dbp ); void writeToDB( XWStreamCtxt* stream, void* closure ); +/* Return GSList whose data is (ptrs to) rowids */ +GSList* listGames( GTKGamesGlobals* gg ); +void getGameName( GTKGamesGlobals* gg, const sqlite3_int64* rowid, + XP_UCHAR* buf, XP_U16 len ); + + #endif diff --git a/xwords4/linux/gtkaskdict.c b/xwords4/linux/gtkaskdict.c index 565c662c3..d635d595b 100644 --- a/xwords4/linux/gtkaskdict.c +++ b/xwords4/linux/gtkaskdict.c @@ -41,9 +41,9 @@ init_list( GtkWidget* list ) } static void -add_to_list( GtkWidget *list, const gchar *str ) +add_to_list( GtkWidget* list, const gchar* str ) { - GtkListStore *store = + GtkListStore* store = GTK_LIST_STORE( gtk_tree_view_get_model(GTK_TREE_VIEW(list))); GtkTreeIter iter; gtk_list_store_append( store, &iter ); diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index 4560e3641..dfcc374f8 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -25,26 +25,45 @@ #include "gtkboard.h" #include "linuxmain.h" -enum { ROW_ITEM, N_ITEMS }; - -typedef struct _GTKGamesGlobals { - sqlite3* pDb; - LaunchParams* params; -} GTKGamesGlobals; +enum { ROW_ITEM, NAME_ITEM, N_ITEMS }; static void init_games_list( GtkWidget* list ) { GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); GtkTreeViewColumn* column = - gtk_tree_view_column_new_with_attributes( "Games", renderer, - "row", ROW_ITEM, NULL ); + gtk_tree_view_column_new_with_attributes( "Row", renderer, "text", + ROW_ITEM, NULL ); gtk_tree_view_append_column( GTK_TREE_VIEW(list), column ); - GtkListStore* store = gtk_list_store_new( N_ITEMS, G_TYPE_STRING ); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes( "Name", renderer, "text", + NAME_ITEM, NULL ); + gtk_tree_view_append_column( GTK_TREE_VIEW(list), column ); + + GtkListStore* store = gtk_list_store_new( N_ITEMS, // G_TYPE_INT64, + G_TYPE_STRING, G_TYPE_STRING ); gtk_tree_view_set_model( GTK_TREE_VIEW(list), GTK_TREE_MODEL(store) ); g_object_unref( store ); } +static void +add_to_list( GtkWidget* list, sqlite3_int64* rowid, const gchar* str ) +{ + GtkListStore* store = + GTK_LIST_STORE( gtk_tree_view_get_model(GTK_TREE_VIEW(list))); + GtkTreeIter iter; + gtk_list_store_append( store, &iter ); + XP_LOGF( "adding %lld, %s", *rowid, str ); + gchar buf[16]; + snprintf( buf, sizeof(buf), "%lld", *rowid ); + gtk_list_store_set( store, &iter, + ROW_ITEM, buf, + NAME_ITEM, str, + -1 ); + XP_LOGF( "DONE adding" ); +} + static void handle_newgame_button( GtkWidget* XP_UNUSED(widget), void* closure ) { @@ -97,6 +116,15 @@ makeGamesWindow( GTKGamesGlobals* gg ) init_games_list( list ); gtk_widget_show( list ); + GSList* games = listGames( gg ); + for ( GSList* iter = games; !!iter; iter = iter->next ) { + XP_UCHAR name[128]; + sqlite3_int64* rowid = (sqlite3_int64*)iter->data; + getGameName( gg, rowid, name, VSIZE(name) ); + add_to_list( list, rowid, name ); + } + g_slist_free( games ); + GtkWidget* hbox = gtk_hbox_new( FALSE, 0 ); gtk_widget_show( hbox ); gtk_container_add( GTK_CONTAINER(vbox), hbox ); diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index ecf34680d..bb8af4fa0 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -209,4 +209,9 @@ struct CommonGlobals { XP_U16 curSaveToken; }; +typedef struct _GTKGamesGlobals { + sqlite3* pDb; + LaunchParams* params; +} GTKGamesGlobals; + #endif From b61ed7b631b596f7c87c944b781df43835da6355 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 5 Jan 2013 21:01:26 -0800 Subject: [PATCH 005/116] opening saved games and saving of changes now work. --- xwords4/linux/gamesdb.c | 54 +++++++++++++++++++++++++++---------- xwords4/linux/gamesdb.h | 2 +- xwords4/linux/gtkboard.c | 18 ++++++++++--- xwords4/linux/gtkmain.c | 56 ++++++++++++++++++++++++++++++++++++--- xwords4/linux/linuxmain.c | 2 +- xwords4/linux/main.h | 3 ++- 6 files changed, 111 insertions(+), 24 deletions(-) diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c index de8589ae3..dedbdc525 100644 --- a/xwords4/linux/gamesdb.c +++ b/xwords4/linux/gamesdb.c @@ -55,29 +55,37 @@ writeToDB( XWStreamCtxt* stream, void* closure ) { int result; CommonGlobals* cGlobals = (CommonGlobals*)closure; - sqlite3_int64 rowid = cGlobals->rowid; + sqlite3_int64 selRow = cGlobals->selRow; sqlite3* pDb = cGlobals->pDb; XP_U16 len = stream_getSize( stream ); + char buf[256]; + char* query; sqlite3_stmt* stmt = NULL; - if ( 0 == rowid ) { /* new row; need to insert blob first */ - const char* txt = "INSERT INTO games (game) VALUES (?)"; - result = sqlite3_prepare_v2( pDb, txt, -1, &stmt, NULL ); - XP_ASSERT( SQLITE_OK == result ); - result = sqlite3_bind_zeroblob( stmt, 1 /*col 0 ??*/, len ); - XP_ASSERT( SQLITE_OK == result ); - result = sqlite3_step( stmt ); - XP_ASSERT( SQLITE_DONE == result ); + if ( -1 == selRow ) { /* new row; need to insert blob first */ + query = "INSERT INTO games (game) VALUES (?)"; + } else { + const char* fmt = "UPDATE games SET game=? where rowid=%lld"; + snprintf( buf, sizeof(buf), fmt, selRow ); + query = buf; + } - rowid = sqlite3_last_insert_rowid( pDb ); - XP_LOGF( "%s: new rowid: %lld", __func__, rowid ); - cGlobals->rowid = rowid; - sqlite3_finalize( stmt ); + result = sqlite3_prepare_v2( pDb, query, -1, &stmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_bind_zeroblob( stmt, 1 /*col 0 ??*/, len ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( stmt ); + XP_ASSERT( SQLITE_DONE == result ); + + if ( -1 == selRow ) { /* new row; need to insert blob first */ + selRow = sqlite3_last_insert_rowid( pDb ); + XP_LOGF( "%s: new rowid: %lld", __func__, selRow ); + cGlobals->selRow = selRow; } sqlite3_blob* blob; result = sqlite3_blob_open( pDb, "main", "games", "game", - rowid, 1 /*flags: writeable*/, &blob ); + selRow, 1 /*flags: writeable*/, &blob ); XP_ASSERT( SQLITE_OK == result ); const XP_U8* ptr = stream_getPtr( stream ); result = sqlite3_blob_write( blob, ptr, len, 0 ); @@ -128,3 +136,21 @@ getGameName( GTKGamesGlobals* XP_UNUSED(gg), const sqlite3_int64* rowid, snprintf( buf, len, "Game %lld", *rowid ); LOG_RETURNF( "%s", buf ); } + +XP_Bool +loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ) +{ + char buf[256]; + snprintf( buf, sizeof(buf), "SELECT game from games WHERE rowid = %lld", rowid ); + + sqlite3_stmt *ppStmt; + int result = sqlite3_prepare_v2( pDb, buf, -1, &ppStmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( ppStmt ); + XP_ASSERT( SQLITE_ROW == result ); + const void* ptr = sqlite3_column_blob( ppStmt, 0 ); + int size = sqlite3_column_bytes( ppStmt, 0 ); + stream_putBytes( stream, ptr, size ); + sqlite3_finalize( ppStmt ); + return XP_TRUE; +} diff --git a/xwords4/linux/gamesdb.h b/xwords4/linux/gamesdb.h index 5a6a4d067..bbda321f4 100644 --- a/xwords4/linux/gamesdb.h +++ b/xwords4/linux/gamesdb.h @@ -36,6 +36,6 @@ void writeToDB( XWStreamCtxt* stream, void* closure ); GSList* listGames( GTKGamesGlobals* gg ); void getGameName( GTKGamesGlobals* gg, const sqlite3_int64* rowid, XP_UCHAR* buf, XP_U16 len ); - +XP_Bool loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ); #endif diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 87267d110..2d9b6ceb7 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -63,6 +63,7 @@ #include "gtkdraw.h" #include "memstream.h" #include "filestream.h" +#include "gamesdb.h" /* static guint gtkSetupClientSocket( GtkAppGlobals* globals, int sock ); */ static void setCtrlsForTray( GtkAppGlobals* globals ); @@ -396,7 +397,8 @@ createOrLoadObjects( GtkAppGlobals* globals ) #ifndef XWFEATURE_STANDALONE_ONLY #endif - LaunchParams* params = globals->cGlobals.params; + CommonGlobals* cGlobals = &globals->cGlobals; + LaunchParams* params = cGlobals->params; globals->draw = (GtkDrawCtx*)gtkDrawCtxtMake( globals->drawing_area, globals ); @@ -415,11 +417,19 @@ createOrLoadObjects( GtkAppGlobals* globals ) }; if ( !!params->fileName && file_exists( params->fileName ) ) { - stream = streamFromFile( &globals->cGlobals, params->fileName, globals ); + stream = streamFromFile( cGlobals, params->fileName, globals ); #ifdef USE_SQLITE } else if ( !!params->dbFileName && file_exists( params->dbFileName ) ) { - stream = streamFromDB( &globals->cGlobals, globals ); + stream = streamFromDB( cGlobals, globals ); #endif + } else if ( !!cGlobals->pDb && 0 <= cGlobals->selRow ) { + stream = mem_stream_make( MPPARM(params->util->mpool) + params->vtMgr, + cGlobals, CHANNEL_NONE, NULL ); + if ( !loadGame( stream, cGlobals->pDb, cGlobals->selRow ) ) { + stream_destroy( stream ); + stream = NULL; + } } if ( !!stream ) { @@ -2501,6 +2511,8 @@ makeNewGame( GtkAppGlobals* globals ) globals->cGlobals.params->dict = linux_dictionary_make( MEMPOOL globals->cGlobals.params, gi->dictName, XP_TRUE ); + } else { + success = XP_FALSE; } return success; } diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index dfcc374f8..1952ddbb3 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -27,9 +27,29 @@ enum { ROW_ITEM, NAME_ITEM, N_ITEMS }; -static void -init_games_list( GtkWidget* list ) +/* Prototype for selection handler callback */ +static void +tree_selection_changed_cb( GtkTreeSelection* selection, gpointer data ) { + LOG_FUNC(); + GTKGamesGlobals* gg = (GTKGamesGlobals*)data; + + GtkTreeIter iter; + GtkTreeModel *model; + gchar *row; + + if ( gtk_tree_selection_get_selected( selection, &model, &iter ) ) { + gtk_tree_model_get( model, &iter, ROW_ITEM, &row, -1 ); + sscanf( row, "%lld", &gg->selRow ); + g_print ("You selected row %s (parsed: %lld)\n", row, gg->selRow ); + g_free( row ); + } +} + +static GtkWidget* +init_games_list( GTKGamesGlobals* gg ) +{ + GtkWidget* list = gtk_tree_view_new(); GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes( "Row", renderer, "text", @@ -45,6 +65,13 @@ init_games_list( GtkWidget* list ) G_TYPE_STRING, G_TYPE_STRING ); gtk_tree_view_set_model( GTK_TREE_VIEW(list), GTK_TREE_MODEL(store) ); g_object_unref( store ); + + GtkTreeSelection* select = + gtk_tree_view_get_selection( GTK_TREE_VIEW (list) ); + gtk_tree_selection_set_mode( select, GTK_SELECTION_SINGLE ); + g_signal_connect( G_OBJECT(select), "changed", + G_CALLBACK (tree_selection_changed_cb), gg ); + return list; } static void @@ -77,10 +104,25 @@ handle_newgame_button( GtkWidget* XP_UNUSED(widget), void* closure ) } else { GtkWidget* gameWindow = globals->window; globals->cGlobals.pDb = gg->pDb; + globals->cGlobals.selRow = -1; gtk_widget_show( gameWindow ); } } +static void +handle_open_button( GtkWidget* XP_UNUSED(widget), void* closure ) +{ + GTKGamesGlobals* gg = (GTKGamesGlobals*)closure; + if ( -1 != gg->selRow ) { + gg->params->needsNewGame = XP_FALSE; + GtkAppGlobals* globals = malloc( sizeof(*globals) ); + initGlobals( globals, gg->params ); + globals->cGlobals.pDb = gg->pDb; + globals->cGlobals.selRow = gg->selRow; + gtk_widget_show( globals->window ); + } +} + static void handle_quit_button( GtkWidget* XP_UNUSED(widget), gpointer data ) { @@ -111,9 +153,9 @@ makeGamesWindow( GTKGamesGlobals* gg ) GtkWidget* vbox = gtk_vbox_new( FALSE, 0 ); gtk_container_add( GTK_CONTAINER(window), vbox ); gtk_widget_show( vbox ); - GtkWidget* list = gtk_tree_view_new(); + GtkWidget* list = init_games_list( gg ); gtk_container_add( GTK_CONTAINER(vbox), list ); - init_games_list( list ); + gtk_widget_show( list ); GSList* games = listGames( gg ); @@ -134,6 +176,11 @@ makeGamesWindow( GTKGamesGlobals* gg ) G_CALLBACK(handle_newgame_button), gg ); gtk_widget_show( button ); + button = gtk_button_new_with_label( "Open" ); + gtk_container_add( GTK_CONTAINER(hbox), button ); + g_signal_connect( GTK_OBJECT(button), "clicked", + G_CALLBACK(handle_open_button), gg ); + gtk_widget_show( button ); button = gtk_button_new_with_label( "Quit" ); gtk_container_add( GTK_CONTAINER(hbox), button ); g_signal_connect( GTK_OBJECT(button), "clicked", @@ -148,6 +195,7 @@ int gtkmain( LaunchParams* params ) { GTKGamesGlobals gg = {0}; + gg.selRow = -1; gg.params = params; XP_LOGF( "%s: I'M HERE!!! (calling makeGamesDB())", __func__ ); gg.pDb = openGamesDB(); diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index bbb4cd3e1..9392a4620 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -261,7 +261,7 @@ saveGame( CommonGlobals* cGlobals ) if ( !!cGlobals->params->fileName || !!cGlobals->pDb ) { XP_Bool doSave = XP_TRUE; XP_Bool newGame = !file_exists( cGlobals->params->fileName ) - || 0 == cGlobals->rowid; + || -1 == cGlobals->selRow; /* don't fail to save first time! */ if ( 0 < cGlobals->params->saveFailPct && !newGame ) { XP_U16 pct = XP_RANDOM() % 100; diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index bb8af4fa0..0c63eee7f 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -169,11 +169,11 @@ struct CommonGlobals { CommonPrefs cp; XWGame game; - sqlite3_int64 rowid; XP_U16 lastNTilesToUse; XP_U16 lastStreamSize; XP_Bool manualFinal; /* use asked for final scores */ sqlite3* pDb; + sqlite3_int64 selRow; SocketChangedFunc socketChanged; void* socketChangedClosure; @@ -211,6 +211,7 @@ struct CommonGlobals { typedef struct _GTKGamesGlobals { sqlite3* pDb; + sqlite3_int64 selRow; LaunchParams* params; } GTKGamesGlobals; From f88387da8029e1682dd03bd1c51da2ff9bcf9435 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 7 Jan 2013 06:10:44 -0800 Subject: [PATCH 006/116] lots of changes to separate game data from app data so several games can be open at once. (curses version is almost certainly broken) --- xwords4/linux/cursesmain.c | 43 ++++---- xwords4/linux/gtkboard.c | 120 ++++++++++++--------- xwords4/linux/gtkboard.h | 2 +- xwords4/linux/gtkmain.c | 50 ++++++--- xwords4/linux/gtkmain.h | 6 +- xwords4/linux/gtknewgame.c | 7 +- xwords4/linux/linuxbt.c | 4 +- xwords4/linux/linuxmain.c | 211 +++++++++++++++++++------------------ xwords4/linux/linuxmain.h | 2 + xwords4/linux/linuxsms.c | 6 +- xwords4/linux/linuxutl.c | 7 +- xwords4/linux/main.h | 13 ++- 12 files changed, 264 insertions(+), 207 deletions(-) diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index bd442f884..7107d9126 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -206,7 +206,7 @@ static CursesMenuHandler getHandlerForKey( const MenuList* list, char ch ); #ifdef MEM_DEBUG -# define MEMPOOL params->util->mpool, +# define MEMPOOL cGlobals->util->mpool, #else # define MEMPOOL #endif @@ -235,7 +235,7 @@ curses_util_userPickTileBlank( XW_UtilCtxt* uc, XP_U16 playerNum, CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; char query[128]; XP_S16 index; - char* playerName = globals->cGlobals.params->gi.players[playerNum].name; + char* playerName = globals->cGlobals.gi.players[playerNum].name; snprintf( query, sizeof(query), "Pick tile for %s! (Tab or type letter to select " @@ -253,7 +253,7 @@ curses_util_userPickTileTray( XW_UtilCtxt* uc, const PickInfo* XP_UNUSED(pi), CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; char query[128]; XP_S16 index; - char* playerName = globals->cGlobals.params->gi.players[playerNum].name; + char* playerName = globals->cGlobals.gi.players[playerNum].name; snprintf( query, sizeof(query), "Pick tile for %s! (Tab or type letter to select " @@ -341,7 +341,7 @@ cursesShowFinalScores( CursesAppGlobals* globals ) XWStreamCtxt* stream; XP_UCHAR* text; - stream = mem_stream_make( MPPARM(globals->cGlobals.params->util->mpool) + stream = mem_stream_make( MPPARM(globals->cGlobals.util->mpool) globals->cGlobals.params->vtMgr, globals, CHANNEL_NONE, NULL ); server_writeFinalScores( globals->cGlobals.game.server, stream ); @@ -1083,7 +1083,7 @@ data_socket_proc( GIOChannel* source, GIOCondition condition, gpointer data ) that on the screen before giving the server another shot. So just call the idle proc. */ if ( redraw ) { - curses_util_requestTime( globals->cGlobals.params->util ); + curses_util_requestTime( globals->cGlobals.util ); } } } @@ -1477,7 +1477,7 @@ curses_util_remSelected( XW_UtilCtxt* uc ) XWStreamCtxt* stream; XP_UCHAR* text; - stream = mem_stream_make( MPPARM(globals->cGlobals.params->util->mpool) + stream = mem_stream_make( MPPARM(globals->cGlobals.util->mpool) globals->cGlobals.params->vtMgr, globals, CHANNEL_NONE, NULL ); board_formatRemainingTiles( globals->cGlobals.game.board, stream ); @@ -1581,7 +1581,7 @@ positionSizeStuff( CursesAppGlobals* globals, int width, int height ) XP_U16 cellWidth, cellHt, scoreLeft, scoreWidth; BoardCtxt* board = globals->cGlobals.game.board; int remWidth = width; - int nRows = globals->cGlobals.params->gi.boardSize; + int nRows = globals->cGlobals.gi.boardSize; cellWidth = CURSES_CELL_WIDTH; cellHt = CURSES_CELL_HT; @@ -1693,6 +1693,7 @@ void cursesmain( XP_Bool isServer, LaunchParams* params ) { int width, height; + CommonGlobals* cGlobals = &g_globals.cGlobals; memset( &g_globals, 0, sizeof(g_globals) ); @@ -1723,7 +1724,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) g_globals.cGlobals.cp.robotTradePct = params->robotTradePct; #endif - setupCursesUtilCallbacks( &g_globals, params->util ); + setupCursesUtilCallbacks( &g_globals, g_globals.cGlobals.util ); #ifdef XWFEATURE_RELAY if ( params->conType == COMMS_CONN_RELAY ) { @@ -1788,26 +1789,26 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) } if ( !!stream ) { - (void)game_makeFromStream( MEMPOOL stream, &g_globals.cGlobals.game, - ¶ms->gi, params->dict, ¶ms->dicts, - params->util, + (void)game_makeFromStream( MEMPOOL stream, &cGlobals->game, + &cGlobals->gi, cGlobals->dict, &cGlobals->dicts, + cGlobals->util, (DrawCtx*)g_globals.draw, &g_globals.cGlobals.cp, &procs ); stream_destroy( stream ); - if ( !isServer && params->gi.serverRole == SERVER_ISSERVER ) { + if ( !isServer && cGlobals->gi.serverRole == SERVER_ISSERVER ) { isServer = XP_TRUE; } opened = XP_TRUE; } if ( !opened ) { - game_makeNewGame( MEMPOOL &g_globals.cGlobals.game, ¶ms->gi, - params->util, (DrawCtx*)g_globals.draw, + game_makeNewGame( MEMPOOL &cGlobals->game, &cGlobals->gi, + cGlobals->util, (DrawCtx*)g_globals.draw, &g_globals.cGlobals.cp, &procs, params->gameSeed ); } #ifndef XWFEATURE_STANDALONE_ONLY - if ( g_globals.cGlobals.game.comms ) { + if ( cGlobals->game.comms ) { CommsAddrRec addr = {0}; if ( 0 ) { @@ -1839,22 +1840,22 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) sizeof(params->connInfo.bt.hostAddr) ); # endif } - comms_setAddr( g_globals.cGlobals.game.comms, &addr ); + comms_setAddr( cGlobals->game.comms, &addr ); } #endif - model_setDictionary( g_globals.cGlobals.game.model, params->dict ); - setSquareBonuses( &g_globals.cGlobals ); + model_setDictionary( cGlobals->game.model, cGlobals->dict ); + setSquareBonuses( cGlobals ); positionSizeStuff( &g_globals, width, height ); #ifndef XWFEATURE_STANDALONE_ONLY /* send any events that need to get off before the event loop begins */ if ( !isServer ) { if ( 1 /* stream_open( params->info.clientInfo.stream ) */) { - server_initClientConnection( g_globals.cGlobals.game.server, + server_initClientConnection( cGlobals->game.server, mem_stream_make( MEMPOOL params->vtMgr, - &g_globals.cGlobals, + cGlobals, (XP_PlayerAddr)0, sendOnClose ) ); } else { @@ -1896,7 +1897,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) saveGame( &g_globals.cGlobals ); game_dispose( &g_globals.cGlobals.game ); /* takes care of the dict */ - gi_disposePlayerInfo( MEMPOOL &g_globals.cGlobals.params->gi ); + gi_disposePlayerInfo( MEMPOOL &cGlobals->gi ); #ifdef XWFEATURE_BLUETOOTH linux_bt_close( &g_globals.cGlobals ); diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 2d9b6ceb7..60997bbf3 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -48,7 +48,7 @@ #include "linuxbt.h" #include "linuxudp.h" #include "linuxsms.h" -/* #include "gtkmain.h" */ +#include "gtkmain.h" #include "draw.h" #include "game.h" @@ -297,7 +297,7 @@ key_release_event( GtkWidget* XP_UNUSED(widget), GdkEventKey* event, #endif #ifdef MEM_DEBUG -# define MEMPOOL globals->cGlobals.params->util->mpool, +# define MEMPOOL globals->cGlobals.util->mpool, #else # define MEMPOOL #endif @@ -423,7 +423,7 @@ createOrLoadObjects( GtkAppGlobals* globals ) stream = streamFromDB( cGlobals, globals ); #endif } else if ( !!cGlobals->pDb && 0 <= cGlobals->selRow ) { - stream = mem_stream_make( MPPARM(params->util->mpool) + stream = mem_stream_make( MPPARM(cGlobals->util->mpool) params->vtMgr, cGlobals, CHANNEL_NONE, NULL ); if ( !loadGame( stream, cGlobals->pDb, cGlobals->selRow ) ) { @@ -433,12 +433,25 @@ createOrLoadObjects( GtkAppGlobals* globals ) } if ( !!stream ) { - opened = game_makeFromStream( MEMPOOL stream, &globals->cGlobals.game, - &globals->cGlobals.params->gi, - params->dict, ¶ms->dicts, params->util, + if ( NULL == cGlobals->dict ) { + CurGameInfo gi = {0}; + XWStreamPos pos = stream_getPos( stream, POS_READ ); + if ( !game_makeFromStream( MEMPOOL stream, NULL, &gi, NULL, NULL, + NULL, NULL, NULL, NULL ) ) { + XP_ASSERT(0); + } + stream_setPos( stream, POS_READ, pos ); + cGlobals->dict = linux_dictionary_make( MEMPOOL params, gi.dictName, XP_TRUE ); + gi_disposePlayerInfo( MEMPOOL &gi ); + XP_ASSERT( !!cGlobals->dict ); + } + + opened = game_makeFromStream( MEMPOOL stream, &cGlobals->game, + &cGlobals->gi, + cGlobals->dict, &cGlobals->dicts, cGlobals->util, (DrawCtx*)globals->draw, - &globals->cGlobals.cp, &procs ); - + &cGlobals->cp, &procs ); + XP_LOGF( "%s: loaded gi at %p", __func__, &cGlobals->gi ); stream_destroy( stream ); } @@ -455,10 +468,10 @@ createOrLoadObjects( GtkAppGlobals* globals ) = params->connInfo.relay.relayName; } #endif - - game_makeNewGame( MEMPOOL &globals->cGlobals.game, ¶ms->gi, - params->util, (DrawCtx*)globals->draw, - &globals->cGlobals.cp, &procs, params->gameSeed ); + CommonGlobals* cGlobals = &globals->cGlobals; + game_makeNewGame( MEMPOOL &cGlobals->game, &cGlobals->gi, + cGlobals->util, (DrawCtx*)globals->draw, + &cGlobals->cp, &procs, params->gameSeed ); addr.conType = params->conType; if ( 0 ) { @@ -496,16 +509,16 @@ createOrLoadObjects( GtkAppGlobals* globals ) #ifndef XWFEATURE_STANDALONE_ONLY /* This may trigger network activity */ - if ( !!globals->cGlobals.game.comms ) { - comms_setAddr( globals->cGlobals.game.comms, &addr ); + if ( !!cGlobals->game.comms ) { + comms_setAddr( cGlobals->game.comms, &addr ); } #endif - model_setDictionary( globals->cGlobals.game.model, params->dict ); - setSquareBonuses( &globals->cGlobals ); - model_setPlayerDicts( globals->cGlobals.game.model, ¶ms->dicts ); + model_setDictionary( cGlobals->game.model, cGlobals->dict ); + setSquareBonuses( cGlobals ); + model_setPlayerDicts( cGlobals->game.model, &cGlobals->dicts ); #ifdef XWFEATURE_SEARCHLIMIT - params->gi.allowHintRect = params->allowHintRect; + cGlobals->gi.allowHintRect = params->allowHintRect; #endif @@ -513,13 +526,13 @@ createOrLoadObjects( GtkAppGlobals* globals ) new_game_impl( globals, XP_FALSE ); #ifndef XWFEATURE_STANDALONE_ONLY } else { - DeviceRole serverRole = globals->cGlobals.params->gi.serverRole; + DeviceRole serverRole = cGlobals->gi.serverRole; if ( serverRole == SERVER_ISCLIENT ) { XWStreamCtxt* stream = mem_stream_make( MEMPOOL params->vtMgr, - &globals->cGlobals, CHANNEL_NONE, + cGlobals, CHANNEL_NONE, sendOnClose ); - server_initClientConnection( globals->cGlobals.game.server, + server_initClientConnection( cGlobals->game.server, stream ); } #endif @@ -549,13 +562,15 @@ configure_event( GtkWidget* widget, GdkEventConfigure* XP_UNUSED(event), gint trayTop; gint boardTop = 0; XP_U16 netStatWidth = 0; - gint nCols = globals->cGlobals.params->gi.boardSize; - gint nRows = nCols; + gint nCols; + gint nRows; if ( globals->draw == NULL ) { createOrLoadObjects( globals ); } + nCols = globals->cGlobals.gi.boardSize; + nRows = nCols; bdWidth = widget->allocation.width - (GTK_RIGHT_MARGIN + GTK_BOARD_LEFT_MARGIN); if ( globals->cGlobals.params->verticalScore ) { @@ -688,6 +703,7 @@ destroy_window( GtkWidget* XP_UNUSED(widget), gpointer data ) LOG_FUNC(); GtkAppGlobals* globals = (GtkAppGlobals*)data; saveGame( &globals->cGlobals ); + windowDestroyed( globals ); // gtk_main_quit(); } @@ -697,7 +713,7 @@ cleanup( GtkAppGlobals* globals ) saveGame( &globals->cGlobals ); game_dispose( &globals->cGlobals.game ); /* takes care of the dict */ - gi_disposePlayerInfo( MEMPOOL &globals->cGlobals.params->gi ); + gi_disposePlayerInfo( MEMPOOL &globals->cGlobals.gi ); #ifdef XWFEATURE_BLUETOOTH linux_bt_close( &globals->cGlobals ); @@ -711,6 +727,7 @@ cleanup( GtkAppGlobals* globals ) #ifdef XWFEATURE_RELAY linux_close_socket( &globals->cGlobals ); #endif + linux_util_vt_destroy( globals->cGlobals.util ); } /* cleanup */ GtkWidget* @@ -802,7 +819,7 @@ new_game_impl( GtkAppGlobals* globals, XP_Bool fireConnDlg ) comms_getInitialAddr( &addr, RELAY_NAME_DEFAULT, RELAY_PORT_DEFAULT ); } - CurGameInfo* gi = &globals->cGlobals.params->gi; + CurGameInfo* gi = &globals->cGlobals.gi; success = newGameDialog( globals, gi, &addr, XP_TRUE, fireConnDlg ); if ( success ) { #ifndef XWFEATURE_STANDALONE_ONLY @@ -817,7 +834,7 @@ new_game_impl( GtkAppGlobals* globals, XP_Bool fireConnDlg ) }; if ( !game_reset( MEMPOOL &globals->cGlobals.game, gi, - globals->cGlobals.params->util, + globals->cGlobals.util, &globals->cGlobals.cp, &procs ) ) { /* if ( NULL == globals->draw ) { */ /* globals->draw = (GtkDrawCtx*)gtkDrawCtxtMake( globals->drawing_area, */ @@ -874,7 +891,7 @@ game_info( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) /* Anything to do if OK is clicked? Changed names etc. already saved. Try server_do in case one's become a robot. */ - CurGameInfo* gi = &globals->cGlobals.params->gi; + CurGameInfo* gi = &globals->cGlobals.gi; if ( newGameDialog( globals, gi, &addr, XP_FALSE, XP_FALSE ) ) { if ( server_do( globals->cGlobals.game.server ) ) { board_draw( globals->cGlobals.game.board ); @@ -898,16 +915,17 @@ save_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) static void change_dictionary( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) { - LaunchParams* params = globals->cGlobals.params; + CommonGlobals* cGlobals = &globals->cGlobals; + LaunchParams* params = cGlobals->params; GSList* dicts = listDicts( params ); gchar buf[265]; gchar* name = gtkaskdict( dicts, buf, VSIZE(buf) ); if ( !!name ) { DictionaryCtxt* dict = - linux_dictionary_make( MPPARM(params->util->mpool) params, name, + linux_dictionary_make( MPPARM(cGlobals->util->mpool) params, name, params->useMmap ); - game_changeDict( MPPARM(params->util->mpool) &globals->cGlobals.game, - ¶ms->gi, dict ); + game_changeDict( MPPARM(cGlobals->util->mpool) &cGlobals->game, + &cGlobals->gi, dict ); } g_slist_free( dicts ); } /* change_dictionary */ @@ -1314,7 +1332,7 @@ handle_hide_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) XP_Bool draw = XP_FALSE; if ( globals->cGlobals.params->nHidden > 0 ) { - gint nRows = globals->cGlobals.params->gi.boardSize; + gint nRows = globals->cGlobals.gi.boardSize; globals->adjustment->page_size = nRows; globals->adjustment->value = 0.0; @@ -1369,7 +1387,7 @@ gtk_util_userPickTileBlank( XW_UtilCtxt* uc, XP_U16 playerNum, { XP_S16 chosen; GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - XP_UCHAR* name = globals->cGlobals.params->gi.players[playerNum].name; + XP_UCHAR* name = globals->cGlobals.gi.players[playerNum].name; chosen = gtkletterask( NULL, XP_FALSE, name, nTiles, texts ); return chosen; @@ -1382,7 +1400,7 @@ gtk_util_userPickTileTray( XW_UtilCtxt* uc, const PickInfo* pi, { XP_S16 chosen; GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - XP_UCHAR* name = globals->cGlobals.params->gi.players[playerNum].name; + XP_UCHAR* name = globals->cGlobals.gi.players[playerNum].name; chosen = gtkletterask( pi, XP_TRUE, name, nTiles, texts ); return chosen; @@ -1437,7 +1455,7 @@ gtk_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 maxOffset, { GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; if ( !!globals->adjustment ) { - gint nRows = globals->cGlobals.params->gi.boardSize; + gint nRows = globals->cGlobals.gi.boardSize; globals->adjustment->page_size = nRows - maxOffset; globals->adjustment->value = newOffset; gtk_adjustment_value_changed( GTK_ADJUSTMENT(globals->adjustment) ); @@ -1451,7 +1469,7 @@ gtkShowFinalScores( const GtkAppGlobals* globals ) XP_UCHAR* text; const CommonGlobals* cGlobals = &globals->cGlobals; - stream = mem_stream_make( MPPARM(cGlobals->params->util->mpool) + stream = mem_stream_make( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr, NULL, CHANNEL_NONE, NULL ); server_writeFinalScores( cGlobals->game.server, stream ); @@ -1721,7 +1739,7 @@ gtk_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 player, if ( turnLost ) { char wordsBuf[256]; XP_U16 i; - XP_UCHAR* name = globals->cGlobals.params->gi.players[player].name; + XP_UCHAR* name = globals->cGlobals.gi.players[player].name; XP_ASSERT( !!name ); for ( i = 0, wordsBuf[0] = '\0'; ; ) { @@ -2159,7 +2177,7 @@ newConnectionInput( GIOChannel *source, before giving the server another shot. So just call the idle proc. */ if ( redraw ) { - gtk_util_requestTime( globals->cGlobals.params->util ); + gtk_util_requestTime( globals->cGlobals.util ); } else { redraw = server_do( globals->cGlobals.game.server ); } @@ -2210,13 +2228,13 @@ gtk_socket_changed( void* closure, int oldSock, int newSock, void** storage ) XP_ASSERT( info != NULL ); g_source_remove( info->watch ); g_io_channel_unref( info->channel ); - XP_FREE( globals->cGlobals.params->util->mpool, info ); + XP_FREE( globals->cGlobals.util->mpool, info ); *storage = NULL; XP_LOGF( "Removed socket %d from gtk's list of listened-to sockets", oldSock ); } if ( newSock != -1 ) { - info = (SockInfo*)XP_MALLOC( globals->cGlobals.params->util->mpool, + info = (SockInfo*)XP_MALLOC( globals->cGlobals.util->mpool, sizeof(*info) ); GIOChannel* channel = g_io_channel_unix_new( newSock ); g_io_channel_set_close_on_unref( channel, TRUE ); @@ -2277,7 +2295,7 @@ gtk_socket_acceptor( int listener, Acceptor func, CommonGlobals* globals, #endif g_source_remove( info->watch ); g_io_channel_unref( info->channel ); - XP_FREE( globals->params->util->mpool, info ); + XP_FREE( globals->util->mpool, info ); *storage = NULL; XP_LOGF( "Removed listener %d from gtk's list of listened-to sockets", oldSock ); } else { @@ -2293,7 +2311,7 @@ gtk_socket_acceptor( int listener, Acceptor func, CommonGlobals* globals, XP_LOGF( "%s: g_io_add_watch(%d) => %d", __func__, listener, watch ); XP_ASSERT( NULL == info ); - info = XP_MALLOC( globals->params->util->mpool, sizeof(*info) ); + info = XP_MALLOC( globals->util->mpool, sizeof(*info) ); info->channel = channel; info->watch = watch; info->socket = listener; @@ -2386,7 +2404,12 @@ initGlobals( GtkAppGlobals* globals, LaunchParams* params ) globals->cGlobals.cp.hideCrosshairs = params->hideCrosshairs; #endif - setupGtkUtilCallbacks( globals, params->util ); + XW_UtilCtxt* util = calloc( 1, sizeof(*globals->cGlobals.util) ); + globals->cGlobals.util = util; + linux_util_vt_init( MPPARM(params->mpool) util ); + util->gameInfo = &globals->cGlobals.gi; + setupLinuxUtilCallbacks( util ); + setupGtkUtilCallbacks( globals, util ); globals->window = window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); if ( !!params->fileName ) { @@ -2435,7 +2458,7 @@ initGlobals( GtkAppGlobals* globals, LaunchParams* params ) /* install scrollbar even if not needed -- since zooming can make it needed later */ GtkWidget* vscrollbar; - gint nRows = globals->cGlobals.params->gi.boardSize; + gint nRows = globals->cGlobals.gi.boardSize; globals->adjustment = (GtkAdjustment*) gtk_adjustment_new( 0, 0, nRows, 1, 2, nRows - globals->cGlobals.params->nHidden ); @@ -2505,15 +2528,14 @@ makeNewGame( GtkAppGlobals* globals ) comms_getInitialAddr( &addr, RELAY_NAME_DEFAULT, RELAY_PORT_DEFAULT ); } - CurGameInfo* gi = &globals->cGlobals.params->gi; + CurGameInfo* gi = &globals->cGlobals.gi; XP_Bool success = newGameDialog( globals, gi, &addr, XP_TRUE, XP_FALSE ); - if ( success && !!gi->dictName && !globals->cGlobals.params->dict ) { - globals->cGlobals.params->dict = + if ( success && !!gi->dictName && !globals->cGlobals.dict ) { + globals->cGlobals.dict = linux_dictionary_make( MEMPOOL globals->cGlobals.params, gi->dictName, XP_TRUE ); - } else { - success = XP_FALSE; } + LOG_RETURNF( "%d", success ); return success; } diff --git a/xwords4/linux/gtkboard.h b/xwords4/linux/gtkboard.h index d8263d428..2fec8d385 100644 --- a/xwords4/linux/gtkboard.h +++ b/xwords4/linux/gtkboard.h @@ -87,7 +87,7 @@ typedef struct GtkAppGlobals { CommonGlobals cGlobals; GtkWidget* window; GtkDrawCtx* draw; - + GTKGamesGlobals* gg; /* GdkPixmap* pixmap; */ GtkWidget* drawing_area; diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index 1952ddbb3..5eec006f2 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -1,4 +1,4 @@ -/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ /* * Copyright 2000-2013 by Eric House (xwords@eehouse.org). All rights * reserved. @@ -105,6 +105,7 @@ handle_newgame_button( GtkWidget* XP_UNUSED(widget), void* closure ) GtkWidget* gameWindow = globals->window; globals->cGlobals.pDb = gg->pDb; globals->cGlobals.selRow = -1; + globals->gg = gg; gtk_widget_show( gameWindow ); } } @@ -119,6 +120,7 @@ handle_open_button( GtkWidget* XP_UNUSED(widget), void* closure ) initGlobals( globals, gg->params ); globals->cGlobals.pDb = gg->pDb; globals->cGlobals.selRow = gg->selRow; + globals->gg = gg; gtk_widget_show( globals->window ); } } @@ -140,6 +142,15 @@ handle_destroy( GtkWidget* XP_UNUSED(widget), gpointer data ) gtk_main_quit(); } +static void +addButton( gchar* label, GtkWidget* parent, GCallback proc, void* closure ) +{ + GtkWidget* button = gtk_button_new_with_label( label ); + gtk_container_add( GTK_CONTAINER(parent), button ); + g_signal_connect( GTK_OBJECT(button), "clicked", + G_CALLBACK(proc), closure ); + gtk_widget_show( button ); +} static GtkWidget* makeGamesWindow( GTKGamesGlobals* gg ) @@ -170,27 +181,32 @@ makeGamesWindow( GTKGamesGlobals* gg ) GtkWidget* hbox = gtk_hbox_new( FALSE, 0 ); gtk_widget_show( hbox ); gtk_container_add( GTK_CONTAINER(vbox), hbox ); - GtkWidget* button = gtk_button_new_with_label( "New Game" ); - gtk_container_add( GTK_CONTAINER(hbox), button ); - g_signal_connect( GTK_OBJECT(button), "clicked", - G_CALLBACK(handle_newgame_button), gg ); - gtk_widget_show( button ); - - button = gtk_button_new_with_label( "Open" ); - gtk_container_add( GTK_CONTAINER(hbox), button ); - g_signal_connect( GTK_OBJECT(button), "clicked", - G_CALLBACK(handle_open_button), gg ); - gtk_widget_show( button ); - button = gtk_button_new_with_label( "Quit" ); - gtk_container_add( GTK_CONTAINER(hbox), button ); - g_signal_connect( GTK_OBJECT(button), "clicked", - G_CALLBACK(handle_quit_button), gg ); - gtk_widget_show( button ); + + addButton( "New game", hbox, G_CALLBACK(handle_newgame_button), gg ); + addButton( "Open", hbox, G_CALLBACK(handle_open_button), gg ); + addButton( "Quit", hbox, G_CALLBACK(handle_quit_button), gg ); gtk_widget_show( window ); return window; } +static gint +freeGameGlobals( gpointer data ) +{ + LOG_FUNC(); + GtkAppGlobals* globals = (GtkAppGlobals*)data; + // GTKGamesGlobals* gg = globals->gg; + freeGlobals( globals ); + return 0; /* don't run again */ +} + +void +windowDestroyed( GtkAppGlobals* globals ) +{ + /* schedule to run after we're back to main loop */ + (void)g_idle_add( freeGameGlobals, globals ); +} + int gtkmain( LaunchParams* params ) { diff --git a/xwords4/linux/gtkmain.h b/xwords4/linux/gtkmain.h index 2d5c7ce52..95753f0a3 100644 --- a/xwords4/linux/gtkmain.h +++ b/xwords4/linux/gtkmain.h @@ -1,5 +1,5 @@ -/* -*- mode: C; fill-column: 78; c-basic-offset: 4; -*- */ -/* Copyright 1997 - 2005 by Eric House (xwords@eehouse.org) All rights +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* Copyright 1997 - 2013 by Eric House (xwords@eehouse.org) All rights * reserved. * * This program is free software; you can redistribute it and/or @@ -21,7 +21,9 @@ #define _GTKMAIN_H_ #include "main.h" +#include "gtkboard.h" int gtkmain( LaunchParams* params ); +void windowDestroyed( GtkAppGlobals* globals ); #endif diff --git a/xwords4/linux/gtknewgame.c b/xwords4/linux/gtknewgame.c index d5acd03cd..a491263b5 100644 --- a/xwords4/linux/gtknewgame.c +++ b/xwords4/linux/gtknewgame.c @@ -552,10 +552,9 @@ newGameDialog( GtkAppGlobals* globals, CurGameInfo* gi, CommsAddrRec* addr, state.globals = globals; state.gi = gi; - state.newGameCtxt = newg_make( MPPARM(globals->cGlobals.params - ->util->mpool) + state.newGameCtxt = newg_make( MPPARM(globals->cGlobals.util->mpool) isNewGame, - globals->cGlobals.params->util, + globals->cGlobals.util, gtk_newgame_col_enable, gtk_newgame_attr_enable, gtk_newgame_col_get, @@ -585,7 +584,7 @@ newGameDialog( GtkAppGlobals* globals, CurGameInfo* gi, CommsAddrRec* addr, if ( !state.cancelled && !state.revert ) { if ( newg_store( state.newGameCtxt, gi, XP_TRUE ) ) { gi->boardSize = state.nCols; - replaceStringIfDifferent( globals->cGlobals.params->util->mpool, + replaceStringIfDifferent( globals->cGlobals.util->mpool, &gi->dictName, state.dict ); } else { /* Do it again if we warned user of inconsistency. */ diff --git a/xwords4/linux/linuxbt.c b/xwords4/linux/linuxbt.c index 66dcbdd63..534b6ca53 100644 --- a/xwords4/linux/linuxbt.c +++ b/xwords4/linux/linuxbt.c @@ -360,7 +360,7 @@ linux_bt_open( CommonGlobals* globals, XP_Bool amMaster ) LinBtStuff* btStuff = globals->btStuff; if ( !btStuff ) { btStuff = globals->btStuff - = lbt_make( MPPARM(globals->params->util->mpool) amMaster ); + = lbt_make( MPPARM(globals->util->mpool) amMaster ); btStuff->globals = globals; btStuff->socket = -1; @@ -413,7 +413,7 @@ linux_bt_close( CommonGlobals* globals ) (void)close( btStuff->socket ); } - XP_FREE( globals->params->util->mpool, btStuff ); + XP_FREE( globals->util->mpool, btStuff ); globals->btStuff = NULL; } } /* linux_bt_close */ diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 9392a4620..5b5765e73 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -103,7 +103,7 @@ streamFromFile( CommonGlobals* cGlobals, char* name, void* closure ) } fclose( f ); - stream = mem_stream_make( MPPARM(cGlobals->params->util->mpool) + stream = mem_stream_make( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr, closure, CHANNEL_NONE, NULL ); stream_putBytes( stream, buf, statBuf.st_size ); @@ -131,7 +131,7 @@ streamFromDB( CommonGlobals* cGlobals, void* closure ) XP_U8 buf[size]; res = sqlite3_blob_read( ppBlob, buf, size, 0 ); if ( SQLITE_OK == res ) { - stream = mem_stream_make( MPPARM(params->util->mpool) + stream = mem_stream_make( MPPARM(cGlobals->util->mpool) params->vtMgr, closure, CHANNEL_NONE, NULL ); stream_putBytes( stream, buf, size ); @@ -213,7 +213,7 @@ catGameHistory( CommonGlobals* cGlobals ) if ( !!cGlobals->game.model ) { XP_Bool gameOver = server_getGameIsOver( cGlobals->game.server ); XWStreamCtxt* stream = - mem_stream_make( MPPARM(cGlobals->params->util->mpool) + mem_stream_make( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr, NULL, CHANNEL_NONE, catOnClose ); model_writeGameHistory( cGlobals->game.model, stream, @@ -227,15 +227,15 @@ void catFinalScores( const CommonGlobals* cGlobals, XP_S16 quitter ) { XWStreamCtxt* stream; - XP_ASSERT( quitter < cGlobals->params->gi.nPlayers ); + XP_ASSERT( quitter < cGlobals->gi.nPlayers ); - stream = mem_stream_make( MPPARM(cGlobals->params->util->mpool) + stream = mem_stream_make( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr, NULL, CHANNEL_NONE, catOnClose ); if ( -1 != quitter ) { XP_UCHAR buf[128]; XP_SNPRINTF( buf, VSIZE(buf), "Player %s resigned\n", - cGlobals->params->gi.players[quitter].name ); + cGlobals->gi.players[quitter].name ); stream_catString( stream, buf ); } server_writeFinalScores( cGlobals->game.server, stream ); @@ -273,14 +273,14 @@ saveGame( CommonGlobals* cGlobals ) MemStreamCloseCallback onClose = !!cGlobals->pDb? writeToDB : writeToFile; outStream = - mem_stream_make_sized( MPPARM(cGlobals->params->util->mpool) + mem_stream_make_sized( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr, cGlobals->lastStreamSize, cGlobals, 0, onClose ); stream_open( outStream ); game_saveToStream( &cGlobals->game, - &cGlobals->params->gi, + &cGlobals->gi, outStream, ++cGlobals->curSaveToken ); cGlobals->lastStreamSize = stream_getSize( outStream ); stream_destroy( outStream ); @@ -305,10 +305,10 @@ handle_messages_from( CommonGlobals* cGlobals, const TransportProcs* procs, #ifdef DEBUG XP_Bool opened = #endif - game_makeFromStream( MPPARM(cGlobals->params->util->mpool) + game_makeFromStream( MPPARM(cGlobals->util->mpool) stream, &cGlobals->game, - ¶ms->gi, params->dict, - ¶ms->dicts, params->util, + &cGlobals->gi, cGlobals->dict, + &cGlobals->dicts, cGlobals->util, NULL /*draw*/, &cGlobals->cp, procs ); XP_ASSERT( opened ); @@ -333,7 +333,7 @@ handle_messages_from( CommonGlobals* cGlobals, const TransportProcs* procs, XP_LOGF( "%s: 2: unexpected nRead: %d", __func__, nRead ); break; } - stream = mem_stream_make( MPPARM(cGlobals->params->util->mpool) + stream = mem_stream_make( MPPARM(cGlobals->util->mpool) params->vtMgr, cGlobals, CHANNEL_NONE, NULL ); stream_putBytes( stream, buf, len ); @@ -366,10 +366,10 @@ read_pipe_then_close( CommonGlobals* cGlobals, const TransportProcs* procs ) #ifdef DEBUG XP_Bool opened = #endif - game_makeFromStream( MPPARM(cGlobals->params->util->mpool) + game_makeFromStream( MPPARM(cGlobals->util->mpool) stream, &cGlobals->game, - ¶ms->gi, params->dict, - ¶ms->dicts, params->util, + &cGlobals->gi, cGlobals->dict, + &cGlobals->dicts, cGlobals->util, NULL /*draw*/, &cGlobals->cp, procs ); XP_ASSERT( opened ); @@ -397,7 +397,7 @@ read_pipe_then_close( CommonGlobals* cGlobals, const TransportProcs* procs ) XP_LOGF( "%s: 2: unexpected nRead: %d", __func__, nRead ); break; } - stream = mem_stream_make( MPPARM(cGlobals->params->util->mpool) + stream = mem_stream_make( MPPARM(cGlobals->util->mpool) params->vtMgr, cGlobals, CHANNEL_NONE, NULL ); stream_putBytes( stream, buf, len ); @@ -1039,7 +1039,7 @@ stream_from_msgbuf( CommonGlobals* globals, unsigned char* bufPtr, XP_U16 nBytes ) { XWStreamCtxt* result; - result = mem_stream_make( MPPARM(globals->params->util->mpool) + result = mem_stream_make( MPPARM(globals->util->mpool) globals->params->vtMgr, globals, CHANNEL_NONE, NULL ); stream_putBytes( result, bufPtr, nBytes ); @@ -1137,7 +1137,7 @@ linux_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer ) CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; DeviceRole newRole = isServer? SERVER_ISSERVER : SERVER_ISCLIENT; cGlobals->params->serverRole = newRole; - cGlobals->params->gi.serverRole = newRole; + cGlobals->gi.serverRole = newRole; } #endif @@ -1263,8 +1263,7 @@ testGetNthWord( const DictionaryCtxt* dict, char** XP_UNUSED_DBG(words), } static void -walk_dict_test( const LaunchParams* XP_UNUSED_DBG(params), - const DictionaryCtxt* dict, +walk_dict_test( MPFORMAL const DictionaryCtxt* dict, GSList* testPrefixes, const char* testMinMax ) { /* This is just to test that the dict-iterating code works. The words are @@ -1337,18 +1336,18 @@ walk_dict_test( const LaunchParams* XP_UNUSED_DBG(params), XP_U16 maxCount = dict_numTileFaces( dict ); IndexData data; data.count = maxCount * maxCount; - data.indices = XP_MALLOC( params->util->mpool, + data.indices = XP_MALLOC( mpool, data.count * depth * sizeof(data.indices[0]) ); - data.prefixes = XP_MALLOC( params->util->mpool, + data.prefixes = XP_MALLOC( mpool, depth * data.count * sizeof(data.prefixes[0]) ); XP_LOGF( "making index..." ); dict_makeIndex( &iter, depth, &data ); XP_LOGF( "DONE making index" ); - data.indices = XP_REALLOC( params->util->mpool, data.indices, + data.indices = XP_REALLOC( mpool, data.indices, data.count * depth * sizeof(*data.indices) ); - data.prefixes = XP_REALLOC( params->util->mpool, data.prefixes, + data.prefixes = XP_REALLOC( mpool, data.prefixes, depth * data.count * sizeof(*data.prefixes) ); #if 0 for ( ii = 0; ii < nIndices; ++ii ) { @@ -1403,14 +1402,14 @@ walk_dict_test( const LaunchParams* XP_UNUSED_DBG(params), } } - XP_FREE( params->util->mpool, data.indices ); - XP_FREE( params->util->mpool, data.prefixes ); + XP_FREE( mpool, data.indices ); + XP_FREE( mpool, data.prefixes ); } XP_LOGF( "done" ); } static void -walk_dict_test_all( const LaunchParams* params, GSList* testDicts, +walk_dict_test_all( MPFORMAL const LaunchParams* params, GSList* testDicts, GSList* testPrefixes, const char* testMinMax ) { int ii; @@ -1418,11 +1417,11 @@ walk_dict_test_all( const LaunchParams* params, GSList* testDicts, for ( ii = 0; ii < count; ++ii ) { gchar* name = (gchar*)g_slist_nth_data( testDicts, ii ); DictionaryCtxt* dict = - linux_dictionary_make( MPPARM(params->util->mpool) params, name, + linux_dictionary_make( MPPARM(mpool) params, name, params->useMmap ); if ( NULL != dict ) { XP_LOGF( "walk_dict_test(%s)", name ); - walk_dict_test( params, dict, testPrefixes, testMinMax ); + walk_dict_test( MPPARM(mpool) dict, testPrefixes, testMinMax ); dict_destroy( dict ); } } @@ -1482,7 +1481,7 @@ getDictPath( const LaunchParams *params, const char* name, break; } } - LOG_RETURNF( "%d", success ); + XP_LOGF( "%s(%s)=>%d", __func__, name, success ); return success; } @@ -1512,36 +1511,46 @@ listDicts( const LaunchParams *params ) return result; } +void +setupLinuxUtilCallbacks( XW_UtilCtxt* util ) +{ +#ifndef XWFEATURE_STANDALONE_ONLY + util->vtable->m_util_informMissing = linux_util_informMissing; + util->vtable->m_util_addrChange = linux_util_addrChange; + util->vtable->m_util_setIsServer = linux_util_setIsServer; +#endif +} + static void initParams( LaunchParams* params ) { memset( params, 0, sizeof(*params) ); - params->util = calloc( 1, sizeof(*params->util) ); + // params->util = calloc( 1, sizeof(*params->util) ); /* XP_MEMSET( params->util, 0, sizeof(params->util) ); */ #ifdef MEM_DEBUG - params->util->mpool = mpool_make(); + params->mpool = mpool_make(); #endif - params->vtMgr = make_vtablemgr(MPPARM_NOCOMMA(params->util->mpool)); - linux_util_vt_init( MPPARM(params->util->mpool) params->util ); + params->vtMgr = make_vtablemgr(MPPARM_NOCOMMA(params->mpool)); + // linux_util_vt_init( MPPARM(params->mpool) params->util ); #ifndef XWFEATURE_STANDALONE_ONLY - params->util->vtable->m_util_informMissing = linux_util_informMissing; - params->util->vtable->m_util_addrChange = linux_util_addrChange; - params->util->vtable->m_util_setIsServer = linux_util_setIsServer; + /* params->util->vtable->m_util_informMissing = linux_util_informMissing; */ + /* params->util->vtable->m_util_addrChange = linux_util_addrChange; */ + /* params->util->vtable->m_util_setIsServer = linux_util_setIsServer; */ #endif } static void freeParams( LaunchParams* params ) { - linux_util_vt_destroy( params->util ); - vtmgr_destroy( MPPARM(params->util->mpool) params->vtMgr ); + // linux_util_vt_destroy( params->util ); + vtmgr_destroy( MPPARM(params->mpool) params->vtMgr ); - mpool_destroy( params->util->mpool ); + mpool_destroy( params->mpool ); - free( params->util ); + // free( params->util ); } int @@ -1558,7 +1567,7 @@ main( int argc, char** argv ) LaunchParams mainParams; XP_U16 nPlayerDicts = 0; XP_U16 robotCount = 0; - XP_U16 ii; + /* XP_U16 ii; */ #ifdef XWFEATURE_WALKDICT GSList* testDicts = NULL; GSList* testPrefixes = NULL; @@ -1610,13 +1619,13 @@ main( int argc, char** argv ) mainParams.connInfo.ip.port = DEFAULT_PORT; mainParams.connInfo.ip.hostName = "localhost"; #endif - mainParams.gi.boardSize = 15; + mainParams.pgi.boardSize = 15; mainParams.quitAfter = -1; mainParams.sleepOnAnchor = XP_FALSE; mainParams.printHistory = XP_FALSE; mainParams.undoWhenDone = XP_FALSE; - mainParams.gi.timerEnabled = XP_FALSE; - mainParams.gi.dictLang = -1; + mainParams.pgi.timerEnabled = XP_FALSE; + mainParams.pgi.dictLang = -1; mainParams.noHeartbeat = XP_FALSE; mainParams.nHidden = 0; mainParams.needsNewGame = XP_FALSE; @@ -1682,7 +1691,7 @@ main( int argc, char** argv ) break; case CMD_DICT: trimDictPath( optarg, dictbuf, VSIZE(dictbuf), &path, &dict ); - mainParams.gi.dictName = copyString( mainParams.util->mpool, dict ); + mainParams.pgi.dictName = copyString( mainParams.mpool, dict ); if ( !path ) { path = "."; } @@ -1759,13 +1768,13 @@ main( int argc, char** argv ) mainParams.skipWarnings = 1; break; case CMD_LOCALPWD: - mainParams.gi.players[mainParams.nLocalPlayers-1].password + mainParams.pgi.players[mainParams.nLocalPlayers-1].password = (XP_UCHAR*)optarg; break; case CMD_LOCALSMARTS: - index = mainParams.gi.nPlayers - 1; - XP_ASSERT( LP_IS_ROBOT( &mainParams.gi.players[index] ) ); - mainParams.gi.players[index].robotIQ = atoi(optarg); + index = mainParams.pgi.nPlayers - 1; + XP_ASSERT( LP_IS_ROBOT( &mainParams.pgi.players[index] ) ); + mainParams.pgi.players[index].robotIQ = atoi(optarg); break; #ifdef XWFEATURE_SMS case CMD_SMSNUMBER: /* SMS phone number */ @@ -1781,22 +1790,22 @@ main( int argc, char** argv ) mainParams.dropNthRcvd = atoi( optarg ); break; case CMD_NOHINTS: - mainParams.gi.hintsNotAllowed = XP_TRUE; + mainParams.pgi.hintsNotAllowed = XP_TRUE; break; case CMD_PICKTILESFACEUP: - mainParams.gi.allowPickTiles = XP_TRUE; + mainParams.pgi.allowPickTiles = XP_TRUE; break; case CMD_PLAYERNAME: - index = mainParams.gi.nPlayers++; + index = mainParams.pgi.nPlayers++; ++mainParams.nLocalPlayers; - mainParams.gi.players[index].robotIQ = 0; /* means human */ - mainParams.gi.players[index].isLocal = XP_TRUE; - mainParams.gi.players[index].name = - copyString( mainParams.util->mpool, (XP_UCHAR*)optarg); + mainParams.pgi.players[index].robotIQ = 0; /* means human */ + mainParams.pgi.players[index].isLocal = XP_TRUE; + mainParams.pgi.players[index].name = + copyString( mainParams.mpool, (XP_UCHAR*)optarg); break; case CMD_REMOTEPLAYER: - index = mainParams.gi.nPlayers++; - mainParams.gi.players[index].isLocal = XP_FALSE; + index = mainParams.pgi.nPlayers++; + mainParams.pgi.players[index].isLocal = XP_FALSE; ++mainParams.info.serverInfo.nRemotePlayers; break; case CMD_PORT: @@ -1805,12 +1814,12 @@ main( int argc, char** argv ) break; case CMD_ROBOTNAME: ++robotCount; - index = mainParams.gi.nPlayers++; + index = mainParams.pgi.nPlayers++; ++mainParams.nLocalPlayers; - mainParams.gi.players[index].robotIQ = 1; /* real smart by default */ - mainParams.gi.players[index].isLocal = XP_TRUE; - mainParams.gi.players[index].name = - copyString( mainParams.util->mpool, (XP_UCHAR*)optarg); + mainParams.pgi.players[index].robotIQ = 1; /* real smart by default */ + mainParams.pgi.players[index].isLocal = XP_TRUE; + mainParams.pgi.players[index].name = + copyString( mainParams.mpool, (XP_UCHAR*)optarg); break; case CMD_SORTNEW: mainParams.sortNewTiles = XP_TRUE; @@ -1822,8 +1831,8 @@ main( int argc, char** argv ) mainParams.sleepOnAnchor = XP_TRUE; break; case CMD_TIMERMINUTES: - mainParams.gi.gameSeconds = atoi(optarg) * 60; - mainParams.gi.timerEnabled = XP_TRUE; + mainParams.pgi.gameSeconds = atoi(optarg) * 60; + mainParams.pgi.timerEnabled = XP_TRUE; break; case CMD_UNDOWHENDONE: mainParams.undoWhenDone = XP_TRUE; @@ -1849,13 +1858,13 @@ main( int argc, char** argv ) case CMD_PHONIES: switch( atoi(optarg) ) { case 0: - mainParams.gi.phoniesAction = PHONIES_IGNORE; + mainParams.pgi.phoniesAction = PHONIES_IGNORE; break; case 1: - mainParams.gi.phoniesAction = PHONIES_WARN; + mainParams.pgi.phoniesAction = PHONIES_WARN; break; case 2: - mainParams.gi.phoniesAction = PHONIES_DISALLOW; + mainParams.pgi.phoniesAction = PHONIES_DISALLOW; break; default: usage( argv[0], "phonies takes 0 or 1 or 2" ); @@ -1872,7 +1881,7 @@ main( int argc, char** argv ) mainParams.quitAfter = atoi(optarg); break; case CMD_BOARDSIZE: - mainParams.gi.boardSize = atoi(optarg); + mainParams.pgi.boardSize = atoi(optarg); break; #ifdef XWFEATURE_BLUETOOTH case CMD_BTADDR: @@ -1951,17 +1960,17 @@ main( int argc, char** argv ) } } - XP_ASSERT( mainParams.gi.nPlayers == mainParams.nLocalPlayers + XP_ASSERT( mainParams.pgi.nPlayers == mainParams.nLocalPlayers + mainParams.info.serverInfo.nRemotePlayers ); if ( isServer ) { if ( mainParams.info.serverInfo.nRemotePlayers == 0 ) { - mainParams.gi.serverRole = SERVER_STANDALONE; + mainParams.pgi.serverRole = SERVER_STANDALONE; } else { - mainParams.gi.serverRole = SERVER_ISSERVER; + mainParams.pgi.serverRole = SERVER_ISSERVER; } } else { - mainParams.gi.serverRole = SERVER_ISCLIENT; + mainParams.pgi.serverRole = SERVER_ISCLIENT; } /* sanity checks */ @@ -1978,21 +1987,21 @@ main( int argc, char** argv ) } } - if ( !!mainParams.gi.dictName ) { + if ( !!mainParams.pgi.dictName ) { /* char path[256]; */ /* getDictPath( &mainParams, mainParams.gi.dictName, path, VSIZE(path) ); */ - mainParams.dict = - linux_dictionary_make( MPPARM(mainParams.util->mpool) &mainParams, - mainParams.gi.dictName, - mainParams.useMmap ); - XP_ASSERT( !!mainParams.dict ); - mainParams.gi.dictLang = dict_getLangCode( mainParams.dict ); + /* mainParams.dict = */ + /* linux_dictionary_make( MPPARM(mainParams.mpool) &mainParams, */ + /* mainParams.pgi.dictName, */ + /* mainParams.useMmap ); */ + /* XP_ASSERT( !!mainParams.dict ); */ + /* mainParams.pgi.dictLang = dict_getLangCode( mainParams.dict ); */ } else if ( isServer ) { #ifdef STUBBED_DICT mainParams.dict = make_stubbed_dict( MPPARM_NOCOMMA(mainParams.util->mpool) ); XP_WARNF( "no dictionary provided: using English stub dict\n" ); - mainParams.gi.dictLang = dict_getLangCode( mainParams.dict ); + mainParams.pgi.dictLang = dict_getLangCode( mainParams.dict ); #else if ( 0 == nPlayerDicts ) { mainParams.needsNewGame = XP_TRUE; @@ -2003,7 +2012,7 @@ main( int argc, char** argv ) } if ( 0 < mainParams.info.serverInfo.nRemotePlayers - && SERVER_STANDALONE == mainParams.gi.serverRole ) { + && SERVER_STANDALONE == mainParams.pgi.serverRole ) { mainParams.needsNewGame = XP_TRUE; } @@ -2011,20 +2020,20 @@ main( int argc, char** argv ) given. It's an error to give too many, or not to give enough if there's no game-dict */ if ( 0 < nPlayerDicts ) { - XP_U16 nextDict = 0; - for ( ii = 0; ii < mainParams.gi.nPlayers; ++ii ) { - if ( mainParams.gi.players[ii].isLocal ) { - const XP_UCHAR* name = mainParams.playerDictNames[nextDict++]; - XP_ASSERT( !!name ); - mainParams.dicts.dicts[ii] = - linux_dictionary_make( MPPARM(mainParams.util->mpool) - &mainParams, name, mainParams.useMmap ); - } - } - if ( nextDict < nPlayerDicts ) { - usage( argv[0], " --player-dict used more times than there are " - "local players" ); - } + /* XP_U16 nextDict = 0; */ + /* for ( ii = 0; ii < mainParams.pgi.nPlayers; ++ii ) { */ + /* if ( mainParams.pgi.players[ii].isLocal ) { */ + /* const XP_UCHAR* name = mainParams.playerDictNames[nextDict++]; */ + /* XP_ASSERT( !!name ); */ + /* mainParams.dicts.dicts[ii] = */ + /* linux_dictionary_make( MPPARM(mainParams.mpool) */ + /* &mainParams, name, mainParams.useMmap ); */ + /* } */ + /* } */ + /* if ( nextDict < nPlayerDicts ) { */ + /* usage( argv[0], " --player-dict used more times than there are " */ + /* "local players" ); */ + /* } */ } /* if ( !isServer ) { */ @@ -2034,7 +2043,7 @@ main( int argc, char** argv ) /* } */ #ifdef XWFEATURE_WALKDICT if ( !!testDicts ) { - walk_dict_test_all( &mainParams, testDicts, testPrefixes, testMinMax ); + walk_dict_test_all( mainParams.mpool, &mainParams, testDicts, testPrefixes, testMinMax ); exit( 0 ); } #endif @@ -2099,7 +2108,7 @@ main( int argc, char** argv ) /* mainParams.util->vtable->m_util_makeStreamFromAddr = */ /* linux_util_makeStreamFromAddr; */ - mainParams.util->gameInfo = &mainParams.gi; + // mainParams.util->gameInfo = &mainParams.pgi; srandom( seed ); /* init linux random number generator */ XP_LOGF( "seeded srandom with %d", seed ); @@ -2122,8 +2131,8 @@ main( int argc, char** argv ) } if ( mainParams.needsNewGame ) { - gi_initPlayerInfo( MPPARM(mainParams.util->mpool) - &mainParams.gi, NULL ); + gi_initPlayerInfo( MPPARM(mainParams.mpool) + &mainParams.pgi, NULL ); } /* curses doesn't have newgame dialog */ diff --git a/xwords4/linux/linuxmain.h b/xwords4/linux/linuxmain.h index f6cb25219..9a4721619 100644 --- a/xwords4/linux/linuxmain.h +++ b/xwords4/linux/linuxmain.h @@ -94,6 +94,8 @@ void setOneSecondTimer( CommonGlobals* cGlobals ); # define setOneSecondTimer( cGlobals ) #endif +void setupLinuxUtilCallbacks( XW_UtilCtxt* util ); + /* void initParams( LaunchParams* params ); */ /* void freeParams( LaunchParams* params ); */ diff --git a/xwords4/linux/linuxsms.c b/xwords4/linux/linuxsms.c index b69f751c6..8d4954783 100644 --- a/xwords4/linux/linuxsms.c +++ b/xwords4/linux/linuxsms.c @@ -1,4 +1,4 @@ -/* -*-mode: C; compile-command: "make -j MEMDEBUG=TRUE";-*- */ +/* -*- compile-command: "make -j MEMDEBUG=TRUE";-*- */ /* * Copyright 2006-2009 by Eric House (xwords@eehouse.org). All rights * reserved. @@ -93,7 +93,7 @@ linux_sms_init( CommonGlobals* globals, const CommsAddrRec* addr ) { LinSMSData* data = globals->smsData; if ( !data ) { - data = XP_MALLOC( globals->params->util->mpool, sizeof(*data) ); + data = XP_MALLOC( globals->util->mpool, sizeof(*data) ); XP_ASSERT( !!data ); XP_MEMSET( data, 0, sizeof(*data) ); globals->smsData = data; @@ -125,7 +125,7 @@ linux_sms_close( CommonGlobals* globals ) { LinSMSData* data = globals->smsData; if ( !!data ) { - XP_FREE( globals->params->util->mpool, data ); + XP_FREE( globals->util->mpool, data ); globals->smsData = NULL; } } /* linux_sms_close */ diff --git a/xwords4/linux/linuxutl.c b/xwords4/linux/linuxutl.c index 650c0c892..59d1d96c2 100644 --- a/xwords4/linux/linuxutl.c +++ b/xwords4/linux/linuxutl.c @@ -148,7 +148,7 @@ setSquareBonuses( const CommonGlobals* cGlobals ) { XP_U16 nBonuses; XWBonusType* bonuses = - bonusesFor( cGlobals->params->gi.boardSize, &nBonuses ); + bonusesFor( cGlobals->gi.boardSize, &nBonuses ); if ( !!bonuses ) { model_setSquareBonuses( cGlobals->game.model, bonuses, nBonuses ); } @@ -390,6 +390,9 @@ linux_util_deviceRegistered( XW_UtilCtxt* uc, DevIDType typ, void linux_util_vt_init( MPFORMAL XW_UtilCtxt* util ) { +#ifdef MEM_DEBUG + util->mpool = mpool; +#endif util->vtable = XP_MALLOC( mpool, sizeof(UtilVtable) ); util->vtable->m_util_makeEmptyDict = linux_util_makeEmptyDict; @@ -611,7 +614,7 @@ writeNoConnMsgs( CommonGlobals* cGlobals, int fd ) XP_ASSERT( 0 < nMsgs ); XWStreamCtxt* stream = - mem_stream_make( MPPARM(cGlobals->params->util->mpool) + mem_stream_make( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr, cGlobals, CHANNEL_NONE, NULL ); stream_putU16( stream, 1 ); /* number of relayIDs */ diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index 0c63eee7f..a9870b57a 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -46,10 +46,8 @@ typedef struct LinuxUtilCtxt { typedef struct LaunchParams { /* CommPipeCtxt* pipe; */ - XW_UtilCtxt* util; - DictionaryCtxt* dict; - CurGameInfo gi; - PlayerDicts dicts; + CurGameInfo pgi; + GSList* dictDirs; char* fileName; XP_U16 saveFailPct; @@ -139,7 +137,7 @@ typedef struct LaunchParams { ServerInfo serverInfo; ClientInfo clientInfo; } info; - + MPSLOT } LaunchParams; typedef struct CommonGlobals CommonGlobals; @@ -167,8 +165,12 @@ typedef struct _TimerInfo { struct CommonGlobals { LaunchParams* params; CommonPrefs cp; + XW_UtilCtxt* util; XWGame game; + CurGameInfo gi; + DictionaryCtxt* dict; + PlayerDicts dicts; XP_U16 lastNTilesToUse; XP_U16 lastStreamSize; XP_Bool manualFinal; /* use asked for final scores */ @@ -213,6 +215,7 @@ typedef struct _GTKGamesGlobals { sqlite3* pDb; sqlite3_int64 selRow; LaunchParams* params; + GSList* globalsList; } GTKGamesGlobals; #endif From 469e65646402a0f796c9c3403a78eb9588dac916 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 7 Jan 2013 06:36:34 -0800 Subject: [PATCH 007/116] track open games so don't double-open and can close all on quit --- xwords4/linux/gtkboard.c | 8 ++++---- xwords4/linux/gtkmain.c | 41 ++++++++++++++++++++++++++++++++++----- xwords4/linux/linuxmain.c | 2 +- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 60997bbf3..f10f99b3c 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -712,9 +712,6 @@ cleanup( GtkAppGlobals* globals ) { saveGame( &globals->cGlobals ); - game_dispose( &globals->cGlobals.game ); /* takes care of the dict */ - gi_disposePlayerInfo( MEMPOOL &globals->cGlobals.gi ); - #ifdef XWFEATURE_BLUETOOTH linux_bt_close( &globals->cGlobals ); #endif @@ -727,6 +724,9 @@ cleanup( GtkAppGlobals* globals ) #ifdef XWFEATURE_RELAY linux_close_socket( &globals->cGlobals ); #endif + game_dispose( &globals->cGlobals.game ); /* takes care of the dict */ + gi_disposePlayerInfo( MEMPOOL &globals->cGlobals.gi ); + linux_util_vt_destroy( globals->cGlobals.util ); } /* cleanup */ @@ -2384,7 +2384,7 @@ initGlobals( GtkAppGlobals* globals, LaunchParams* params ) # endif globals->cGlobals.socketChanged = gtk_socket_changed; - globals->cGlobals.socketChangedClosure = &globals; + globals->cGlobals.socketChangedClosure = globals; globals->cGlobals.addAcceptor = gtk_socket_acceptor; #endif diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index 5eec006f2..2e5431dd3 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -25,6 +25,31 @@ #include "gtkboard.h" #include "linuxmain.h" +static void +recordOpened( GTKGamesGlobals* gg, GtkAppGlobals* globals ) +{ + gg->globalsList = g_slist_prepend( gg->globalsList, globals ); + globals->gg = gg; +} + +static void +recordClosed( GTKGamesGlobals* gg, GtkAppGlobals* globals ) +{ + gg->globalsList = g_slist_remove( gg->globalsList, globals ); +} + +static XP_Bool +gameIsOpen( GTKGamesGlobals* gg, sqlite3_int64 rowid ) +{ + XP_Bool found = XP_FALSE; + GSList* iter; + for ( iter = gg->globalsList; !!iter && !found; iter = iter->next ) { + GtkAppGlobals* globals = (GtkAppGlobals*)iter->data; + found = globals->cGlobals.selRow == rowid; + } + return found; +} + enum { ROW_ITEM, NAME_ITEM, N_ITEMS }; /* Prototype for selection handler callback */ @@ -105,7 +130,7 @@ handle_newgame_button( GtkWidget* XP_UNUSED(widget), void* closure ) GtkWidget* gameWindow = globals->window; globals->cGlobals.pDb = gg->pDb; globals->cGlobals.selRow = -1; - globals->gg = gg; + recordOpened( gg, globals ); gtk_widget_show( gameWindow ); } } @@ -114,13 +139,13 @@ static void handle_open_button( GtkWidget* XP_UNUSED(widget), void* closure ) { GTKGamesGlobals* gg = (GTKGamesGlobals*)closure; - if ( -1 != gg->selRow ) { + if ( -1 != gg->selRow && !gameIsOpen( gg, gg->selRow ) ) { gg->params->needsNewGame = XP_FALSE; GtkAppGlobals* globals = malloc( sizeof(*globals) ); initGlobals( globals, gg->params ); globals->cGlobals.pDb = gg->pDb; globals->cGlobals.selRow = gg->selRow; - globals->gg = gg; + recordOpened( gg, globals ); gtk_widget_show( globals->window ); } } @@ -138,7 +163,12 @@ handle_destroy( GtkWidget* XP_UNUSED(widget), gpointer data ) { LOG_FUNC(); GTKGamesGlobals* gg = (GTKGamesGlobals*)data; - gg = gg; + GSList* iter; + for ( iter = gg->globalsList; !!iter; iter = iter->next ) { + GtkAppGlobals* globals = (GtkAppGlobals*)iter->data; + freeGlobals( globals ); + } + g_slist_free( gg->globalsList ); gtk_main_quit(); } @@ -195,7 +225,8 @@ freeGameGlobals( gpointer data ) { LOG_FUNC(); GtkAppGlobals* globals = (GtkAppGlobals*)data; - // GTKGamesGlobals* gg = globals->gg; + GTKGamesGlobals* gg = globals->gg; + recordClosed( gg, globals ); freeGlobals( globals ); return 0; /* don't run again */ } diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 5b5765e73..9e9ee6224 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -967,7 +967,7 @@ linux_close_socket( CommonGlobals* cGlobals ) (*cGlobals->socketChanged)( cGlobals->socketChangedClosure, socket, -1, &cGlobals->storage ); - XP_ASSERT( -1 == cGlobals->socket ); + XP_ASSERT( -1 == cGlobals->socket ); XP_LOGF( "linux_close_socket" ); close( socket ); From 565b742971fa88daec8c0931414f57e42f2810d3 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 7 Jan 2013 08:00:47 -0800 Subject: [PATCH 008/116] snapshot -- connects to relay but doesn't play yet. --- xwords4/common/comtypes.h | 2 +- xwords4/linux/Makefile | 1 + xwords4/linux/gamesdb.c | 15 ++++++----- xwords4/linux/gamesdb.h | 2 +- xwords4/linux/gtkboard.c | 57 ++++++++++++++++++++------------------- xwords4/linux/gtkmain.c | 16 +++++++++-- xwords4/linux/gtkmain.h | 1 + xwords4/linux/linuxmain.c | 7 ++++- xwords4/linux/main.h | 7 +++++ 9 files changed, 70 insertions(+), 38 deletions(-) diff --git a/xwords4/common/comtypes.h b/xwords4/common/comtypes.h index 805694f1e..d841696f6 100644 --- a/xwords4/common/comtypes.h +++ b/xwords4/common/comtypes.h @@ -249,7 +249,7 @@ typedef struct _PlayerDicts { # define RELAY_ROOM_DEFAULT "Room 1" #endif #ifndef RELAY_PORT_DEFAULT -# define RELAY_PORT_DEFAULT 10999 +# define RELAY_PORT_DEFAULT 10997 #endif #ifdef MEM_DEBUG diff --git a/xwords4/linux/Makefile b/xwords4/linux/Makefile index ed114e839..38861248a 100644 --- a/xwords4/linux/Makefile +++ b/xwords4/linux/Makefile @@ -101,6 +101,7 @@ DEFINES += -DXWFEATURE_WALKDICT DEFINES += -DXWFEATURE_WALKDICT_FILTER DEFINES += -DXWFEATURE_DICTSANITY DEFINES += -DHASH_STREAM +DEFINES += -DRELAY_NAME_DEFAULT="\"localhost\"" #DEFINES += -DXWFEATURE_SCOREONEPASS ### Enable zero or one of these two ### #DEFINES += -DXWFEATURE_TRAYUNDO_ALL diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c index dedbdc525..bd4b7a146 100644 --- a/xwords4/linux/gamesdb.c +++ b/xwords4/linux/gamesdb.c @@ -21,13 +21,11 @@ #include "gamesdb.h" #include "main.h" -#define DB_NAME "games.db" - sqlite3* -openGamesDB( void ) +openGamesDB( const char* dbName ) { sqlite3* pDb = NULL; - int result = sqlite3_open( DB_NAME, &pDb ); + int result = sqlite3_open( dbName, &pDb ); XP_ASSERT( SQLITE_OK == result ); const char* createStr = @@ -62,7 +60,8 @@ writeToDB( XWStreamCtxt* stream, void* closure ) char* query; sqlite3_stmt* stmt = NULL; - if ( -1 == selRow ) { /* new row; need to insert blob first */ + XP_Bool newGame = -1 == selRow; + if ( newGame ) { /* new row; need to insert blob first */ query = "INSERT INTO games (game) VALUES (?)"; } else { const char* fmt = "UPDATE games SET game=? where rowid=%lld"; @@ -77,7 +76,7 @@ writeToDB( XWStreamCtxt* stream, void* closure ) result = sqlite3_step( stmt ); XP_ASSERT( SQLITE_DONE == result ); - if ( -1 == selRow ) { /* new row; need to insert blob first */ + if ( newGame ) { /* new row; need to insert blob first */ selRow = sqlite3_last_insert_rowid( pDb ); XP_LOGF( "%s: new rowid: %lld", __func__, selRow ); cGlobals->selRow = selRow; @@ -95,6 +94,10 @@ writeToDB( XWStreamCtxt* stream, void* closure ) if ( !!stmt ) { sqlite3_finalize( stmt ); } + + if ( newGame ) { + (*cGlobals->firstSave)( cGlobals->firstSaveClosure ); + } } GSList* diff --git a/xwords4/linux/gamesdb.h b/xwords4/linux/gamesdb.h index bbda321f4..73f5b9a2d 100644 --- a/xwords4/linux/gamesdb.h +++ b/xwords4/linux/gamesdb.h @@ -27,7 +27,7 @@ #include "main.h" #include "comtypes.h" -sqlite3* openGamesDB( void ); +sqlite3* openGamesDB( const char* dbName ); void closeGamesDB( sqlite3* dbp ); void writeToDB( XWStreamCtxt* stream, void* closure ); diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index f10f99b3c..58f21c649 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -456,35 +456,34 @@ createOrLoadObjects( GtkAppGlobals* globals ) } if ( !opened ) { - CommsAddrRec addr; + CommsAddrRec addr = cGlobals->addr; - XP_MEMSET( &addr, 0, sizeof(addr) ); - addr.conType = params->conType; + /* XP_MEMSET( &addr, 0, sizeof(addr) ); */ + /* addr.conType = cGlobals->addr.conType; */ #ifdef XWFEATURE_RELAY - if ( addr.conType == COMMS_CONN_RELAY ) { - XP_ASSERT( !!params->connInfo.relay.relayName ); - globals->cGlobals.defaultServerName - = params->connInfo.relay.relayName; - } + /* if ( addr.conType == COMMS_CONN_RELAY ) { */ + /* XP_ASSERT( !!params->connInfo.relay.relayName ); */ + /* globals->cGlobals.defaultServerName */ + /* = params->connInfo.relay.relayName; */ + /* } */ #endif - CommonGlobals* cGlobals = &globals->cGlobals; game_makeNewGame( MEMPOOL &cGlobals->game, &cGlobals->gi, cGlobals->util, (DrawCtx*)globals->draw, &cGlobals->cp, &procs, params->gameSeed ); - addr.conType = params->conType; + // addr.conType = params->conType; if ( 0 ) { #ifdef XWFEATURE_RELAY } else if ( addr.conType == COMMS_CONN_RELAY ) { - addr.u.ip_relay.ipAddr = 0; - addr.u.ip_relay.port = params->connInfo.relay.defaultSendPort; - addr.u.ip_relay.seeksPublicRoom = params->connInfo.relay.seeksPublicRoom; - addr.u.ip_relay.advertiseRoom = params->connInfo.relay.advertiseRoom; - XP_STRNCPY( addr.u.ip_relay.hostName, params->connInfo.relay.relayName, - sizeof(addr.u.ip_relay.hostName) - 1 ); - XP_STRNCPY( addr.u.ip_relay.invite, params->connInfo.relay.invite, - sizeof(addr.u.ip_relay.invite) - 1 ); + /* addr.u.ip_relay.ipAddr = 0; */ + /* addr.u.ip_relay.port = params->connInfo.relay.defaultSendPort; */ + /* addr.u.ip_relay.seeksPublicRoom = params->connInfo.relay.seeksPublicRoom; */ + /* addr.u.ip_relay.advertiseRoom = params->connInfo.relay.advertiseRoom; */ + /* XP_STRNCPY( addr.u.ip_relay.hostName, params->connInfo.relay.relayName, */ + /* sizeof(addr.u.ip_relay.hostName) - 1 ); */ + /* XP_STRNCPY( addr.u.ip_relay.invite, params->connInfo.relay.invite, */ + /* sizeof(addr.u.ip_relay.invite) - 1 ); */ #endif #ifdef XWFEATURE_BLUETOOTH } else if ( addr.conType == COMMS_CONN_BT ) { @@ -2385,6 +2384,8 @@ initGlobals( GtkAppGlobals* globals, LaunchParams* params ) globals->cGlobals.socketChanged = gtk_socket_changed; globals->cGlobals.socketChangedClosure = globals; + globals->cGlobals.firstSave = newGameSaved; + globals->cGlobals.firstSaveClosure = globals; globals->cGlobals.addAcceptor = gtk_socket_acceptor; #endif @@ -2521,18 +2522,20 @@ freeGlobals( GtkAppGlobals* globals ) XP_Bool makeNewGame( GtkAppGlobals* globals ) { - CommsAddrRec addr; - if ( !!globals->cGlobals.game.comms ) { - comms_getAddr( globals->cGlobals.game.comms, &addr ); + CommonGlobals* cGlobals = &globals->cGlobals; + if ( !!cGlobals->game.comms ) { + comms_getAddr( cGlobals->game.comms, &cGlobals->addr ); } else { - comms_getInitialAddr( &addr, RELAY_NAME_DEFAULT, RELAY_PORT_DEFAULT ); + comms_getInitialAddr( &cGlobals->addr, RELAY_NAME_DEFAULT, + RELAY_PORT_DEFAULT ); } - CurGameInfo* gi = &globals->cGlobals.gi; - XP_Bool success = newGameDialog( globals, gi, &addr, XP_TRUE, XP_FALSE ); - if ( success && !!gi->dictName && !globals->cGlobals.dict ) { - globals->cGlobals.dict = - linux_dictionary_make( MEMPOOL globals->cGlobals.params, + CurGameInfo* gi = &cGlobals->gi; + XP_Bool success = newGameDialog( globals, gi, &cGlobals->addr, + XP_TRUE, XP_FALSE ); + if ( success && !!gi->dictName && !cGlobals->dict ) { + cGlobals->dict = + linux_dictionary_make( MEMPOOL cGlobals->params, gi->dictName, XP_TRUE ); } LOG_RETURNF( "%d", success ); diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index 2e5431dd3..e8c8411c9 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -183,7 +183,7 @@ addButton( gchar* label, GtkWidget* parent, GCallback proc, void* closure ) } static GtkWidget* -makeGamesWindow( GTKGamesGlobals* gg ) +makeGamesWindow( GTKGamesGlobals* gg ) { GtkWidget* window; @@ -195,6 +195,7 @@ makeGamesWindow( GTKGamesGlobals* gg ) gtk_container_add( GTK_CONTAINER(window), vbox ); gtk_widget_show( vbox ); GtkWidget* list = init_games_list( gg ); + gg->listWidget = list; gtk_container_add( GTK_CONTAINER(vbox), list ); gtk_widget_show( list ); @@ -238,6 +239,17 @@ windowDestroyed( GtkAppGlobals* globals ) (void)g_idle_add( freeGameGlobals, globals ); } +void +newGameSaved( void* closure ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)closure; + GTKGamesGlobals* gg = globals->gg; + CommonGlobals* cGlobals = &globals->cGlobals; + XP_UCHAR buf[128]; + getGameName( gg, &cGlobals->selRow, buf, sizeof(buf) ); + add_to_list( gg->listWidget, &cGlobals->selRow, buf ); +} + int gtkmain( LaunchParams* params ) { @@ -245,7 +257,7 @@ gtkmain( LaunchParams* params ) gg.selRow = -1; gg.params = params; XP_LOGF( "%s: I'M HERE!!! (calling makeGamesDB())", __func__ ); - gg.pDb = openGamesDB(); + gg.pDb = openGamesDB( params->dbName ); (void)makeGamesWindow( &gg ); gtk_main(); diff --git a/xwords4/linux/gtkmain.h b/xwords4/linux/gtkmain.h index 95753f0a3..015b7d307 100644 --- a/xwords4/linux/gtkmain.h +++ b/xwords4/linux/gtkmain.h @@ -25,5 +25,6 @@ int gtkmain( LaunchParams* params ); void windowDestroyed( GtkAppGlobals* globals ); +void newGameSaved( void* closure ); #endif diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 9e9ee6224..ab2467f26 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -74,7 +74,7 @@ #include "memstream.h" #include "LocalizedStrIncludes.h" -#define DEFAULT_PORT 10999 +#define DEFAULT_PORT 10997 #define DEFAULT_LISTEN_PORT 4998 XP_Bool @@ -523,6 +523,7 @@ typedef enum { #endif ,CMD_GAMESEED ,CMD_GAMEFILE + ,CMD_DBFILE ,CMD_SAVEFAIL_PCT #ifdef USE_SQLITE ,CMD_GAMEDB_FILE @@ -620,6 +621,7 @@ static CmdInfoRec CmdInfoRecs[] = { #endif ,{ CMD_GAMESEED, true, "game-seed", "game seed (for relay play)" } ,{ CMD_GAMEFILE, true, "file", "file to save to/read from" } + ,{ CMD_DBFILE, true, "db", "sqlite3 db to store game data" } ,{ CMD_SAVEFAIL_PCT, true, "savefail-pct", "How often, at random, does save fail?" } #ifdef USE_SQLITE ,{ CMD_GAMEDB_FILE, true, "game-db-file", @@ -1736,6 +1738,9 @@ main( int argc, char** argv ) case CMD_GAMEFILE: mainParams.fileName = optarg; break; + case CMD_DBFILE: + mainParams.dbName = optarg; + break; case CMD_SAVEFAIL_PCT: mainParams.saveFailPct = atoi( optarg ); break; diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index a9870b57a..866b1e503 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -50,6 +50,7 @@ typedef struct LaunchParams { GSList* dictDirs; char* fileName; + char* dbName; XP_U16 saveFailPct; const XP_UCHAR* playerDictNames[MAX_NUM_PLAYERS]; #ifdef USE_SQLITE @@ -162,6 +163,8 @@ typedef struct _TimerInfo { #endif } TimerInfo; +typedef void (*FirstSaveFunc)(void* closure); + struct CommonGlobals { LaunchParams* params; CommonPrefs cp; @@ -169,6 +172,7 @@ struct CommonGlobals { XWGame game; CurGameInfo gi; + CommsAddrRec addr; DictionaryCtxt* dict; PlayerDicts dicts; XP_U16 lastNTilesToUse; @@ -179,6 +183,8 @@ struct CommonGlobals { SocketChangedFunc socketChanged; void* socketChangedClosure; + FirstSaveFunc firstSave; + void* firstSaveClosure; CommsRelayState state; @@ -216,6 +222,7 @@ typedef struct _GTKGamesGlobals { sqlite3_int64 selRow; LaunchParams* params; GSList* globalsList; + GtkWidget* listWidget; } GTKGamesGlobals; #endif From 76f3bb341356c9e7130f538ac96cdd1ff7aa7ab5 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 7 Jan 2013 20:40:46 -0800 Subject: [PATCH 009/116] fix curses version so discon_ok2 tests work again. --- xwords4/linux/cursesmain.c | 14 ++++++++ xwords4/linux/gtkboard.c | 20 +++-------- xwords4/linux/linuxdict.h | 29 +++++++++++++++ xwords4/linux/linuxmain.c | 74 ++++++++++++++++++++++++++++++++++++++ xwords4/linux/linuxmain.h | 5 +++ xwords4/linux/linuxutl.c | 1 + xwords4/linux/linuxutl.h | 4 +-- 7 files changed, 128 insertions(+), 19 deletions(-) create mode 100644 xwords4/linux/linuxdict.h diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index 7107d9126..fa937ec14 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -40,6 +40,7 @@ #include "linuxmain.h" #include "linuxutl.h" +#include "linuxdict.h" #include "cursesmain.h" #include "cursesask.h" #include "cursesletterask.h" @@ -1724,8 +1725,11 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) g_globals.cGlobals.cp.robotTradePct = params->robotTradePct; #endif + setupUtil( &g_globals.cGlobals ); setupCursesUtilCallbacks( &g_globals, g_globals.cGlobals.util ); + initFromParams( &g_globals.cGlobals, params ); + #ifdef XWFEATURE_RELAY if ( params->conType == COMMS_CONN_RELAY ) { g_globals.cGlobals.defaultServerName @@ -1789,6 +1793,9 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) } if ( !!stream ) { + if ( NULL == cGlobals->dict ) { + cGlobals->dict = makeDictForStream( cGlobals, stream ); + } (void)game_makeFromStream( MEMPOOL stream, &cGlobals->game, &cGlobals->gi, cGlobals->dict, &cGlobals->dicts, cGlobals->util, @@ -1844,6 +1851,11 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) } #endif + if ( NULL == cGlobals->dict ) { + cGlobals->dict = + linux_dictionary_make( MEMPOOL params, + cGlobals->gi.dictName, XP_TRUE ); + } model_setDictionary( cGlobals->game.model, cGlobals->dict ); setSquareBonuses( cGlobals ); positionSizeStuff( &g_globals, width, height ); @@ -1910,5 +1922,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) #endif endwin(); + + linux_util_vt_destroy( g_globals.cGlobals.util ); } /* cursesmain */ #endif /* PLATFORM_NCURSES */ diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 58f21c649..ee2db63e4 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -59,6 +59,7 @@ #include "gtkpasswdask.h" #include "gtkntilesask.h" #include "gtkaskdict.h" +#include "linuxdict.h" /* #include "undo.h" */ #include "gtkdraw.h" #include "memstream.h" @@ -434,16 +435,7 @@ createOrLoadObjects( GtkAppGlobals* globals ) if ( !!stream ) { if ( NULL == cGlobals->dict ) { - CurGameInfo gi = {0}; - XWStreamPos pos = stream_getPos( stream, POS_READ ); - if ( !game_makeFromStream( MEMPOOL stream, NULL, &gi, NULL, NULL, - NULL, NULL, NULL, NULL ) ) { - XP_ASSERT(0); - } - stream_setPos( stream, POS_READ, pos ); - cGlobals->dict = linux_dictionary_make( MEMPOOL params, gi.dictName, XP_TRUE ); - gi_disposePlayerInfo( MEMPOOL &gi ); - XP_ASSERT( !!cGlobals->dict ); + cGlobals->dict = makeDictForStream( cGlobals, stream ); } opened = game_makeFromStream( MEMPOOL stream, &cGlobals->game, @@ -2405,12 +2397,8 @@ initGlobals( GtkAppGlobals* globals, LaunchParams* params ) globals->cGlobals.cp.hideCrosshairs = params->hideCrosshairs; #endif - XW_UtilCtxt* util = calloc( 1, sizeof(*globals->cGlobals.util) ); - globals->cGlobals.util = util; - linux_util_vt_init( MPPARM(params->mpool) util ); - util->gameInfo = &globals->cGlobals.gi; - setupLinuxUtilCallbacks( util ); - setupGtkUtilCallbacks( globals, util ); + setupUtil( &globals->cGlobals ); + setupGtkUtilCallbacks( globals, globals->cGlobals.util ); globals->window = window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); if ( !!params->fileName ) { diff --git a/xwords4/linux/linuxdict.h b/xwords4/linux/linuxdict.h new file mode 100644 index 000000000..0e111ce90 --- /dev/null +++ b/xwords4/linux/linuxdict.h @@ -0,0 +1,29 @@ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2000 - 2013 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program 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 program 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. + */ + + +#ifndef _LINUXDICT_H_ +#define _LINUXDICT_H_ + +DictionaryCtxt* linux_dictionary_make( MPFORMAL + const LaunchParams* mainParams, + const char* dictFileName, + XP_Bool useMMap ); +#endif diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index ab2467f26..19e662b7a 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -58,6 +58,7 @@ #include "dictiter.h" #include "main.h" #include "gamesdb.h" +#include "linuxdict.h" #ifdef PLATFORM_NCURSES # include "cursesmain.h" #endif @@ -151,6 +152,25 @@ streamFromDB( CommonGlobals* cGlobals, void* closure ) } #endif +DictionaryCtxt* +makeDictForStream( CommonGlobals* cGlobals, XWStreamCtxt* stream ) +{ + CurGameInfo gi = {0}; + XWStreamPos pos = stream_getPos( stream, POS_READ ); + if ( !game_makeFromStream( MPPARM(cGlobals->util->mpool) stream, NULL, &gi, + NULL, NULL, NULL, NULL, NULL, NULL ) ) { + XP_ASSERT(0); + } + stream_setPos( stream, POS_READ, pos ); + + DictionaryCtxt* dict = + linux_dictionary_make( MPPARM(cGlobals->util->mpool) cGlobals->params, + gi.dictName, XP_TRUE ); + gi_disposePlayerInfo( MPPARM(cGlobals->util->mpool) &gi ); + XP_ASSERT( !!dict ); + return dict; +} + void writeToFile( XWStreamCtxt* stream, void* closure ) { @@ -1523,6 +1543,60 @@ setupLinuxUtilCallbacks( XW_UtilCtxt* util ) #endif } +/* Set up cGlobals->gi and cGlobals->addr based on params fields */ +void +initFromParams( CommonGlobals* cGlobals, LaunchParams* params ) +{ + LOG_FUNC(); + /* CurGameInfo */ + cGlobals->gi = params->pgi; + + /* addr */ + CommsAddrRec* addr = &cGlobals->addr; + XP_MEMSET( addr, 0, sizeof(*addr) ); + if ( 0 ) { +#ifdef XWFEATURE_RELAY + } else if ( params->conType == COMMS_CONN_RELAY ) { + addr->conType = COMMS_CONN_RELAY; + addr->u.ip_relay.ipAddr = 0; /* ??? */ + addr->u.ip_relay.port = params->connInfo.relay.defaultSendPort; + addr->u.ip_relay.seeksPublicRoom = + params->connInfo.relay.seeksPublicRoom; + addr->u.ip_relay.advertiseRoom = params->connInfo.relay.advertiseRoom; + XP_STRNCPY( addr->u.ip_relay.hostName, + params->connInfo.relay.relayName, + sizeof(addr->u.ip_relay.hostName) - 1 ); + XP_STRNCPY( addr->u.ip_relay.invite, params->connInfo.relay.invite, + sizeof(addr->u.ip_relay.invite) - 1 ); +#endif +#ifdef XWFEATURE_SMS + } else if ( params->conType == COMMS_CONN_SMS ) { + addr->conType = COMMS_CONN_SMS; + XP_STRNCPY( addr->u.sms.phone, params->connInfo.sms.serverPhone, + sizeof(addr->u.sms.phone) - 1 ); + addr->u.sms.port = params->connInfo.sms.port; +#endif +#ifdef XWFEATURE_BLUETOOTH + } else if ( params->conType == COMMS_CONN_BT ) { + addr->conType = COMMS_CONN_BT; + XP_ASSERT( sizeof(addr->u.bt.btAddr) + >= sizeof(params->connInfo.bt.hostAddr)); + XP_MEMCPY( &addr->u.bt.btAddr, ¶ms->connInfo.bt.hostAddr, + sizeof(params->connInfo.bt.hostAddr) ); +#endif + } +} + +void +setupUtil( CommonGlobals* cGlobals ) +{ + XW_UtilCtxt* util = calloc( 1, sizeof(*util) ); + cGlobals->util = util; + linux_util_vt_init( MPPARM(cGlobals->params->mpool) util ); + util->gameInfo = &cGlobals->gi; + setupLinuxUtilCallbacks( util ); +} + static void initParams( LaunchParams* params ) { diff --git a/xwords4/linux/linuxmain.h b/xwords4/linux/linuxmain.h index 9a4721619..77b08d3ad 100644 --- a/xwords4/linux/linuxmain.h +++ b/xwords4/linux/linuxmain.h @@ -95,6 +95,11 @@ void setOneSecondTimer( CommonGlobals* cGlobals ); #endif void setupLinuxUtilCallbacks( XW_UtilCtxt* util ); +void initFromParams( CommonGlobals* cGlobals, LaunchParams* params ); +void setupUtil( CommonGlobals* cGlobals ); + +DictionaryCtxt* makeDictForStream( CommonGlobals* cGlobals, + XWStreamCtxt* stream ); /* void initParams( LaunchParams* params ); */ /* void freeParams( LaunchParams* params ); */ diff --git a/xwords4/linux/linuxutl.c b/xwords4/linux/linuxutl.c index 59d1d96c2..4cfba5a22 100644 --- a/xwords4/linux/linuxutl.c +++ b/xwords4/linux/linuxutl.c @@ -31,6 +31,7 @@ #include "linuxutl.h" #include "main.h" +#include "linuxdict.h" #include "LocalizedStrIncludes.h" #ifdef DEBUG diff --git a/xwords4/linux/linuxutl.h b/xwords4/linux/linuxutl.h index 781031a32..91ea0ed42 100644 --- a/xwords4/linux/linuxutl.h +++ b/xwords4/linux/linuxutl.h @@ -32,9 +32,6 @@ void linux_debugf(const char*, ...) __attribute__ ((format (printf, 1, 2))); #endif -DictionaryCtxt* linux_dictionary_make( MPFORMAL const LaunchParams* mainParams, - const char* dictFileName, XP_Bool useMMap ); - void linux_util_vt_init( MPFORMAL XW_UtilCtxt* util ); void linux_util_vt_destroy( XW_UtilCtxt* util ); @@ -49,6 +46,7 @@ XP_Bool storeNoConnMsg( CommonGlobals* cGlobals, const XP_U8* msg, XP_U16 len, const XP_UCHAR* relayID ); void writeNoConnMsgs( CommonGlobals* cGlobals, int fd ); + #ifdef STREAM_VERS_BIGBOARD void setSquareBonuses( const CommonGlobals* cGlobals ); #else From 1cb959c86d13a1390abe1bc9a6e931370b63d2bc Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 7 Jan 2013 20:51:59 -0800 Subject: [PATCH 010/116] type tree model column correctly --- xwords4/linux/gtkmain.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index e8c8411c9..f61c712b3 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -56,18 +56,13 @@ enum { ROW_ITEM, NAME_ITEM, N_ITEMS }; static void tree_selection_changed_cb( GtkTreeSelection* selection, gpointer data ) { - LOG_FUNC(); GTKGamesGlobals* gg = (GTKGamesGlobals*)data; GtkTreeIter iter; GtkTreeModel *model; - gchar *row; if ( gtk_tree_selection_get_selected( selection, &model, &iter ) ) { - gtk_tree_model_get( model, &iter, ROW_ITEM, &row, -1 ); - sscanf( row, "%lld", &gg->selRow ); - g_print ("You selected row %s (parsed: %lld)\n", row, gg->selRow ); - g_free( row ); + gtk_tree_model_get( model, &iter, ROW_ITEM, &gg->selRow, -1 ); } } @@ -86,8 +81,8 @@ init_games_list( GTKGamesGlobals* gg ) NAME_ITEM, NULL ); gtk_tree_view_append_column( GTK_TREE_VIEW(list), column ); - GtkListStore* store = gtk_list_store_new( N_ITEMS, // G_TYPE_INT64, - G_TYPE_STRING, G_TYPE_STRING ); + GtkListStore* store = gtk_list_store_new( N_ITEMS, G_TYPE_INT64, + G_TYPE_STRING ); gtk_tree_view_set_model( GTK_TREE_VIEW(list), GTK_TREE_MODEL(store) ); g_object_unref( store ); @@ -106,11 +101,11 @@ add_to_list( GtkWidget* list, sqlite3_int64* rowid, const gchar* str ) GTK_LIST_STORE( gtk_tree_view_get_model(GTK_TREE_VIEW(list))); GtkTreeIter iter; gtk_list_store_append( store, &iter ); - XP_LOGF( "adding %lld, %s", *rowid, str ); - gchar buf[16]; - snprintf( buf, sizeof(buf), "%lld", *rowid ); + /* XP_LOGF( "adding %lld, %s", *rowid, str ); */ + /* gchar buf[16]; */ + /* snprintf( buf, sizeof(buf), "%lld", *rowid ); */ gtk_list_store_set( store, &iter, - ROW_ITEM, buf, + ROW_ITEM, *rowid, NAME_ITEM, str, -1 ); XP_LOGF( "DONE adding" ); From 5e2bfd20dafd188fa513bee2bff776b7460f7507 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 8 Jan 2013 06:32:43 -0800 Subject: [PATCH 011/116] add a checkbox -- which apparently won't interact until I "implement the 'activate' virtual method". --- xwords4/linux/gtkmain.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index f61c712b3..c33d4298c 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -50,10 +50,9 @@ gameIsOpen( GTKGamesGlobals* gg, sqlite3_int64 rowid ) return found; } -enum { ROW_ITEM, NAME_ITEM, N_ITEMS }; +enum { CHECK_ITEM, ROW_ITEM, NAME_ITEM, N_ITEMS }; -/* Prototype for selection handler callback */ -static void +static void tree_selection_changed_cb( GtkTreeSelection* selection, gpointer data ) { GTKGamesGlobals* gg = (GTKGamesGlobals*)data; @@ -70,8 +69,19 @@ static GtkWidget* init_games_list( GTKGamesGlobals* gg ) { GtkWidget* list = gtk_tree_view_new(); - GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); - GtkTreeViewColumn* column = + GtkCellRenderer* renderer; + GtkTreeViewColumn* column; + + renderer = gtk_cell_renderer_toggle_new(); + /* gtk_cell_renderer_toggle_set_activatable( GTK_CELL_RENDERER_TOGGLE(renderer), */ + /* TRUE ); */ + column = + gtk_tree_view_column_new_with_attributes( "", renderer, "active", + CHECK_ITEM, NULL ); + gtk_tree_view_append_column( GTK_TREE_VIEW(list), column ); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes( "Row", renderer, "text", ROW_ITEM, NULL ); gtk_tree_view_append_column( GTK_TREE_VIEW(list), column ); @@ -81,12 +91,12 @@ init_games_list( GTKGamesGlobals* gg ) NAME_ITEM, NULL ); gtk_tree_view_append_column( GTK_TREE_VIEW(list), column ); - GtkListStore* store = gtk_list_store_new( N_ITEMS, G_TYPE_INT64, + GtkListStore* store = gtk_list_store_new( N_ITEMS, G_TYPE_BOOLEAN, G_TYPE_INT64, G_TYPE_STRING ); gtk_tree_view_set_model( GTK_TREE_VIEW(list), GTK_TREE_MODEL(store) ); g_object_unref( store ); - GtkTreeSelection* select = + GtkTreeSelection* select = gtk_tree_view_get_selection( GTK_TREE_VIEW (list) ); gtk_tree_selection_set_mode( select, GTK_SELECTION_SINGLE ); g_signal_connect( G_OBJECT(select), "changed", From fac3e6b67839b85d83c9e349d4f81dacdd4b4536 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 8 Jan 2013 07:28:30 -0800 Subject: [PATCH 012/116] fix gtk version to correctly implement util_setIsServer (which only Android did so far) by calling server_initClientConnection(). Now relay games work with both started as hosts. (GTK UI prevents starting one as a guest; cmdline is required for that, if it still works.) --- xwords4/linux/cursesmain.c | 10 ++++++++++ xwords4/linux/gtkboard.c | 19 ++++++++++++++++++- xwords4/linux/linuxmain.c | 22 +++++++++++++++++----- xwords4/linux/linuxmain.h | 2 ++ 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index fa937ec14..73c1e1e3e 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -407,6 +407,14 @@ curses_util_informNetDict( XW_UtilCtxt* uc, XP_LangCode XP_UNUSED(lang), XP_LOGF( "%s: %s => %s (cksum: %s)", __func__, oldName, newName, newSum ); } +static void +curses_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer ) +{ + LOG_FUNC(); + CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; + linuxSetIsServer( cGlobals, isServer ); +} + #ifdef XWFEATURE_HILITECELL static XP_Bool curses_util_hiliteCell( XW_UtilCtxt* uc, @@ -1526,6 +1534,8 @@ setupCursesUtilCallbacks( CursesAppGlobals* globals, XW_UtilCtxt* util ) util->vtable->m_util_informUndo = curses_util_informUndo; util->vtable->m_util_notifyGameOver = curses_util_notifyGameOver; util->vtable->m_util_informNetDict = curses_util_informNetDict; + util->vtable->m_util_setIsServer = curses_util_setIsServer; + #ifdef XWFEATURE_HILITECELL util->vtable->m_util_hiliteCell = curses_util_hiliteCell; #endif diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index ee2db63e4..ac1fd849f 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -1534,6 +1534,22 @@ gtk_util_informNetDict( XW_UtilCtxt* uc, XP_LangCode XP_UNUSED(lang), (void)gtkask( globals->window, buf, GTK_BUTTONS_OK ); } +static gint +changeRoles( gpointer data ) +{ + linuxChangeRoles( (CommonGlobals*)data ); + return 0; +} + +static void +gtk_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer ) +{ + CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; + linuxSetIsServer( cGlobals, isServer ); + + (void)g_idle_add( changeRoles, cGlobals ); +} + /* define this to prevent user events during debugging from stopping the engine */ /* #define DONT_ABORT_ENGINE */ @@ -1604,7 +1620,7 @@ pen_timer_func( gpointer data ) } return XP_FALSE; -} /* pentimer_idle_func */ +} /* pen_timer_func */ static gint score_timer_func( gpointer data ) @@ -2061,6 +2077,7 @@ setupGtkUtilCallbacks( GtkAppGlobals* globals, XW_UtilCtxt* util ) util->vtable->m_util_informUndo = gtk_util_informUndo; util->vtable->m_util_notifyGameOver = gtk_util_notifyGameOver; util->vtable->m_util_informNetDict = gtk_util_informNetDict; + util->vtable->m_util_setIsServer = gtk_util_setIsServer; #ifdef XWFEATURE_HILITECELL util->vtable->m_util_hiliteCell = gtk_util_hiliteCell; #endif diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 19e662b7a..0d7b01568 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -1152,15 +1152,28 @@ linux_util_addrChange( XW_UtilCtxt* uc, } } -static void -linux_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer ) +void +linuxSetIsServer( CommonGlobals* cGlobals, XP_Bool isServer ) { - XP_LOGF( "%s(%d)", __func__, isServer ); - CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; + XP_LOGF( "%s(isServer=%d)", __func__, isServer ); DeviceRole newRole = isServer? SERVER_ISSERVER : SERVER_ISCLIENT; cGlobals->params->serverRole = newRole; cGlobals->gi.serverRole = newRole; } + +void +linuxChangeRoles( CommonGlobals* cGlobals ) +{ + ServerCtxt* server = cGlobals->game.server; + server_reset( server, cGlobals->game.comms ); + if ( SERVER_ISCLIENT == cGlobals->gi.serverRole ) { + XWStreamCtxt* stream = + mem_stream_make( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr, + cGlobals, CHANNEL_NONE, sendOnClose ); + server_initClientConnection( server, stream ); + } + (void)server_do( server ); +} #endif static unsigned int @@ -1539,7 +1552,6 @@ setupLinuxUtilCallbacks( XW_UtilCtxt* util ) #ifndef XWFEATURE_STANDALONE_ONLY util->vtable->m_util_informMissing = linux_util_informMissing; util->vtable->m_util_addrChange = linux_util_addrChange; - util->vtable->m_util_setIsServer = linux_util_setIsServer; #endif } diff --git a/xwords4/linux/linuxmain.h b/xwords4/linux/linuxmain.h index 77b08d3ad..1519a5182 100644 --- a/xwords4/linux/linuxmain.h +++ b/xwords4/linux/linuxmain.h @@ -100,6 +100,8 @@ void setupUtil( CommonGlobals* cGlobals ); DictionaryCtxt* makeDictForStream( CommonGlobals* cGlobals, XWStreamCtxt* stream ); +void linuxSetIsServer( CommonGlobals* cGlobals, XP_Bool isServer ); +void linuxChangeRoles( CommonGlobals* cGlobals ); /* void initParams( LaunchParams* params ); */ /* void freeParams( LaunchParams* params ); */ From 7437a71aa644257b23180c16f32ce2e0e0ee301c Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 9 Jan 2013 06:29:12 -0800 Subject: [PATCH 013/116] use string_printf --- xwords4/relay/cidlock.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/xwords4/relay/cidlock.cpp b/xwords4/relay/cidlock.cpp index 854fadf3e..510af5a9d 100644 --- a/xwords4/relay/cidlock.cpp +++ b/xwords4/relay/cidlock.cpp @@ -52,9 +52,9 @@ CidLock::~CidLock() void CidLock::print_claimed( const char* caller ) { - char buf[512] = {0}; int unclaimed = 0; - int len = snprintf( buf, sizeof(buf), "after %s: ", caller ); + string str; + string_printf( str, "after %s: ", caller ); // Assume we have the mutex!!!! map< CookieID, CidInfo*>::iterator iter; for ( iter = m_infos.begin(); iter != m_infos.end(); ++iter ) { @@ -62,13 +62,11 @@ CidLock::print_claimed( const char* caller ) if ( 0 == info->GetOwner() ) { ++unclaimed; } else { - len += snprintf( &buf[len], sizeof(buf)-len, "%d,", - info->GetCid() ); + string_printf( str, "%d,", info->GetCid() ); } } - len += snprintf( &buf[len], sizeof(buf)-len, " (plus %d unclaimed.)", - unclaimed ); - logf( XW_LOGINFO, "%s: claimed: %s", __func__, buf ); + string_printf( str, "%d,", " (plus %d unclaimed.)", unclaimed ); + logf( XW_LOGINFO, "%s: claimed: %s", __func__, str.c_str() ); } #else # define PRINT_CLAIMED() From dddb135b9d23811114a6b6805e080bc82c27f8db Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 9 Jan 2013 06:30:52 -0800 Subject: [PATCH 014/116] add more columns, and update each time game is saved --- xwords4/linux/gamesdb.c | 82 ++++++++++++++++++++++++++++--- xwords4/linux/gamesdb.h | 14 +++++- xwords4/linux/gtkboard.c | 4 +- xwords4/linux/gtkmain.c | 100 +++++++++++++++++++++++++------------- xwords4/linux/gtkmain.h | 2 +- xwords4/linux/linuxmain.c | 4 ++ xwords4/linux/main.h | 7 +-- 7 files changed, 163 insertions(+), 50 deletions(-) diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c index bd4b7a146..bf33cdd06 100644 --- a/xwords4/linux/gamesdb.c +++ b/xwords4/linux/gamesdb.c @@ -1,4 +1,4 @@ -/* -*-mode: compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ /* * Copyright 2000-2013 by Eric House (xwords@eehouse.org). All rights * reserved. @@ -21,6 +21,10 @@ #include "gamesdb.h" #include "main.h" +static void getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, + int len ); + + sqlite3* openGamesDB( const char* dbName ) { @@ -32,6 +36,10 @@ openGamesDB( const char* dbName ) "CREATE TABLE games ( " "game BLOB" ",room VARCHAR(32)" + ",ended INT(1)" + ",turn INT(2)" + ",nmoves INT" + ",nmissing INT(2)" ")"; result = sqlite3_exec( pDb, createStr, NULL, NULL, NULL ); @@ -95,9 +103,39 @@ writeToDB( XWStreamCtxt* stream, void* closure ) sqlite3_finalize( stmt ); } - if ( newGame ) { - (*cGlobals->firstSave)( cGlobals->firstSaveClosure ); + (*cGlobals->onSave)( cGlobals->onSaveClosure, selRow, newGame ); +} + +void +summarize( CommonGlobals* cGlobals ) +{ + XP_S16 nMoves = model_getNMoves( cGlobals->game.model ); + XP_Bool gameOver = server_getGameIsOver( cGlobals->game.server ); + XP_S16 turn = server_getCurrentTurn( cGlobals->game.server ); + XP_S16 nMissing = 0; + CommsAddrRec addr = {0}; + gchar* room = ""; + + if ( !!cGlobals->game.comms ) { + nMissing = server_getMissingPlayers( cGlobals->game.server ); + comms_getAddr( cGlobals->game.comms, &addr ); + if ( COMMS_CONN_RELAY == addr.conType ) { + room = addr.u.ip_relay.invite; + } } + + const char* fmt = "UPDATE games SET room='%s', ended=%d, turn=%d, nmissing=%d, nmoves=%d" + " where rowid=%lld"; + XP_UCHAR buf[256]; + snprintf( buf, sizeof(buf), fmt, room, gameOver?1:0, turn, nMissing, nMoves, + cGlobals->selRow ); + XP_LOGF( "query: %s", buf ); + sqlite3_stmt* stmt = NULL; + int result = sqlite3_prepare_v2( cGlobals->pDb, buf, -1, &stmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( stmt ); + XP_ASSERT( SQLITE_DONE == result ); + sqlite3_finalize( stmt ); } GSList* @@ -132,12 +170,30 @@ listGames( GTKGamesGlobals* gg ) return list; } -void -getGameName( GTKGamesGlobals* XP_UNUSED(gg), const sqlite3_int64* rowid, - XP_UCHAR* buf, XP_U16 len ) +XP_Bool +getGameInfo( GTKGamesGlobals* gg, sqlite3_int64 rowid, GameInfo* gib ) { - snprintf( buf, len, "Game %lld", *rowid ); - LOG_RETURNF( "%s", buf ); + XP_Bool success = XP_FALSE; + const char* fmt = "SELECT room, ended, turn, nmoves, nmissing " + "FROM games WHERE rowid = %lld"; + XP_UCHAR query[256]; + snprintf( query, sizeof(query), fmt, rowid ); + + sqlite3_stmt* ppStmt; + int result = sqlite3_prepare_v2( gg->pDb, query, -1, &ppStmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( ppStmt ); + if ( SQLITE_ROW == result ) { + success = XP_TRUE; + getColumnText( ppStmt, 0, gib->room, sizeof(gib->room) ); + gib->gameOver = 1 == sqlite3_column_int( ppStmt, 1 ); + gib->turn = sqlite3_column_int( ppStmt, 2 ); + gib->nMoves = sqlite3_column_int( ppStmt, 3 ); + gib->nMissing = sqlite3_column_int( ppStmt, 4 ); + snprintf( gib->name, sizeof(gib->name), "Game %lld", rowid ); + } + sqlite3_finalize( ppStmt ); + return success; } XP_Bool @@ -157,3 +213,13 @@ loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ) sqlite3_finalize( ppStmt ); return XP_TRUE; } + +static void +getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, int len ) +{ + const unsigned char* txt = sqlite3_column_text( ppStmt, iCol ); + int needLen = sqlite3_column_bytes( ppStmt, iCol ); + XP_ASSERT( needLen < len ); + XP_MEMCPY( buf, txt, needLen ); + buf[needLen] = '\0'; +} diff --git a/xwords4/linux/gamesdb.h b/xwords4/linux/gamesdb.h index 73f5b9a2d..aa51c50de 100644 --- a/xwords4/linux/gamesdb.h +++ b/xwords4/linux/gamesdb.h @@ -27,15 +27,25 @@ #include "main.h" #include "comtypes.h" +typedef struct _GameInfo { + XP_UCHAR name[128]; + XP_UCHAR room[128]; + XP_S16 nMoves; + XP_Bool gameOver; + XP_S16 turn; + XP_S16 nMissing; +} GameInfo; + + sqlite3* openGamesDB( const char* dbName ); void closeGamesDB( sqlite3* dbp ); void writeToDB( XWStreamCtxt* stream, void* closure ); +void summarize( CommonGlobals* cGlobals ); /* Return GSList whose data is (ptrs to) rowids */ GSList* listGames( GTKGamesGlobals* gg ); -void getGameName( GTKGamesGlobals* gg, const sqlite3_int64* rowid, - XP_UCHAR* buf, XP_U16 len ); +XP_Bool getGameInfo( GTKGamesGlobals* gg, sqlite3_int64 rowid, GameInfo* gib ); XP_Bool loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ); #endif diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index ac1fd849f..71b60b1a9 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -2393,8 +2393,8 @@ initGlobals( GtkAppGlobals* globals, LaunchParams* params ) globals->cGlobals.socketChanged = gtk_socket_changed; globals->cGlobals.socketChangedClosure = globals; - globals->cGlobals.firstSave = newGameSaved; - globals->cGlobals.firstSaveClosure = globals; + globals->cGlobals.onSave = onGameSaved; + globals->cGlobals.onSaveClosure = globals; globals->cGlobals.addAcceptor = gtk_socket_acceptor; #endif diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index c33d4298c..acb42ea4c 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -20,11 +20,15 @@ #ifdef PLATFORM_GTK +#include "xptypes.h" #include "main.h" #include "gamesdb.h" #include "gtkboard.h" #include "linuxmain.h" +static void onNewData( GTKGamesGlobals* gg, sqlite3_int64 rowid, + XP_Bool isNew ); + static void recordOpened( GTKGamesGlobals* gg, GtkAppGlobals* globals ) { @@ -50,7 +54,8 @@ gameIsOpen( GTKGamesGlobals* gg, sqlite3_int64 rowid ) return found; } -enum { CHECK_ITEM, ROW_ITEM, NAME_ITEM, N_ITEMS }; +enum { CHECK_ITEM, ROW_ITEM, NAME_ITEM, ROOM_ITEM, OVER_ITEM, TURN_ITEM, NMOVES_ITEM, MISSING_ITEM, + N_ITEMS }; static void tree_selection_changed_cb( GtkTreeSelection* selection, gpointer data ) @@ -65,6 +70,16 @@ tree_selection_changed_cb( GtkTreeSelection* selection, gpointer data ) } } +static void +addTextColumn( GtkWidget* list, const gchar* title, int item ) +{ + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn* column = + gtk_tree_view_column_new_with_attributes( title, renderer, "text", + item, NULL ); + gtk_tree_view_append_column( GTK_TREE_VIEW(list), column ); +} + static GtkWidget* init_games_list( GTKGamesGlobals* gg ) { @@ -73,26 +88,24 @@ init_games_list( GTKGamesGlobals* gg ) GtkTreeViewColumn* column; renderer = gtk_cell_renderer_toggle_new(); - /* gtk_cell_renderer_toggle_set_activatable( GTK_CELL_RENDERER_TOGGLE(renderer), */ - /* TRUE ); */ column = gtk_tree_view_column_new_with_attributes( "", renderer, "active", CHECK_ITEM, NULL ); gtk_tree_view_append_column( GTK_TREE_VIEW(list), column ); - renderer = gtk_cell_renderer_text_new(); - column = - gtk_tree_view_column_new_with_attributes( "Row", renderer, "text", - ROW_ITEM, NULL ); - gtk_tree_view_append_column( GTK_TREE_VIEW(list), column ); + addTextColumn( list, "Row", ROW_ITEM ); + addTextColumn( list, "Name", NAME_ITEM ); + addTextColumn( list, "Room", ROOM_ITEM ); + addTextColumn( list, "Ended", OVER_ITEM ); + addTextColumn( list, "Turn", TURN_ITEM ); + addTextColumn( list, "NMoves", NMOVES_ITEM ); + addTextColumn( list, "NMissing", MISSING_ITEM ); - renderer = gtk_cell_renderer_text_new(); - column = gtk_tree_view_column_new_with_attributes( "Name", renderer, "text", - NAME_ITEM, NULL ); - gtk_tree_view_append_column( GTK_TREE_VIEW(list), column ); - - GtkListStore* store = gtk_list_store_new( N_ITEMS, G_TYPE_BOOLEAN, G_TYPE_INT64, - G_TYPE_STRING ); + GtkListStore* store = gtk_list_store_new( N_ITEMS, G_TYPE_BOOLEAN, + G_TYPE_INT64, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_BOOLEAN, + G_TYPE_INT, G_TYPE_INT, + G_TYPE_INT ); gtk_tree_view_set_model( GTK_TREE_VIEW(list), GTK_TREE_MODEL(store) ); g_object_unref( store ); @@ -105,18 +118,34 @@ init_games_list( GTKGamesGlobals* gg ) } static void -add_to_list( GtkWidget* list, sqlite3_int64* rowid, const gchar* str ) +add_to_list( GtkWidget* list, sqlite3_int64 rowid, XP_Bool isNew, + const GameInfo* gib ) { - GtkListStore* store = - GTK_LIST_STORE( gtk_tree_view_get_model(GTK_TREE_VIEW(list))); + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list)); + GtkListStore* store = GTK_LIST_STORE( model ); GtkTreeIter iter; - gtk_list_store_append( store, &iter ); - /* XP_LOGF( "adding %lld, %s", *rowid, str ); */ - /* gchar buf[16]; */ - /* snprintf( buf, sizeof(buf), "%lld", *rowid ); */ + if ( isNew ) { + gtk_list_store_append( store, &iter ); + } else { + gboolean valid; + for ( valid = gtk_tree_model_get_iter_first( model, &iter ); + valid; + valid = gtk_tree_model_iter_next( model, &iter ) ) { + sqlite3_int64 tmpid; + gtk_tree_model_get( model, &iter, ROW_ITEM, &tmpid, -1 ); + if ( tmpid == rowid ) { + break; + } + } + } gtk_list_store_set( store, &iter, - ROW_ITEM, *rowid, - NAME_ITEM, str, + ROW_ITEM, rowid, + NAME_ITEM, gib->name, + ROOM_ITEM, gib->room, + OVER_ITEM, gib->gameOver, + TURN_ITEM, gib->turn, + NMOVES_ITEM, gib->nMoves, + MISSING_ITEM, gib->nMissing, -1 ); XP_LOGF( "DONE adding" ); } @@ -207,10 +236,8 @@ makeGamesWindow( GTKGamesGlobals* gg ) GSList* games = listGames( gg ); for ( GSList* iter = games; !!iter; iter = iter->next ) { - XP_UCHAR name[128]; sqlite3_int64* rowid = (sqlite3_int64*)iter->data; - getGameName( gg, rowid, name, VSIZE(name) ); - add_to_list( list, rowid, name ); + onNewData( gg, *rowid, XP_TRUE ); } g_slist_free( games ); @@ -244,15 +271,22 @@ windowDestroyed( GtkAppGlobals* globals ) (void)g_idle_add( freeGameGlobals, globals ); } +static void +onNewData( GTKGamesGlobals* gg, sqlite3_int64 rowid, XP_Bool isNew ) +{ + GameInfo gib; + if ( getGameInfo( gg, rowid, &gib ) ) { + add_to_list( gg->listWidget, rowid, isNew, &gib ); + } +} + void -newGameSaved( void* closure ) +onGameSaved( void* closure, sqlite3_int64 rowid, + XP_Bool firstTime ) { GtkAppGlobals* globals = (GtkAppGlobals*)closure; GTKGamesGlobals* gg = globals->gg; - CommonGlobals* cGlobals = &globals->cGlobals; - XP_UCHAR buf[128]; - getGameName( gg, &cGlobals->selRow, buf, sizeof(buf) ); - add_to_list( gg->listWidget, &cGlobals->selRow, buf ); + onNewData( gg, rowid, firstTime ); } int @@ -261,7 +295,6 @@ gtkmain( LaunchParams* params ) GTKGamesGlobals gg = {0}; gg.selRow = -1; gg.params = params; - XP_LOGF( "%s: I'M HERE!!! (calling makeGamesDB())", __func__ ); gg.pDb = openGamesDB( params->dbName ); (void)makeGamesWindow( &gg ); @@ -269,7 +302,6 @@ gtkmain( LaunchParams* params ) closeGamesDB( gg.pDb ); - XP_LOGF( "%s: I'M BACK!!!", __func__ ); return 0; } /* gtkmain */ diff --git a/xwords4/linux/gtkmain.h b/xwords4/linux/gtkmain.h index 015b7d307..c7d5e13e3 100644 --- a/xwords4/linux/gtkmain.h +++ b/xwords4/linux/gtkmain.h @@ -25,6 +25,6 @@ int gtkmain( LaunchParams* params ); void windowDestroyed( GtkAppGlobals* globals ); -void newGameSaved( void* closure ); +void onGameSaved( void* closure, sqlite3_int64 rowid, XP_Bool firstTime ); #endif diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 0d7b01568..5a8e0d73a 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -307,6 +307,10 @@ saveGame( CommonGlobals* cGlobals ) game_saveSucceeded( &cGlobals->game, cGlobals->curSaveToken ); XP_LOGF( "%s: saved", __func__ ); + + if ( !!cGlobals->pDb ) { + summarize( cGlobals ); + } } else { XP_LOGF( "%s: simulating save failure", __func__ ); } diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index 866b1e503..8533bb2a2 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -163,7 +163,8 @@ typedef struct _TimerInfo { #endif } TimerInfo; -typedef void (*FirstSaveFunc)(void* closure); +typedef void (*OnSaveFunc)( void* closure, sqlite3_int64 rowid, + XP_Bool firstTime ); struct CommonGlobals { LaunchParams* params; @@ -183,8 +184,8 @@ struct CommonGlobals { SocketChangedFunc socketChanged; void* socketChangedClosure; - FirstSaveFunc firstSave; - void* firstSaveClosure; + OnSaveFunc onSave; + void* onSaveClosure; CommsRelayState state; From 7fec736947cfadf77b9b12fff6e810417284175f Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 15 Jan 2013 18:41:17 -0800 Subject: [PATCH 015/116] add udp socket and protocol for use over it so that a device can manage a single connection to the relay for all of its games. Works so far to the extent that the game's playable with all boards on the same device (with checkins about to come) as long as all boards are open. (Client doesn't handle opening closed games yet.) --- xwords4/relay/Makefile | 1 + xwords4/relay/addrinfo.cpp | 5 +- xwords4/relay/addrinfo.h | 29 +++-- xwords4/relay/cref.cpp | 12 +- xwords4/relay/cref.h | 4 +- xwords4/relay/dbmgr.cpp | 32 +++--- xwords4/relay/dbmgr.h | 3 +- xwords4/relay/devid.h | 9 ++ xwords4/relay/devmgr.cpp | 61 ++++++++++ xwords4/relay/devmgr.h | 54 +++++++++ xwords4/relay/scripts/showinplay.sh | 2 +- xwords4/relay/xwrelay.conf_tmplate | 4 + xwords4/relay/xwrelay.cpp | 165 ++++++++++++++++++++++++++-- xwords4/relay/xwrelay.h | 31 ++++++ xwords4/relay/xwrelay.sh | 1 + xwords4/relay/xwrelay_priv.h | 3 + 16 files changed, 370 insertions(+), 46 deletions(-) create mode 100644 xwords4/relay/devmgr.cpp create mode 100644 xwords4/relay/devmgr.h diff --git a/xwords4/relay/Makefile b/xwords4/relay/Makefile index 4d6f95188..1c4d12d37 100644 --- a/xwords4/relay/Makefile +++ b/xwords4/relay/Makefile @@ -31,6 +31,7 @@ SRC = \ tpool.cpp \ cidlock.cpp \ addrinfo.cpp \ + devmgr.cpp \ xwrelay.cpp \ # STATIC ?= -static diff --git a/xwords4/relay/addrinfo.cpp b/xwords4/relay/addrinfo.cpp index 6486517de..d92367719 100644 --- a/xwords4/relay/addrinfo.cpp +++ b/xwords4/relay/addrinfo.cpp @@ -32,7 +32,10 @@ AddrInfo::equals( const AddrInfo& other ) const if ( isTCP() ) { equal = m_socket == other.m_socket; } else { - assert(0); /* later.... */ + assert( m_socket == other.m_socket ); /* both same UDP socket */ + /* what does equal mean on udp addresses? Same host, or same host AND game */ + equal = m_clientToken == other.m_clientToken + && 0 == memcmp( &m_saddr, &other.m_saddr, sizeof(m_saddr) ); } } return equal; diff --git a/xwords4/relay/addrinfo.h b/xwords4/relay/addrinfo.h index 84e3230b6..0432b40c9 100644 --- a/xwords4/relay/addrinfo.h +++ b/xwords4/relay/addrinfo.h @@ -24,6 +24,7 @@ #include #include +#include class AddrInfo { public: @@ -32,31 +33,45 @@ class AddrInfo { struct sockaddr_in addr_in; } AddrUnion; + /* Those constructed without params are only valid after another copied on + top of it */ AddrInfo() { - memset( this, 0, sizeof(*this) ); - m_socket = -1; m_isValid = false; } - AddrInfo( bool isTCP, int socket, const AddrUnion* saddr ) { - m_isValid = true; - m_isTCP = isTCP; - m_socket = socket; - memcpy( &m_saddr, saddr, sizeof(m_saddr) ); + AddrInfo( int socket, const AddrUnion* saddr ) { + construct( socket, saddr, true ); + } + + AddrInfo( int socket, uint32_t clientToken, const AddrUnion* saddr ) { + construct( socket, saddr, false ); + m_clientToken = clientToken; } void setIsTCP( bool val ) { m_isTCP = val; } bool isTCP() const { return m_isTCP; } /* later UDP will be here too */ int socket() const { assert(m_isValid); return m_socket; } + uint32_t clientToken() const { assert(m_isValid); return m_clientToken; } struct in_addr sin_addr() const { return m_saddr.addr_in.sin_addr; } + const struct sockaddr* sockaddr() const { assert(m_isValid); return &m_saddr.addr; } bool equals( const AddrInfo& other ) const; private: + void construct( int socket, const AddrUnion* saddr, bool isTCP ) { + memset( this, 0, sizeof(*this) ); + + m_socket = socket; + m_isTCP = isTCP; + memcpy( &m_saddr, saddr, sizeof(m_saddr) ); + m_isValid = true; + } + // AddrInfo& operator=(const AddrInfo&); // Prevent assignment int m_socket; bool m_isTCP; bool m_isValid; + uint32_t m_clientToken; /* must be 32 bit */ AddrUnion m_saddr; }; diff --git a/xwords4/relay/cref.cpp b/xwords4/relay/cref.cpp index 9ca339383..bb44126ee 100644 --- a/xwords4/relay/cref.cpp +++ b/xwords4/relay/cref.cpp @@ -276,7 +276,7 @@ CookieRef::_PutMsg( HostID srcID, const AddrInfo* addr, HostID destID, void CookieRef::_Disconnect( const AddrInfo* addr, HostID hostID ) { - logf( XW_LOGINFO, "%s(socket=%d, hostID=%d)", __func__, socket, hostID ); + logf( XW_LOGINFO, "%s(hostID=%d)", __func__, hostID ); CRefEvent evt( XWE_DISCONN, addr ); evt.u.discon.srcID = hostID; @@ -636,7 +636,7 @@ CookieRef::handleEvents() /* Assumption: has mutex!!!! */ while ( m_eventQueue.size () > 0 ) { XW_RELAY_STATE nextState; - DBMgr::DevIDRelay devID; + DevIDRelay devID; CRefEvent evt = m_eventQueue.front(); m_eventQueue.pop_front(); @@ -848,7 +848,6 @@ CookieRef::send_stored_messages( HostID dest, const AddrInfo* addr ) assert( dest > 0 && dest <= 4 ); assert( -1 != addr->socket() ); - assert( addr->isTCP() ); for ( ; ; ) { unsigned char buf[MAX_MSG_LEN]; @@ -867,9 +866,9 @@ CookieRef::send_stored_messages( HostID dest, const AddrInfo* addr ) bool CookieRef::increasePlayerCounts( CRefEvent* evt, bool reconn, HostID* hidp, - DBMgr::DevIDRelay* devIDp ) + DevIDRelay* devIDp ) { - DBMgr::DevIDRelay devID = DBMgr::DEVID_NONE; + DevIDRelay devID = DBMgr::DEVID_NONE; int nPlayersH = evt->u.con.nPlayersH; int seed = evt->u.con.seed; @@ -1038,7 +1037,7 @@ CookieRef::cancelAllConnectedTimer() void CookieRef::sendResponse( const CRefEvent* evt, bool initial, - const DBMgr::DevIDRelay* devID ) + const DevIDRelay* devID ) { /* Now send the response */ unsigned char buf[1 /* cmd */ @@ -1191,7 +1190,6 @@ void CookieRef::notifyOthers( const AddrInfo* addr, XWRelayMsg msg, XWREASON why ) { assert( addr->socket() != 0 ); - assert( addr->isTCP() ); ASSERT_LOCKED(); RWReadLock rrl( &m_socketsRWLock ); diff --git a/xwords4/relay/cref.h b/xwords4/relay/cref.h index 5ff36326d..254c33a89 100644 --- a/xwords4/relay/cref.h +++ b/xwords4/relay/cref.h @@ -219,11 +219,11 @@ class CookieRef { void handleEvents(); void sendResponse( const CRefEvent* evt, bool initial, - const DBMgr::DevIDRelay* devID ); + const DevIDRelay* devID ); void sendAnyStored( const CRefEvent* evt ); void initPlayerCounts( const CRefEvent* evt ); bool increasePlayerCounts( CRefEvent* evt, bool reconn, HostID* hidp, - DBMgr::DevIDRelay* devID ); + DevIDRelay* devID ); void updateAck( HostID hostID, bool keep ); void dropPending( int seed ); diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index e1c853091..8e22035ae 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -249,10 +249,10 @@ DBMgr::AllDevsAckd( const char* const connName ) // Return DevIDRelay for device, adding it to devices table IFF it's not // already there. -DBMgr::DevIDRelay +DevIDRelay DBMgr::RegisterDevice( const DevID* host ) { - DBMgr::DevIDRelay devID; + DevIDRelay devID; assert( host->m_devIDType != ID_TYPE_NONE ); int ii; bool success; @@ -261,7 +261,7 @@ DBMgr::RegisterDevice( const DevID* host ) devID = getDevID( host ); // If it's not present *and* of type ID_TYPE_RELAY, we can do nothing. - // Fail. + // Otherwise proceed. if ( DEVID_NONE == devID && ID_TYPE_RELAY < host->m_devIDType ) { // loop until we're successful inserting the unique key. Ship with this // coming from random, but test with increasing values initially to make @@ -269,7 +269,7 @@ DBMgr::RegisterDevice( const DevID* host ) for ( success = false, ii = 0; !success; ++ii ) { assert( 10 > ii ); // better to check that we're looping BECAUSE // of uniqueness problem. - devID = (DBMgr::DevIDRelay)random(); + devID = (DevIDRelay)random(); if ( DEVID_NONE == devID ) { continue; } @@ -327,13 +327,14 @@ DBMgr::AddDevice( const char* connName, HostID curID, int clientVersion, const char* fmt = "UPDATE " GAMES_TABLE " SET nPerDevice[%d] = %d," " clntVers[%d] = %d," " seeds[%d] = %d, addrs[%d] = \'%s\', %s" - " mtimes[%d]='now', ack[%d]=\'%c\'" + " tokens[%d] = %d, mtimes[%d]='now', ack[%d]=\'%c\'" " WHERE connName = '%s'"; string query; char* ntoa = inet_ntoa( addr->sin_addr() ); string_printf( query, fmt, newID, nToAdd, newID, clientVersion, - newID, seed, newID, ntoa, devIDBuf.c_str(), newID, - newID, ackd?'A':'a', connName ); + newID, seed, newID, ntoa, devIDBuf.c_str(), + newID, addr->clientToken(), newID, newID, ackd?'A':'a', + connName ); logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); execSql( query ); @@ -632,10 +633,10 @@ DBMgr::readArray( const char* const connName, int arr[] ) /* len 4 */ PQclear( result ); } -DBMgr::DevIDRelay +DevIDRelay DBMgr::getDevID( const char* connName, int hid ) { - DBMgr::DevIDRelay devID; + DevIDRelay devID; const char* fmt = "SELECT devids[%d] FROM " GAMES_TABLE " WHERE connName='%s'"; string query; string_printf( query, fmt, hid, connName ); @@ -643,29 +644,28 @@ DBMgr::getDevID( const char* connName, int hid ) PGresult* result = PQexec( getThreadConn(), query.c_str() ); assert( 1 == PQntuples( result ) ); - devID = (DBMgr::DevIDRelay)strtoul( PQgetvalue( result, 0, 0 ), NULL, 10 ); + devID = (DevIDRelay)strtoul( PQgetvalue( result, 0, 0 ), NULL, 10 ); PQclear( result ); return devID; } -DBMgr::DevIDRelay +DevIDRelay DBMgr::getDevID( const DevID* devID ) { - DBMgr::DevIDRelay rDevID = DEVID_NONE; + DevIDRelay rDevID = DEVID_NONE; DevIDType devIDType = devID->m_devIDType; string query; assert( ID_TYPE_NONE < devIDType ); - const char* asStr = devID->m_devIDString.c_str(); if ( ID_TYPE_RELAY == devIDType ) { // confirm it's there - DBMgr::DevIDRelay cur = strtoul( asStr, NULL, 16 ); + DevIDRelay cur = devID->asRelayID(); if ( DEVID_NONE != cur ) { const char* fmt = "SELECT id FROM " DEVICES_TABLE " WHERE id=%d"; string_printf( query, fmt, cur ); } } else { const char* fmt = "SELECT id FROM " DEVICES_TABLE " WHERE devtype=%d and devid = '%s'"; - string_printf( query, fmt, devIDType, asStr ); + string_printf( query, fmt, devIDType, devID->m_devIDString.c_str() ); } if ( 0 < query.size() ) { @@ -673,7 +673,7 @@ DBMgr::getDevID( const DevID* devID ) PGresult* result = PQexec( getThreadConn(), query.c_str() ); assert( 1 >= PQntuples( result ) ); if ( 1 == PQntuples( result ) ) { - rDevID = (DBMgr::DevIDRelay)strtoul( PQgetvalue( result, 0, 0 ), NULL, 10 ); + rDevID = (DevIDRelay)strtoul( PQgetvalue( result, 0, 0 ), NULL, 10 ); } PQclear( result ); } diff --git a/xwords4/relay/dbmgr.h b/xwords4/relay/dbmgr.h index 2106ae101..49ab1066a 100644 --- a/xwords4/relay/dbmgr.h +++ b/xwords4/relay/dbmgr.h @@ -36,7 +36,6 @@ class DBMgr { /* DevIDs on various platforms are stored in devices table. This is the key, and used in msgs and games tables as a shorter way to refer to them. */ - typedef unsigned int DevIDRelay; static const DevIDRelay DEVID_NONE = 0; static DBMgr* Get(); @@ -62,7 +61,7 @@ class DBMgr { char* connNameBuf, int bufLen, int* nPlayersHP ); bool AllDevsAckd( const char* const connName ); - DevIDRelay RegisterDevice( const DevID* hosts ); + DevIDRelay RegisterDevice( const DevID* host ); HostID AddDevice( const char* const connName, HostID curID, int clientVersion, int nToAdd, unsigned short seed, const AddrInfo* addr, diff --git a/xwords4/relay/devid.h b/xwords4/relay/devid.h index a31744dd0..013a68fe0 100644 --- a/xwords4/relay/devid.h +++ b/xwords4/relay/devid.h @@ -22,6 +22,8 @@ #define _DEVID_H_ #include +#include + #include "xwrelay.h" /* DevID protocol. @@ -52,11 +54,18 @@ * */ +#include + using namespace std; class DevID { public: DevID() { m_devIDType = ID_TYPE_NONE; } + DevID(DevIDType typ) { m_devIDType = typ; } + DevIDRelay asRelayID() const { + assert( ID_TYPE_RELAY == m_devIDType ); + return strtoul( m_devIDString.c_str(), NULL, 16 ); + } string m_devIDString; DevIDType m_devIDType; }; diff --git a/xwords4/relay/devmgr.cpp b/xwords4/relay/devmgr.cpp new file mode 100644 index 000000000..f76a02233 --- /dev/null +++ b/xwords4/relay/devmgr.cpp @@ -0,0 +1,61 @@ +/* -*- compile-command: "make -k -j3"; -*- */ + +/* + * Copyright 2013 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program 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 program 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. + */ + +#include "devmgr.h" +#include "mlock.h" + +static DevMgr* s_instance = NULL; + +/* static */ DevMgr* +DevMgr::Get() +{ + if ( s_instance == NULL ) { + s_instance = new DevMgr(); + } + return s_instance; +} /* Get */ + +void +DevMgr::Remember( DevIDRelay devid, const AddrInfo::AddrUnion* saddr ) +{ + time_t now = time( NULL ); + MutexLock ml( &m_mapLock ); + UDPAddrRec rec( saddr, now ); + m_devAddrMap.insert( pair( devid, rec ) ); + logf( XW_LOGINFO, "dev->addr map now contains %d entries", m_devAddrMap.size() ); +} + +#if 0 // not used yet +const AddrInfo::AddrUnion* +DevMgr::get( DevIDRelay devid ) +{ + const AddrInfo::AddrUnion* result = NULL; + MutexLock ml( &m_mapLock ); + map::iterator iter; + iter = m_devAddrMap.find( devid ); + if ( m_devAddrMap.end() != iter ) { + result = &iter->second.m_addr; + logf( XW_LOGINFO, "%s: found addr for %.8x; is %d seconds old", __func__, + devid, time(NULL) - iter->second.m_added ); + } + return result; +} +#endif + diff --git a/xwords4/relay/devmgr.h b/xwords4/relay/devmgr.h new file mode 100644 index 000000000..799d6d4f7 --- /dev/null +++ b/xwords4/relay/devmgr.h @@ -0,0 +1,54 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2013 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program 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. + * + * This program 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. + */ + + +#ifndef _DEVMGR_H_ +#define _DEVMGR_H_ + +#include + +#include "xwrelay_priv.h" +#include "addrinfo.h" + +using namespace std; + +class DevMgr { + public: + static DevMgr* Get(); + void Remember( DevIDRelay devid, const AddrInfo::AddrUnion* saddr ); + const AddrInfo::AddrUnion* get( DevIDRelay devid ); + + private: + DevMgr() { pthread_mutex_init( &m_mapLock, NULL ); } + ~DevMgr() { pthread_mutex_destroy( &m_mapLock ); } + + class UDPAddrRec { + public: + UDPAddrRec( const AddrInfo::AddrUnion* addr, time_t tim ) { + m_addr = *addr; m_added = tim; + } + AddrInfo::AddrUnion m_addr; + time_t m_added; + }; + + map m_devAddrMap; + pthread_mutex_t m_mapLock; +}; + +#endif diff --git a/xwords4/relay/scripts/showinplay.sh b/xwords4/relay/scripts/showinplay.sh index ee0c5e19a..3d2c25651 100755 --- a/xwords4/relay/scripts/showinplay.sh +++ b/xwords4/relay/scripts/showinplay.sh @@ -26,7 +26,7 @@ QUERY="WHERE NOT -NTOTAL = sum_array(nperdevice)" echo "Device (pid) count: $(pidof xwords | wc | awk '{print $2}')" echo "Row count:" $(psql -t xwgames -c "select count(*) FROM games $QUERY;") -echo "SELECT dead,connname,cid,room,lang,clntVers,ntotal,nperdevice,seeds,devids,ack,nsent "\ +echo "SELECT dead,connname,cid,room,lang,clntVers,ntotal,nperdevice,seeds,addrs,tokens,ack,nsent "\ "FROM games $QUERY ORDER BY NOT dead, connname LIMIT $LIMIT;" \ | psql xwgames diff --git a/xwords4/relay/xwrelay.conf_tmplate b/xwords4/relay/xwrelay.conf_tmplate index 4f80efb2c..2f6bfc00d 100644 --- a/xwords4/relay/xwrelay.conf_tmplate +++ b/xwords4/relay/xwrelay.conf_tmplate @@ -26,12 +26,16 @@ GAME_PORTS=10997 # What ports do we listen on for per-device incoming connections? DEVICE_PORTS=10998 +# Port for per-device UDP interface (experimental) +UDPPORT=10997 + # default 5 SOCK_TIMEOUT_SECONDS=5 # And the control port is? CTLPORT=11000 + # port for web interface WWW_PORT=11001 #--- INADDR_ANY: 0x00000000 diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index 4c4c11718..221a7dbe7 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -76,9 +76,11 @@ #include "lstnrmgr.h" #include "dbmgr.h" #include "addrinfo.h" +#include "devmgr.h" static int s_nSpawns = 0; static int g_maxsocks = -1; +static int g_udpsock = -1; void logf( XW_LogLevel level, const char* format, ... ) @@ -334,6 +336,15 @@ denyConnection( const AddrInfo* addr, XWREASON err ) send_with_length_unsafe( addr, buf, sizeof(buf) ); } +static ssize_t +send_via_udp( int socket, const struct sockaddr *dest_addr, + const unsigned char* buf, int buflen ) +{ + ssize_t nSent = sendto( socket, buf, buflen, + 0, /* flags */ dest_addr, sizeof(*dest_addr) ); + return nSent; +} + /* No mutex here. Caller better be ensuring no other thread can access this * socket. */ bool @@ -343,15 +354,29 @@ send_with_length_unsafe( const AddrInfo* addr, unsigned char* buf, assert( !!addr ); bool ok = false; int socket = addr->socket(); - assert ( addr->isTCP() ); - unsigned short len = htons( bufLen ); - ssize_t nSent = send( socket, &len, 2, 0 ); - if ( nSent == 2 ) { - nSent = send( socket, buf, bufLen, 0 ); - if ( nSent == ssize_t(bufLen) ) { - logf( XW_LOGINFO, "sent %d bytes on socket %d", nSent, socket ); - ok = true; + if ( addr->isTCP() ) { + unsigned short len = htons( bufLen ); + ssize_t nSent = send( socket, &len, 2, 0 ); + if ( nSent == 2 ) { + nSent = send( socket, buf, bufLen, 0 ); + if ( nSent == ssize_t(bufLen) ) { + logf( XW_LOGINFO, "sent %d bytes on socket %d", nSent, socket ); + ok = true; + } } + } else { + uint32_t clientToken = addr->clientToken(); + assert( 0 != clientToken ); + unsigned char tmpbuf[1 + 1 + sizeof(clientToken) + bufLen]; + tmpbuf[0] = XWREG_PROTO_VERSION; + tmpbuf[1] = XWRREG_MSG; + clientToken = htonl(clientToken); + memcpy( &tmpbuf[2], &clientToken, sizeof(clientToken) ); + memcpy( &tmpbuf[2 + sizeof(clientToken)], buf, bufLen ); + const struct sockaddr* saddr = addr->sockaddr(); + send_via_udp( g_udpsock, saddr, tmpbuf, sizeof(tmpbuf) ); + logf( XW_LOGINFO, "sent %d bytes on UDP socket %d", bufLen, socket ); + ok = true; } if ( !ok ) { @@ -1046,6 +1071,94 @@ handle_proxy_packet( unsigned char* buf, int len, const AddrInfo* addr ) } } /* handle_proxy_packet */ +static void +registerDevice( const DevID* devID, const AddrInfo::AddrUnion* saddr ) +{ + DevIDRelay relayID; + if ( ID_TYPE_RELAY != devID->m_devIDType ) { // known to us; just update the time + relayID = DBMgr::Get()->RegisterDevice( devID ); + if ( ID_TYPE_NONE != relayID ) { + // send it back to the device + char idbuf[9]; + int len = snprintf( idbuf, sizeof(idbuf), "%.8X", relayID ); + logf( XW_LOGERROR, "%s: len(%s) => %d", __func__, idbuf, len ); + unsigned char buf[1 + 1 + 2 + len]; + buf[0] = XWREG_PROTO_VERSION; + buf[1] = XWRREG_REGRSP; + short lenNBO = htons(len); + memcpy( &buf[2], &lenNBO, sizeof(lenNBO)); + memcpy( &buf[4], idbuf, len ); + send_via_udp( g_udpsock, &saddr->addr, buf, sizeof(buf) ); + } + } else { + relayID = devID->asRelayID(); + } + // Now let's map the address to the devid for future sending purposes. + DevMgr::Get()->Remember( relayID, saddr ); +} + +// This will need to be done in a thread before there can be simulaneous +// connections. +static void +handle_udp_packet( int udpsock ) +{ + bool success = false; + logf( XW_LOGINFO, "%s()", __func__ ); + unsigned char buf[512]; + AddrInfo::AddrUnion saddr; + memset( &saddr, 0, sizeof(saddr) ); + socklen_t fromlen = sizeof(saddr.addr_in); + + ssize_t nRead = recvfrom( udpsock, buf, sizeof(buf), 0 /*flags*/, + &saddr.addr, &fromlen ); + if ( 2 <= nRead ) { + unsigned char* ptr = buf; + unsigned char* end = buf + nRead; + logf( XW_LOGINFO, "%s: recvfrom=>%d", __func__, nRead ); + + unsigned char proto = *ptr++; + if ( XWREG_PROTO_VERSION != 0 ) { + logf( XW_LOGERROR, "unexpected proto %d", __func__, (int) proto ); + } else { + int msg = *ptr++; + switch( msg ) { + case XWRREG_REG: { + DevIDType typ = (DevIDType)*ptr++; + unsigned short idLen; + if ( !getNetShort( &ptr, end, &idLen ) ) { + break; + } + if ( end - ptr > idLen ) { + logf( XW_LOGERROR, "full devID not received" ); + break; + } + DevID devID( typ ); + devID.m_devIDString.append( (const char*)ptr, idLen ); + ptr += idLen; + registerDevice( &devID, &saddr ); + } + break; + case XWRREG_MSG: { + uint32_t clientToken; + memcpy( &clientToken, ptr, sizeof(clientToken) ); + ptr += sizeof(clientToken); + clientToken = ntohl( clientToken ); + if ( 0 != clientToken ) { + AddrInfo addr( udpsock, clientToken, &saddr ); + success = processMessage( ptr, end - ptr, &addr ); + } else { + logf( XW_LOGERROR, "%s: dropping packet with token of 0" ); + } + } + break; + default: + logf( XW_LOGERROR, "%s: unexpected msg %d", __func__, msg ); + } + } + } + logf( XW_LOGINFO, "%s()=>%d", __func__, success ); +} + /* From stack overflow, toward a snprintf with an expanding buffer. */ void @@ -1134,6 +1247,7 @@ main( int argc, char** argv ) { int port = 0; int ctrlport = 0; + int udpport = 0; #ifdef DO_HTTP int httpport = 0; const char* cssFile = NULL; @@ -1156,7 +1270,7 @@ main( int argc, char** argv ) first. */ for ( ; ; ) { - int opt = getopt(argc, argv, "h?c:p:m:n:f:l:t:s:w:" + int opt = getopt(argc, argv, "h?c:p:m:n:f:l:t:s:u:w:" "DF" ); if ( opt == -1 ) { @@ -1210,6 +1324,9 @@ main( int argc, char** argv ) case 't': nWorkerThreads = atoi( optarg ); break; + case 'u': + udpport = atoi( optarg ); + break; default: usage( argv[0] ); exit( 1 ); @@ -1232,6 +1349,9 @@ main( int argc, char** argv ) if ( ctrlport == 0 ) { (void)cfg->GetValueFor( "CTLPORT", &ctrlport ); } + if ( 0 == udpport ) { + (void)cfg->GetValueFor( "UDPPORT", &udpport ); + } #ifdef DO_HTTP if ( httpport == 0 ) { (void)cfg->GetValueFor( "WWW_PORT", &httpport ); @@ -1371,6 +1491,19 @@ main( int argc, char** argv ) exit( 1 ); } + if ( 0 != udpport ) { + struct sockaddr_in saddr; + g_udpsock = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); + saddr.sin_family = PF_INET; + saddr.sin_addr.s_addr = htonl(INADDR_ANY); + saddr.sin_port = htons(udpport); + int err = bind( g_udpsock, (struct sockaddr*)&saddr, sizeof(saddr) ); + if ( 0 != err ) { + logf( XW_LOGERROR, "bind()=>%s", strerror(errno) ); + g_udpsock = -1; + } + } + #ifdef DO_HTTP HttpState http_state; int addr; @@ -1404,6 +1537,9 @@ main( int argc, char** argv ) FD_ZERO(&rfds); g_listeners.AddToFDSet( &rfds ); FD_SET( g_control, &rfds ); + if ( -1 != g_udpsock ) { + FD_SET( g_udpsock, &rfds ); + } #ifdef DO_HTTP if ( -1 != g_http ) { FD_SET( g_http, &rfds ); @@ -1413,6 +1549,9 @@ main( int argc, char** argv ) if ( g_control > highest ) { highest = g_control; } + if ( g_udpsock > highest ) { + highest = g_udpsock; + } #ifdef DO_HTTP if ( g_http > highest ) { highest = g_http; @@ -1458,7 +1597,7 @@ main( int argc, char** argv ) "%s: accepting connection from %s on socket %d", __func__, inet_ntoa(saddr.addr_in.sin_addr), newSock ); - AddrInfo addr( true, newSock, &saddr ); + AddrInfo addr( newSock, &saddr ); tPool->AddSocket( perGame ? XWThreadPool::STYPE_GAME : XWThreadPool::STYPE_PROXY, &addr ); @@ -1471,6 +1610,12 @@ main( int argc, char** argv ) // run_ctrl_thread( g_control ); --retval; } + if ( FD_ISSET( g_udpsock, &rfds ) ) { + // This will need to be done in a separate thread, or pushed + // to the existing thread pool + handle_udp_packet( g_udpsock ); + --retval; + } #ifdef DO_HTTP if ( FD_ISSET( g_http, &rfds ) ) { FD_CLR( g_http, &rfds ); diff --git a/xwords4/relay/xwrelay.h b/xwords4/relay/xwrelay.h index 93a3785da..2f30c5068 100644 --- a/xwords4/relay/xwrelay.h +++ b/xwords4/relay/xwrelay.h @@ -27,6 +27,37 @@ /* Set if device is acting a server; cleared if as client */ #define FLAGS_SERVER_BIT 0x01 +/* message types for the udp-based per-device (not per-game) protocol */ +#define XWREG_PROTO_VERSION 0 +#ifndef CANT_DO_TYPEDEF +typedef +#endif +enum { XWRREG_NONE /* 0 is an illegal value */ + ,XWRREG_REG /* dev->relay: device registers self and + self-selected (e.g. gcm) or assigned devid + format: proto: 1; this enum: 1; idType: 1, + idLen: 2, id: */ + + ,XWRREG_REGRSP /* relay->device: if non-relay-assigned devid + type was given, this gives the + relay-assigned one to be used from now on. + format: proto: 1, this enum: 1, idLen: 2, id: + */ + + ,XWRREG_PING /* device->relay: keep the UDP connection + open. format: proto: 1, this enum: 1. */ + + ,XWRREG_MSG /* dev->relay and relay->dev: norm: a message from a game to + the relay format: proto: 1, this enum: 1, + clientToken: 4, message*/ + + ,XWRREG_MSGRSP /* relay->dev: conveys error on receipt of XWRREG_MSG */ +} +#ifndef CANT_DO_TYPEDEF + XWRelayReg +#endif +; + #ifndef CANT_DO_TYPEDEF typedef #endif diff --git a/xwords4/relay/xwrelay.sh b/xwords4/relay/xwrelay.sh index 111797100..f39efbdac 100755 --- a/xwords4/relay/xwrelay.sh +++ b/xwords4/relay/xwrelay.sh @@ -58,6 +58,7 @@ cid integer ,mtimes TIMESTAMP(0)[] ,addrs INET[] ,devids INTEGER[] +,tokens INTEGER[] ); EOF diff --git a/xwords4/relay/xwrelay_priv.h b/xwords4/relay/xwrelay_priv.h index 2e933eb89..351d5ba0e 100644 --- a/xwords4/relay/xwrelay_priv.h +++ b/xwords4/relay/xwrelay_priv.h @@ -25,11 +25,14 @@ #include #include #include +#include + #include "lstnrmgr.h" #include "xwrelay.h" #include "addrinfo.h" typedef unsigned char HostID; /* see HOST_ID_SERVER */ +typedef uint32_t DevIDRelay; typedef enum { XW_LOGERROR From a107365c8828cc2fa0f65f0d963ac7c48bd5f3f6 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 15 Jan 2013 19:04:20 -0800 Subject: [PATCH 016/116] change names of two types and variables of that type to make a common pattern more sensible. No code changes. --- xwords4/linux/gamesdb.c | 8 +- xwords4/linux/gamesdb.h | 4 +- xwords4/linux/gtkboard.c | 222 ++++++++++++++++++------------------ xwords4/linux/gtkboard.h | 14 +-- xwords4/linux/gtkchat.c | 2 +- xwords4/linux/gtkchat.h | 2 +- xwords4/linux/gtkconnsdlg.c | 4 +- xwords4/linux/gtkconnsdlg.h | 2 +- xwords4/linux/gtkdraw.c | 4 +- xwords4/linux/gtkdraw.h | 2 +- xwords4/linux/gtkmain.c | 117 ++++++++++--------- xwords4/linux/gtkmain.h | 2 +- xwords4/linux/gtknewgame.c | 4 +- xwords4/linux/gtknewgame.h | 2 +- xwords4/linux/main.h | 4 +- 15 files changed, 196 insertions(+), 197 deletions(-) diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c index bf33cdd06..d52409c32 100644 --- a/xwords4/linux/gamesdb.c +++ b/xwords4/linux/gamesdb.c @@ -139,12 +139,12 @@ summarize( CommonGlobals* cGlobals ) } GSList* -listGames( GTKGamesGlobals* gg ) +listGames( GtkAppGlobals* apg ) { GSList* list = NULL; sqlite3_stmt *ppStmt; - int result = sqlite3_prepare_v2( gg->pDb, + int result = sqlite3_prepare_v2( apg->pDb, "SELECT rowid FROM games ORDER BY rowid", -1, &ppStmt, NULL ); XP_ASSERT( SQLITE_OK == result ); @@ -171,7 +171,7 @@ listGames( GTKGamesGlobals* gg ) } XP_Bool -getGameInfo( GTKGamesGlobals* gg, sqlite3_int64 rowid, GameInfo* gib ) +getGameInfo( GtkAppGlobals* apg, sqlite3_int64 rowid, GameInfo* gib ) { XP_Bool success = XP_FALSE; const char* fmt = "SELECT room, ended, turn, nmoves, nmissing " @@ -180,7 +180,7 @@ getGameInfo( GTKGamesGlobals* gg, sqlite3_int64 rowid, GameInfo* gib ) snprintf( query, sizeof(query), fmt, rowid ); sqlite3_stmt* ppStmt; - int result = sqlite3_prepare_v2( gg->pDb, query, -1, &ppStmt, NULL ); + int result = sqlite3_prepare_v2( apg->pDb, query, -1, &ppStmt, NULL ); XP_ASSERT( SQLITE_OK == result ); result = sqlite3_step( ppStmt ); if ( SQLITE_ROW == result ) { diff --git a/xwords4/linux/gamesdb.h b/xwords4/linux/gamesdb.h index aa51c50de..323a1ece2 100644 --- a/xwords4/linux/gamesdb.h +++ b/xwords4/linux/gamesdb.h @@ -44,8 +44,8 @@ void writeToDB( XWStreamCtxt* stream, void* closure ); void summarize( CommonGlobals* cGlobals ); /* Return GSList whose data is (ptrs to) rowids */ -GSList* listGames( GTKGamesGlobals* gg ); -XP_Bool getGameInfo( GTKGamesGlobals* gg, sqlite3_int64 rowid, GameInfo* gib ); +GSList* listGames( GtkAppGlobals* apg ); +XP_Bool getGameInfo( GtkAppGlobals* apg, sqlite3_int64 rowid, GameInfo* gib ); XP_Bool loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ); #endif diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 71b60b1a9..8bdef1dcf 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -66,19 +66,19 @@ #include "filestream.h" #include "gamesdb.h" -/* static guint gtkSetupClientSocket( GtkAppGlobals* globals, int sock ); */ -static void setCtrlsForTray( GtkAppGlobals* globals ); -static void new_game( GtkWidget* widget, GtkAppGlobals* globals ); -static XP_Bool new_game_impl( GtkAppGlobals* globals, XP_Bool fireConnDlg ); -static void setZoomButtons( GtkAppGlobals* globals, XP_Bool* inOut ); -static void disenable_buttons( GtkAppGlobals* globals ); +/* static guint gtkSetupClientSocket( GtkGameGlobals* globals, int sock ); */ +static void setCtrlsForTray( GtkGameGlobals* globals ); +static void new_game( GtkWidget* widget, GtkGameGlobals* globals ); +static XP_Bool new_game_impl( GtkGameGlobals* globals, XP_Bool fireConnDlg ); +static void setZoomButtons( GtkGameGlobals* globals, XP_Bool* inOut ); +static void disenable_buttons( GtkGameGlobals* globals ); #define GTK_TRAY_HT_ROWS 3 #if 0 static XWStreamCtxt* -lookupClientStream( GtkAppGlobals* globals, int sock ) +lookupClientStream( GtkGameGlobals* globals, int sock ) { short i; for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { @@ -93,7 +93,7 @@ lookupClientStream( GtkAppGlobals* globals, int sock ) } /* lookupClientStream */ static void -rememberClient( GtkAppGlobals* globals, guint key, int sock, +rememberClient( GtkGameGlobals* globals, guint key, int sock, XWStreamCtxt* stream ) { short i; @@ -112,7 +112,7 @@ rememberClient( GtkAppGlobals* globals, guint key, int sock, #endif static void -gtkSetAltState( GtkAppGlobals* globals, guint state ) +gtkSetAltState( GtkGameGlobals* globals, guint state ) { globals->altKeyDown = (state & (GDK_MOD1_MASK|GDK_SHIFT_MASK|GDK_CONTROL_MASK)) != 0; @@ -120,7 +120,7 @@ gtkSetAltState( GtkAppGlobals* globals, guint state ) static gint button_press_event( GtkWidget* XP_UNUSED(widget), GdkEventButton *event, - GtkAppGlobals* globals ) + GtkGameGlobals* globals ) { XP_Bool redraw, handled; @@ -140,7 +140,7 @@ button_press_event( GtkWidget* XP_UNUSED(widget), GdkEventButton *event, static gint motion_notify_event( GtkWidget* XP_UNUSED(widget), GdkEventMotion *event, - GtkAppGlobals* globals ) + GtkGameGlobals* globals ) { XP_Bool handled; @@ -162,7 +162,7 @@ motion_notify_event( GtkWidget* XP_UNUSED(widget), GdkEventMotion *event, static gint button_release_event( GtkWidget* XP_UNUSED(widget), GdkEventMotion *event, - GtkAppGlobals* globals ) + GtkGameGlobals* globals ) { XP_Bool redraw; @@ -238,7 +238,7 @@ evtToXPKey( GdkEventKey* event, XP_Bool* movesCursorP ) #ifdef KEYBOARD_NAV static gint key_press_event( GtkWidget* XP_UNUSED(widget), GdkEventKey* event, - GtkAppGlobals* globals ) + GtkGameGlobals* globals ) { XP_Bool handled = XP_FALSE; XP_Bool movesCursor; @@ -263,7 +263,7 @@ key_press_event( GtkWidget* XP_UNUSED(widget), GdkEventKey* event, static gint key_release_event( GtkWidget* XP_UNUSED(widget), GdkEventKey* event, - GtkAppGlobals* globals ) + GtkGameGlobals* globals ) { XP_Bool handled = XP_FALSE; XP_Bool movesCursor; @@ -307,7 +307,7 @@ static void relay_status_gtk( void* closure, CommsRelayState state ) { XP_LOGF( "%s got status: %s", __func__, CommsRelayState2Str(state) ); - GtkAppGlobals* globals = (GtkAppGlobals*)closure; + GtkGameGlobals* globals = (GtkGameGlobals*)closure; globals->cGlobals.state = state; globals->stateChar = 'A' + COMMS_RELAYSTATE_ALLCONNECTED - state; draw_gtk_status( globals->draw, globals->stateChar ); @@ -336,7 +336,7 @@ relay_connd_gtk( void* closure, XP_UCHAR* const room, } if ( !skip ) { - GtkAppGlobals* globals = (GtkAppGlobals*)closure; + GtkGameGlobals* globals = (GtkGameGlobals*)closure; (void)gtkask_timeout( globals->window, buf, GTK_BUTTONS_OK, 500 ); } } @@ -344,7 +344,7 @@ relay_connd_gtk( void* closure, XP_UCHAR* const room, static gint invoke_new_game( gpointer data ) { - GtkAppGlobals* globals = (GtkAppGlobals*)data; + GtkGameGlobals* globals = (GtkGameGlobals*)data; new_game_impl( globals, XP_FALSE ); return 0; } @@ -352,7 +352,7 @@ invoke_new_game( gpointer data ) static gint invoke_new_game_conns( gpointer data ) { - GtkAppGlobals* globals = (GtkAppGlobals*)data; + GtkGameGlobals* globals = (GtkGameGlobals*)data; new_game_impl( globals, XP_TRUE ); return 0; } @@ -361,7 +361,7 @@ static void relay_error_gtk( void* closure, XWREASON relayErr ) { LOG_FUNC(); - GtkAppGlobals* globals = (GtkAppGlobals*)closure; + GtkGameGlobals* globals = (GtkGameGlobals*)closure; gint (*proc)( gpointer data ) = NULL; switch( relayErr ) { @@ -391,7 +391,7 @@ relay_error_gtk( void* closure, XWREASON relayErr ) } static void -createOrLoadObjects( GtkAppGlobals* globals ) +createOrLoadObjects( GtkGameGlobals* globals ) { XWStreamCtxt* stream = NULL; XP_Bool opened = XP_FALSE; @@ -545,7 +545,7 @@ createOrLoadObjects( GtkAppGlobals* globals ) */ static gboolean configure_event( GtkWidget* widget, GdkEventConfigure* XP_UNUSED(event), - GtkAppGlobals* globals ) + GtkGameGlobals* globals ) { short bdWidth, bdHeight; short timerLeft, timerTop; @@ -643,7 +643,7 @@ configure_event( GtkWidget* widget, GdkEventConfigure* XP_UNUSED(event), static gint expose_event( GtkWidget* XP_UNUSED(widget), GdkEventExpose* XP_UNUSED(event), - GtkAppGlobals* globals ) + GtkGameGlobals* globals ) { /* gdk_draw_rectangle( widget->window,//((GtkDrawCtx*)globals->draw)->pixmap, @@ -675,7 +675,7 @@ expose_event( GtkWidget* XP_UNUSED(widget), #if 0 static gint handle_client_event( GtkWidget *widget, GdkEventClient *event, - GtkAppGlobals* globals ) + GtkGameGlobals* globals ) { XP_LOGF( "handle_client_event called: event->type = " ); if ( event->type == GDK_CLIENT_EVENT ) { @@ -692,14 +692,14 @@ static void destroy_window( GtkWidget* XP_UNUSED(widget), gpointer data ) { LOG_FUNC(); - GtkAppGlobals* globals = (GtkAppGlobals*)data; + GtkGameGlobals* globals = (GtkGameGlobals*)data; saveGame( &globals->cGlobals ); windowDestroyed( globals ); // gtk_main_quit(); } static void -cleanup( GtkAppGlobals* globals ) +cleanup( GtkGameGlobals* globals ) { saveGame( &globals->cGlobals ); @@ -739,7 +739,7 @@ makeAddSubmenu( GtkWidget* menubar, gchar* label ) } /* makeAddSubmenu */ static void -tile_values( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +tile_values( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { if ( !!globals->cGlobals.game.server ) { XWStreamCtxt* stream = @@ -756,14 +756,14 @@ tile_values( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) } /* tile_values */ static void -game_history( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +game_history( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { catGameHistory( &globals->cGlobals ); } /* game_history */ #ifdef TEXT_MODEL static void -dump_board( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +dump_board( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { if ( !!globals->cGlobals.game.model ) { XWStreamCtxt* stream = @@ -779,7 +779,7 @@ dump_board( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) #endif static void -final_scores( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +final_scores( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { XP_Bool gameOver = server_getGameIsOver( globals->cGlobals.game.server ); @@ -799,7 +799,7 @@ final_scores( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) } /* final_scores */ static XP_Bool -new_game_impl( GtkAppGlobals* globals, XP_Bool fireConnDlg ) +new_game_impl( GtkGameGlobals* globals, XP_Bool fireConnDlg ) { XP_Bool success = XP_FALSE; CommsAddrRec addr; @@ -869,13 +869,13 @@ new_game_impl( GtkAppGlobals* globals, XP_Bool fireConnDlg ) } /* new_game_impl */ static void -new_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +new_game( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { new_game_impl( globals, XP_FALSE ); } /* new_game */ static void -game_info( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +game_info( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { CommsAddrRec addr; comms_getAddr( globals->cGlobals.game.comms, &addr ); @@ -891,20 +891,20 @@ game_info( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) } static void -load_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) +load_game( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* XP_UNUSED(globals) ) { XP_ASSERT(0); } /* load_game */ static void -save_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) +save_game( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* XP_UNUSED(globals) ) { XP_ASSERT(0); } /* save_game */ #ifdef XWFEATURE_CHANGEDICT static void -change_dictionary( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +change_dictionary( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { CommonGlobals* cGlobals = &globals->cGlobals; LaunchParams* params = cGlobals->params; @@ -923,38 +923,38 @@ change_dictionary( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) #endif static void -handle_undo( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) +handle_undo( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* XP_UNUSED(globals) ) { } /* handle_undo */ static void -handle_redo( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) +handle_redo( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* XP_UNUSED(globals) ) { } /* handle_redo */ #ifdef FEATURE_TRAY_EDIT static void handle_trayEditToggle( GtkWidget* XP_UNUSED(widget), - GtkAppGlobals* XP_UNUSED(globals), + GtkGameGlobals* XP_UNUSED(globals), XP_Bool XP_UNUSED(on) ) { } /* handle_trayEditToggle */ static void -handle_trayEditToggle_on( GtkWidget* widget, GtkAppGlobals* globals ) +handle_trayEditToggle_on( GtkWidget* widget, GtkGameGlobals* globals ) { handle_trayEditToggle( widget, globals, XP_TRUE ); } static void -handle_trayEditToggle_off( GtkWidget* widget, GtkAppGlobals* globals ) +handle_trayEditToggle_off( GtkWidget* widget, GtkGameGlobals* globals ) { handle_trayEditToggle( widget, globals, XP_FALSE ); } #endif static void -handle_trade_cancel( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_trade_cancel( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { BoardCtxt* board = globals->cGlobals.game.board; if ( board_endTrade( board ) ) { @@ -964,7 +964,7 @@ handle_trade_cancel( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) #ifndef XWFEATURE_STANDALONE_ONLY static void -handle_resend( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_resend( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { CommsCtxt* comms = globals->cGlobals.game.comms; if ( comms != NULL ) { @@ -974,7 +974,7 @@ handle_resend( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) #ifdef XWFEATURE_COMMSACK static void -handle_ack( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_ack( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { CommsCtxt* comms = globals->cGlobals.game.comms; if ( comms != NULL ) { @@ -985,7 +985,7 @@ handle_ack( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) #ifdef DEBUG static void -handle_commstats( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_commstats( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { CommsCtxt* comms = globals->cGlobals.game.comms; @@ -1004,7 +1004,7 @@ handle_commstats( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) #ifdef MEM_DEBUG static void -handle_memstats( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_memstats( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { XWStreamCtxt* stream = mem_stream_make( MEMPOOL globals->cGlobals.params->vtMgr, @@ -1018,7 +1018,7 @@ handle_memstats( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) static GtkWidget* createAddItem( GtkWidget* parent, gchar* label, - GtkSignalFunc handlerFunc, GtkAppGlobals* globals ) + GtkSignalFunc handlerFunc, GtkGameGlobals* globals ) { GtkWidget* item = gtk_menu_item_new_with_label( label ); @@ -1036,7 +1036,7 @@ createAddItem( GtkWidget* parent, gchar* label, } /* createAddItem */ static GtkWidget* -makeMenus( GtkAppGlobals* globals ) +makeMenus( GtkGameGlobals* globals ) { GtkWidget* menubar = gtk_menu_bar_new(); GtkWidget* fileMenu; @@ -1113,7 +1113,7 @@ makeMenus( GtkAppGlobals* globals ) } /* makeMenus */ static void -disenable_buttons( GtkAppGlobals* globals ) +disenable_buttons( GtkGameGlobals* globals ) { XP_Bool canFlip = 1 < board_visTileCount( globals->cGlobals.game.board ); gtk_widget_set_sensitive( globals->flip_button, canFlip ); @@ -1135,7 +1135,7 @@ disenable_buttons( GtkAppGlobals* globals ) static gboolean handle_flip_button( GtkWidget* XP_UNUSED(widget), gpointer _globals ) { - GtkAppGlobals* globals = (GtkAppGlobals*)_globals; + GtkGameGlobals* globals = (GtkGameGlobals*)_globals; if ( board_flip( globals->cGlobals.game.board ) ) { board_draw( globals->cGlobals.game.board ); } @@ -1145,7 +1145,7 @@ handle_flip_button( GtkWidget* XP_UNUSED(widget), gpointer _globals ) static gboolean handle_value_button( GtkWidget* XP_UNUSED(widget), gpointer closure ) { - GtkAppGlobals* globals = (GtkAppGlobals*)closure; + GtkGameGlobals* globals = (GtkGameGlobals*)closure; if ( board_toggle_showValues( globals->cGlobals.game.board ) ) { board_draw( globals->cGlobals.game.board ); } @@ -1153,7 +1153,7 @@ handle_value_button( GtkWidget* XP_UNUSED(widget), gpointer closure ) } /* handle_value_button */ static void -handle_hint_button( GtkAppGlobals* globals, XP_Bool prev ) +handle_hint_button( GtkGameGlobals* globals, XP_Bool prev ) { XP_Bool redo; if ( board_requestHint( globals->cGlobals.game.board, @@ -1167,19 +1167,19 @@ handle_hint_button( GtkAppGlobals* globals, XP_Bool prev ) } /* handle_hint_button */ static void -handle_prevhint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_prevhint_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { handle_hint_button( globals, XP_TRUE ); } /* handle_prevhint_button */ static void -handle_nexthint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_nexthint_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { handle_hint_button( globals, XP_FALSE ); } /* handle_nexthint_button */ static void -handle_nhint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_nhint_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { XP_Bool redo; @@ -1195,7 +1195,7 @@ handle_nhint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) static void handle_colors_button( GtkWidget* XP_UNUSED(widget), - GtkAppGlobals* XP_UNUSED(globals) ) + GtkGameGlobals* XP_UNUSED(globals) ) { /* XP_Bool oldVal = board_getShowColors( globals->cGlobals.game.board ); */ /* if ( board_setShowColors( globals->cGlobals.game.board, !oldVal ) ) { */ @@ -1204,7 +1204,7 @@ handle_colors_button( GtkWidget* XP_UNUSED(widget), } /* handle_colors_button */ static void -handle_juggle_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_juggle_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { if ( board_juggleTray( globals->cGlobals.game.board ) ) { board_draw( globals->cGlobals.game.board ); @@ -1212,7 +1212,7 @@ handle_juggle_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) } /* handle_juggle_button */ static void -handle_undo_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_undo_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { if ( server_handleUndo( globals->cGlobals.game.server, 0 ) ) { board_draw( globals->cGlobals.game.board ); @@ -1221,13 +1221,13 @@ handle_undo_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) static void handle_redo_button( GtkWidget* XP_UNUSED(widget), - GtkAppGlobals* XP_UNUSED(globals) ) + GtkGameGlobals* XP_UNUSED(globals) ) { } /* handle_redo_button */ static void handle_toggle_undo( GtkWidget* XP_UNUSED(widget), - GtkAppGlobals* globals ) + GtkGameGlobals* globals ) { BoardCtxt* board = globals->cGlobals.game.board; if ( board_redoReplacedTiles( board ) || board_replaceTiles( board ) ) { @@ -1236,7 +1236,7 @@ handle_toggle_undo( GtkWidget* XP_UNUSED(widget), } static void -handle_trade_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_trade_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { if ( board_beginTrade( globals->cGlobals.game.board ) ) { board_draw( globals->cGlobals.game.board ); @@ -1245,7 +1245,7 @@ handle_trade_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) } /* handle_juggle_button */ static void -handle_done_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_done_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { if ( board_commitTurn( globals->cGlobals.game.board ) ) { board_draw( globals->cGlobals.game.board ); @@ -1254,14 +1254,14 @@ handle_done_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) } /* handle_done_button */ static void -setZoomButtons( GtkAppGlobals* globals, XP_Bool* inOut ) +setZoomButtons( GtkGameGlobals* globals, XP_Bool* inOut ) { gtk_widget_set_sensitive( globals->zoomin_button, inOut[0] ); gtk_widget_set_sensitive( globals->zoomout_button, inOut[1] ); } static void -handle_zoomin_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_zoomin_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { XP_Bool inOut[2]; if ( board_zoom( globals->cGlobals.game.board, 1, inOut ) ) { @@ -1271,7 +1271,7 @@ handle_zoomin_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) } /* handle_done_button */ static void -handle_zoomout_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_zoomout_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { XP_Bool inOut[2]; if ( board_zoom( globals->cGlobals.game.board, -1, inOut ) ) { @@ -1282,7 +1282,7 @@ handle_zoomout_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) #ifdef XWFEATURE_CHAT static void -handle_chat_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_chat_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { gchar* msg = gtkGetChatMessage( globals ); if ( NULL != msg ) { @@ -1293,7 +1293,7 @@ handle_chat_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) #endif static void -scroll_value_changed( GtkAdjustment *adj, GtkAppGlobals* globals ) +scroll_value_changed( GtkAdjustment *adj, GtkGameGlobals* globals ) { XP_U16 newValue; gfloat newValueF = adj->value; @@ -1308,7 +1308,7 @@ scroll_value_changed( GtkAdjustment *adj, GtkAppGlobals* globals ) } /* scroll_value_changed */ static void -handle_grid_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_grid_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { globals->gridOn = !globals->gridOn; @@ -1317,7 +1317,7 @@ handle_grid_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) } /* handle_grid_button */ static void -handle_hide_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_hide_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { BoardCtxt* board; XP_Bool draw = XP_FALSE; @@ -1343,7 +1343,7 @@ handle_hide_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) } /* handle_hide_button */ static void -handle_commit_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_commit_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { if ( board_commitTurn( globals->cGlobals.game.board ) ) { board_draw( globals->cGlobals.game.board ); @@ -1351,7 +1351,7 @@ handle_commit_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) } /* handle_commit_button */ static void -gtkUserError( GtkAppGlobals* globals, const char* format, ... ) +gtkUserError( GtkGameGlobals* globals, const char* format, ... ) { char buf[512]; va_list ap; @@ -1368,7 +1368,7 @@ gtkUserError( GtkAppGlobals* globals, const char* format, ... ) static VTableMgr* gtk_util_getVTManager(XW_UtilCtxt* uc) { - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; return globals->cGlobals.params->vtMgr; } /* linux_util_getVTManager */ @@ -1377,7 +1377,7 @@ gtk_util_userPickTileBlank( XW_UtilCtxt* uc, XP_U16 playerNum, const XP_UCHAR** texts, XP_U16 nTiles ) { XP_S16 chosen; - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; XP_UCHAR* name = globals->cGlobals.gi.players[playerNum].name; chosen = gtkletterask( NULL, XP_FALSE, name, nTiles, texts ); @@ -1390,7 +1390,7 @@ gtk_util_userPickTileTray( XW_UtilCtxt* uc, const PickInfo* pi, XP_U16 nTiles ) { XP_S16 chosen; - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; XP_UCHAR* name = globals->cGlobals.gi.players[playerNum].name; chosen = gtkletterask( pi, XP_TRUE, name, nTiles, texts ); @@ -1406,7 +1406,7 @@ gtk_util_askPassword( XW_UtilCtxt* XP_UNUSED(uc), const XP_UCHAR* name, } /* gtk_util_askPassword */ static void -setCtrlsForTray( GtkAppGlobals* XP_UNUSED(globals) ) +setCtrlsForTray( GtkGameGlobals* XP_UNUSED(globals) ) { #if 0 XW_TrayVisState state = @@ -1435,7 +1435,7 @@ static void gtk_util_trayHiddenChange( XW_UtilCtxt* uc, XW_TrayVisState XP_UNUSED(state), XP_U16 XP_UNUSED(nVisibleRows) ) { - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; setCtrlsForTray( globals ); } /* gtk_util_trayHiddenChange */ @@ -1444,7 +1444,7 @@ gtk_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 maxOffset, XP_U16 XP_UNUSED(oldOffset), XP_U16 newOffset ) { - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; if ( !!globals->adjustment ) { gint nRows = globals->cGlobals.gi.boardSize; globals->adjustment->page_size = nRows - maxOffset; @@ -1454,7 +1454,7 @@ gtk_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 maxOffset, } /* gtk_util_yOffsetChange */ static void -gtkShowFinalScores( const GtkAppGlobals* globals ) +gtkShowFinalScores( const GtkGameGlobals* globals ) { XWStreamCtxt* stream; XP_UCHAR* text; @@ -1478,7 +1478,7 @@ static void gtk_util_informMove( XW_UtilCtxt* uc, XWStreamCtxt* expl, XWStreamCtxt* words ) { - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; char* question = strFromStream( !!words? words : expl ); (void)gtkask( globals->window, question, GTK_BUTTONS_OK ); free( question ); @@ -1487,7 +1487,7 @@ gtk_util_informMove( XW_UtilCtxt* uc, XWStreamCtxt* expl, static void gtk_util_informUndo( XW_UtilCtxt* uc ) { - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; (void)gtkask_timeout( globals->window, "Remote player undid a move", GTK_BUTTONS_OK, 500 ); } @@ -1495,7 +1495,7 @@ gtk_util_informUndo( XW_UtilCtxt* uc ) static void gtk_util_notifyGameOver( XW_UtilCtxt* uc, XP_S16 quitter ) { - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; CommonGlobals* cGlobals = &globals->cGlobals; if ( cGlobals->params->printHistory ) { @@ -1521,7 +1521,7 @@ gtk_util_informNetDict( XW_UtilCtxt* uc, XP_LangCode XP_UNUSED(lang), const XP_UCHAR* newName, const XP_UCHAR* newSum, XWPhoniesChoice phoniesAction ) { - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; gchar buf[512]; int offset = snprintf( buf, VSIZE(buf), @@ -1557,7 +1557,7 @@ gtk_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer ) static XP_Bool gtk_util_hiliteCell( XW_UtilCtxt* uc, XP_U16 col, XP_U16 row ) { - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; #ifndef DONT_ABORT_ENGINE gboolean pending; #endif @@ -1582,7 +1582,7 @@ gtk_util_hiliteCell( XW_UtilCtxt* uc, XP_U16 col, XP_U16 row ) static XP_Bool gtk_util_altKeyDown( XW_UtilCtxt* uc ) { - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; return globals->altKeyDown; } @@ -1601,7 +1601,7 @@ gtk_util_engineProgressCallback( XW_UtilCtxt* XP_UNUSED(uc) ) } /* gtk_util_engineProgressCallback */ static void -cancelTimer( GtkAppGlobals* globals, XWTimerReason why ) +cancelTimer( GtkGameGlobals* globals, XWTimerReason why ) { guint src = globals->timerSources[why-1]; if ( src != 0 ) { @@ -1613,7 +1613,7 @@ cancelTimer( GtkAppGlobals* globals, XWTimerReason why ) static gint pen_timer_func( gpointer data ) { - GtkAppGlobals* globals = (GtkAppGlobals*)data; + GtkGameGlobals* globals = (GtkGameGlobals*)data; if ( linuxFireTimer( &globals->cGlobals, TIMER_PENDOWN ) ) { board_draw( globals->cGlobals.game.board ); @@ -1625,7 +1625,7 @@ pen_timer_func( gpointer data ) static gint score_timer_func( gpointer data ) { - GtkAppGlobals* globals = (GtkAppGlobals*)data; + GtkGameGlobals* globals = (GtkGameGlobals*)data; if ( linuxFireTimer( &globals->cGlobals, TIMER_TIMERTICK ) ) { board_draw( globals->cGlobals.game.board ); @@ -1638,7 +1638,7 @@ score_timer_func( gpointer data ) static gint comms_timer_func( gpointer data ) { - GtkAppGlobals* globals = (GtkAppGlobals*)data; + GtkGameGlobals* globals = (GtkGameGlobals*)data; if ( linuxFireTimer( &globals->cGlobals, TIMER_COMMS ) ) { board_draw( globals->cGlobals.game.board ); @@ -1652,7 +1652,7 @@ comms_timer_func( gpointer data ) static gint slowrob_timer_func( gpointer data ) { - GtkAppGlobals* globals = (GtkAppGlobals*)data; + GtkGameGlobals* globals = (GtkGameGlobals*)data; if ( linuxFireTimer( &globals->cGlobals, TIMER_SLOWROBOT ) ) { board_draw( globals->cGlobals.game.board ); @@ -1667,7 +1667,7 @@ gtk_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, XP_U16 XP_UNUSED_STANDALONE(when), XWTimerProc proc, void* closure ) { - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; guint newSrc; cancelTimer( globals, why ); @@ -1705,14 +1705,14 @@ gtk_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, static void gtk_util_clearTimer( XW_UtilCtxt* uc, XWTimerReason why ) { - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; globals->cGlobals.timerInfo[why].proc = NULL; } static gint idle_func( gpointer data ) { - GtkAppGlobals* globals = (GtkAppGlobals*)data; + GtkGameGlobals* globals = (GtkGameGlobals*)data; /* XP_DEBUGF( "idle_func called\n" ); */ /* remove before calling server_do. If server_do puts up a dialog that @@ -1731,7 +1731,7 @@ idle_func( gpointer data ) static void gtk_util_requestTime( XW_UtilCtxt* uc ) { - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; globals->idleID = gtk_idle_add( idle_func, globals ); } /* gtk_util_requestTime */ @@ -1740,7 +1740,7 @@ gtk_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 player, XP_Bool turnLost ) { XP_Bool result; - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; char buf[300]; if ( turnLost ) { @@ -1781,7 +1781,7 @@ gtk_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 player, static void gtk_util_remSelected( XW_UtilCtxt* uc ) { - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; XWStreamCtxt* stream; XP_UCHAR* text; @@ -1800,7 +1800,7 @@ gtk_util_remSelected( XW_UtilCtxt* uc ) static XWStreamCtxt* gtk_util_makeStreamFromAddr(XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) { - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; XWStreamCtxt* stream = mem_stream_make( MEMPOOL globals->cGlobals.params->vtMgr, @@ -1813,7 +1813,7 @@ gtk_util_makeStreamFromAddr(XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) static void gtk_util_showChat( XW_UtilCtxt* uc, const XP_UCHAR* const msg ) { - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; (void)gtkask( globals->window, msg, GTK_BUTTONS_OK ); } #endif @@ -1843,7 +1843,7 @@ gtk_util_playerScoreHeld( XW_UtilCtxt* uc, XP_U16 player ) { LOG_FUNC(); - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; XP_UCHAR scoreExpl[48] = {0}; XP_U16 explLen = sizeof(scoreExpl); @@ -1868,7 +1868,7 @@ gtk_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words ) static void gtk_util_userError( XW_UtilCtxt* uc, UtilErrID id ) { - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; XP_Bool silent; const XP_UCHAR* message = linux_getErrString( id, &silent ); @@ -1885,7 +1885,7 @@ static XP_Bool gtk_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id, XWStreamCtxt* stream ) { - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; XP_Bool result; char* question; XP_Bool freeMe = XP_FALSE; @@ -1921,7 +1921,7 @@ static XP_Bool gtk_util_confirmTrade( XW_UtilCtxt* uc, const XP_UCHAR** tiles, XP_U16 nTiles ) { - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; char question[256]; formatConfirmTrade( tiles, nTiles, question, sizeof(question) ); return gtkask( globals->window, question, GTK_BUTTONS_YES_NO ); @@ -1953,7 +1953,7 @@ makeShowButtonFromBitmap( void* closure, const gchar* filename, } /* makeShowButtonFromBitmap */ static GtkWidget* -makeVerticalBar( GtkAppGlobals* globals, GtkWidget* XP_UNUSED(window) ) +makeVerticalBar( GtkGameGlobals* globals, GtkWidget* XP_UNUSED(window) ) { GtkWidget* vbox; GtkWidget* button; @@ -2030,7 +2030,7 @@ makeVerticalBar( GtkAppGlobals* globals, GtkWidget* XP_UNUSED(window) ) } /* makeVerticalBar */ static GtkWidget* -makeButtons( GtkAppGlobals* globals ) +makeButtons( GtkGameGlobals* globals ) { short i; GtkWidget* hbox; @@ -2062,7 +2062,7 @@ makeButtons( GtkAppGlobals* globals ) } /* makeButtons */ static void -setupGtkUtilCallbacks( GtkAppGlobals* globals, XW_UtilCtxt* util ) +setupGtkUtilCallbacks( GtkGameGlobals* globals, XW_UtilCtxt* util ) { util->vtable->m_util_userError = gtk_util_userError; util->vtable->m_util_userQuery = gtk_util_userQuery; @@ -2118,7 +2118,7 @@ newConnectionInput( GIOChannel *source, { gboolean keepSource = TRUE; int sock = g_io_channel_unix_get_fd( source ); - GtkAppGlobals* globals = (GtkAppGlobals*)data; + GtkGameGlobals* globals = (GtkGameGlobals*)data; XP_LOGF( "%s(%p):condition = 0x%x", __func__, source, (int)condition ); @@ -2228,7 +2228,7 @@ typedef struct SockInfo { static void gtk_socket_changed( void* closure, int oldSock, int newSock, void** storage ) { - GtkAppGlobals* globals = (GtkAppGlobals*)closure; + GtkGameGlobals* globals = (GtkGameGlobals*)closure; SockInfo* info = (SockInfo*)*storage; XP_LOGF( "%s(old:%d; new:%d)", __func__, oldSock, newSock ); @@ -2328,14 +2328,14 @@ gtk_socket_acceptor( int listener, Acceptor func, CommonGlobals* globals, } /* gtk_socket_acceptor */ static void -drop_msg_toggle( GtkWidget* toggle, GtkAppGlobals* globals ) +drop_msg_toggle( GtkWidget* toggle, GtkGameGlobals* globals ) { globals->dropIncommingMsgs = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(toggle) ); } /* drop_msg_toggle */ #endif -static GtkAppGlobals* g_globals_for_signal; +static GtkGameGlobals* g_globals_for_signal; static void handle_sigintterm( int XP_UNUSED(sig) ) { @@ -2346,7 +2346,7 @@ handle_sigintterm( int XP_UNUSED(sig) ) /* int */ /* board_main( LaunchParams* params ) */ /* { */ -/* GtkAppGlobals globals; */ +/* GtkGameGlobals globals; */ /* initGlobals( &globals, params ); */ /* if ( !!params->pipe && !!params->fileName ) { */ @@ -2364,7 +2364,7 @@ handle_sigintterm( int XP_UNUSED(sig) ) /* } */ void -initGlobals( GtkAppGlobals* globals, LaunchParams* params ) +initGlobals( GtkGameGlobals* globals, LaunchParams* params ) { short width, height; GtkWidget* window; @@ -2519,13 +2519,13 @@ initGlobals( GtkAppGlobals* globals, LaunchParams* params ) } /* initGlobals */ void -freeGlobals( GtkAppGlobals* globals ) +freeGlobals( GtkGameGlobals* globals ) { cleanup( globals ); } XP_Bool -makeNewGame( GtkAppGlobals* globals ) +makeNewGame( GtkGameGlobals* globals ) { CommonGlobals* cGlobals = &globals->cGlobals; if ( !!cGlobals->game.comms ) { diff --git a/xwords4/linux/gtkboard.h b/xwords4/linux/gtkboard.h index 2fec8d385..c2e834242 100644 --- a/xwords4/linux/gtkboard.h +++ b/xwords4/linux/gtkboard.h @@ -45,7 +45,7 @@ typedef struct GtkDrawCtx { /* GdkDrawable* pixmap; */ GtkWidget* drawing_area; - struct GtkAppGlobals* globals; + struct GtkGameGlobals* globals; #ifdef USE_CAIRO cairo_t* cr; @@ -83,11 +83,11 @@ typedef struct ClientStreamRec { int sock; } ClientStreamRec; -typedef struct GtkAppGlobals { +typedef struct GtkGameGlobals { CommonGlobals cGlobals; GtkWidget* window; GtkDrawCtx* draw; - GTKGamesGlobals* gg; + GtkAppGlobals* apg; /* GdkPixmap* pixmap; */ GtkWidget* drawing_area; @@ -127,7 +127,7 @@ typedef struct GtkAppGlobals { #ifdef KEYBOARD_NAV XP_Bool keyDown; #endif -} GtkAppGlobals; +} GtkGameGlobals; /* DictionaryCtxt* gtk_dictionary_make(); */ #define GTK_MIN_SCALE 12 /* was 14 */ @@ -166,9 +166,9 @@ typedef struct GtkAppGlobals { #define GTK_BOTTOM_MARGIN GTK_TOP_MARGIN #define GTK_RIGHT_MARGIN GTK_BOARD_LEFT_MARGIN -void initGlobals( GtkAppGlobals* globals, LaunchParams* params ); -void freeGlobals( GtkAppGlobals* globals ); -XP_Bool makeNewGame( GtkAppGlobals* globals ); +void initGlobals( GtkGameGlobals* globals, LaunchParams* params ); +void freeGlobals( GtkGameGlobals* globals ); +XP_Bool makeNewGame( GtkGameGlobals* globals ); #endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkchat.c b/xwords4/linux/gtkchat.c index b8b09940e..3442a8b1b 100644 --- a/xwords4/linux/gtkchat.c +++ b/xwords4/linux/gtkchat.c @@ -22,7 +22,7 @@ #include "gtkchat.h" gchar* -gtkGetChatMessage( GtkAppGlobals* XP_UNUSED(globals) ) +gtkGetChatMessage( GtkGameGlobals* XP_UNUSED(globals) ) { gchar* result = NULL; GtkWidget* dialog = gtk_dialog_new_with_buttons( "message text", NULL, //GtkWindow *parent, diff --git a/xwords4/linux/gtkchat.h b/xwords4/linux/gtkchat.h index 0c3b80828..25a2dc415 100644 --- a/xwords4/linux/gtkchat.h +++ b/xwords4/linux/gtkchat.h @@ -25,7 +25,7 @@ #include #include "gtkboard.h" -gchar* gtkGetChatMessage( GtkAppGlobals* globals ); +gchar* gtkGetChatMessage( GtkGameGlobals* globals ); #endif #endif /* #ifndef _GTKCHAT_H_ */ diff --git a/xwords4/linux/gtkconnsdlg.c b/xwords4/linux/gtkconnsdlg.c index 6488453df..bbc410b96 100644 --- a/xwords4/linux/gtkconnsdlg.c +++ b/xwords4/linux/gtkconnsdlg.c @@ -24,7 +24,7 @@ #include "gtkutils.h" typedef struct _GtkConnsState { - GtkAppGlobals* globals; + GtkGameGlobals* globals; CommsAddrRec* addr; DeviceRole role; @@ -179,7 +179,7 @@ makeBTPage( GtkConnsState* state ) } /* makeBTPage */ gboolean -gtkConnsDlg( GtkAppGlobals* globals, CommsAddrRec* addr, DeviceRole role, +gtkConnsDlg( GtkGameGlobals* globals, CommsAddrRec* addr, DeviceRole role, XP_Bool readOnly ) { GtkConnsState state; diff --git a/xwords4/linux/gtkconnsdlg.h b/xwords4/linux/gtkconnsdlg.h index 31a5d7325..86bdb2f76 100644 --- a/xwords4/linux/gtkconnsdlg.h +++ b/xwords4/linux/gtkconnsdlg.h @@ -26,7 +26,7 @@ #include "gtkboard.h" -gboolean gtkConnsDlg( GtkAppGlobals* globals, CommsAddrRec* addr, +gboolean gtkConnsDlg( GtkGameGlobals* globals, CommsAddrRec* addr, DeviceRole role, XP_Bool readOnly ); #endif /* _GTKCONNSDLG_H_ */ diff --git a/xwords4/linux/gtkdraw.c b/xwords4/linux/gtkdraw.c index e352e7abf..1f6099518 100644 --- a/xwords4/linux/gtkdraw.c +++ b/xwords4/linux/gtkdraw.c @@ -1305,7 +1305,7 @@ allocAndSet( GdkColormap* map, GdkColor* color, unsigned short red, } /* allocAndSet */ DrawCtx* -gtkDrawCtxtMake( GtkWidget* drawing_area, GtkAppGlobals* globals ) +gtkDrawCtxtMake( GtkWidget* drawing_area, GtkGameGlobals* globals ) { GtkDrawCtx* dctx = g_malloc0( sizeof(GtkDrawCtx) ); GdkColormap* map; @@ -1427,7 +1427,7 @@ gtkDrawCtxtMake( GtkWidget* drawing_area, GtkAppGlobals* globals ) void draw_gtk_status( GtkDrawCtx* dctx, char ch ) { - GtkAppGlobals* globals = dctx->globals; + GtkGameGlobals* globals = dctx->globals; XP_Rect rect = { .left = globals->netStatLeft, diff --git a/xwords4/linux/gtkdraw.h b/xwords4/linux/gtkdraw.h index 26b3de603..452575a3c 100644 --- a/xwords4/linux/gtkdraw.h +++ b/xwords4/linux/gtkdraw.h @@ -22,7 +22,7 @@ #include "draw.h" -DrawCtx* gtkDrawCtxtMake( GtkWidget *widget, GtkAppGlobals* globals ); +DrawCtx* gtkDrawCtxtMake( GtkWidget *widget, GtkGameGlobals* globals ); void draw_gtk_status( GtkDrawCtx* draw, char ch ); diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index acb42ea4c..18a3693aa 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -26,29 +26,29 @@ #include "gtkboard.h" #include "linuxmain.h" -static void onNewData( GTKGamesGlobals* gg, sqlite3_int64 rowid, +static void onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid, XP_Bool isNew ); static void -recordOpened( GTKGamesGlobals* gg, GtkAppGlobals* globals ) +recordOpened( GtkAppGlobals* apg, GtkGameGlobals* globals ) { - gg->globalsList = g_slist_prepend( gg->globalsList, globals ); - globals->gg = gg; + apg->globalsList = g_slist_prepend( apg->globalsList, globals ); + globals->apg = apg; } static void -recordClosed( GTKGamesGlobals* gg, GtkAppGlobals* globals ) +recordClosed( GtkAppGlobals* apg, GtkGameGlobals* globals ) { - gg->globalsList = g_slist_remove( gg->globalsList, globals ); + apg->globalsList = g_slist_remove( apg->globalsList, globals ); } static XP_Bool -gameIsOpen( GTKGamesGlobals* gg, sqlite3_int64 rowid ) +gameIsOpen( GtkAppGlobals* apg, sqlite3_int64 rowid ) { XP_Bool found = XP_FALSE; GSList* iter; - for ( iter = gg->globalsList; !!iter && !found; iter = iter->next ) { - GtkAppGlobals* globals = (GtkAppGlobals*)iter->data; + for ( iter = apg->globalsList; !!iter && !found; iter = iter->next ) { + GtkGameGlobals* globals = (GtkGameGlobals*)iter->data; found = globals->cGlobals.selRow == rowid; } return found; @@ -60,13 +60,13 @@ enum { CHECK_ITEM, ROW_ITEM, NAME_ITEM, ROOM_ITEM, OVER_ITEM, TURN_ITEM, NMOVES_ static void tree_selection_changed_cb( GtkTreeSelection* selection, gpointer data ) { - GTKGamesGlobals* gg = (GTKGamesGlobals*)data; + GtkAppGlobals* apg = (GtkAppGlobals*)data; GtkTreeIter iter; GtkTreeModel *model; if ( gtk_tree_selection_get_selected( selection, &model, &iter ) ) { - gtk_tree_model_get( model, &iter, ROW_ITEM, &gg->selRow, -1 ); + gtk_tree_model_get( model, &iter, ROW_ITEM, &apg->selRow, -1 ); } } @@ -81,7 +81,7 @@ addTextColumn( GtkWidget* list, const gchar* title, int item ) } static GtkWidget* -init_games_list( GTKGamesGlobals* gg ) +init_games_list( GtkAppGlobals* apg ) { GtkWidget* list = gtk_tree_view_new(); GtkCellRenderer* renderer; @@ -113,7 +113,7 @@ init_games_list( GTKGamesGlobals* gg ) gtk_tree_view_get_selection( GTK_TREE_VIEW (list) ); gtk_tree_selection_set_mode( select, GTK_SELECTION_SINGLE ); g_signal_connect( G_OBJECT(select), "changed", - G_CALLBACK (tree_selection_changed_cb), gg ); + G_CALLBACK (tree_selection_changed_cb), apg ); return list; } @@ -153,18 +153,18 @@ add_to_list( GtkWidget* list, sqlite3_int64 rowid, XP_Bool isNew, static void handle_newgame_button( GtkWidget* XP_UNUSED(widget), void* closure ) { - GTKGamesGlobals* gg = (GTKGamesGlobals*)closure; + GtkAppGlobals* apg = (GtkAppGlobals*)closure; XP_LOGF( "%s called", __func__ ); - GtkAppGlobals* globals = malloc( sizeof(*globals) ); - gg->params->needsNewGame = XP_FALSE; - initGlobals( globals, gg->params ); + GtkGameGlobals* globals = malloc( sizeof(*globals) ); + apg->params->needsNewGame = XP_FALSE; + initGlobals( globals, apg->params ); if ( !makeNewGame( globals ) ) { freeGlobals( globals ); } else { GtkWidget* gameWindow = globals->window; - globals->cGlobals.pDb = gg->pDb; + globals->cGlobals.pDb = apg->pDb; globals->cGlobals.selRow = -1; - recordOpened( gg, globals ); + recordOpened( apg, globals ); gtk_widget_show( gameWindow ); } } @@ -172,23 +172,22 @@ handle_newgame_button( GtkWidget* XP_UNUSED(widget), void* closure ) static void handle_open_button( GtkWidget* XP_UNUSED(widget), void* closure ) { - GTKGamesGlobals* gg = (GTKGamesGlobals*)closure; - if ( -1 != gg->selRow && !gameIsOpen( gg, gg->selRow ) ) { - gg->params->needsNewGame = XP_FALSE; - GtkAppGlobals* globals = malloc( sizeof(*globals) ); - initGlobals( globals, gg->params ); - globals->cGlobals.pDb = gg->pDb; - globals->cGlobals.selRow = gg->selRow; - recordOpened( gg, globals ); + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + if ( -1 != apg->selRow && !gameIsOpen( apg, apg->selRow ) ) { + apg->params->needsNewGame = XP_FALSE; + GtkGameGlobals* globals = malloc( sizeof(*globals) ); + initGlobals( globals, apg->params ); + globals->cGlobals.pDb = apg->pDb; + globals->cGlobals.selRow = apg->selRow; + recordOpened( apg, globals ); gtk_widget_show( globals->window ); } } static void -handle_quit_button( GtkWidget* XP_UNUSED(widget), gpointer data ) +handle_quit_button( GtkWidget* XP_UNUSED(widget), gpointer XP_UNUSED(data) ) { - GTKGamesGlobals* gg = (GTKGamesGlobals*)data; - gg = gg; + // GtkAppGlobals* apg = (GtkAppGlobals*)data; gtk_main_quit(); } @@ -196,13 +195,13 @@ static void handle_destroy( GtkWidget* XP_UNUSED(widget), gpointer data ) { LOG_FUNC(); - GTKGamesGlobals* gg = (GTKGamesGlobals*)data; + GtkAppGlobals* apg = (GtkAppGlobals*)data; GSList* iter; - for ( iter = gg->globalsList; !!iter; iter = iter->next ) { - GtkAppGlobals* globals = (GtkAppGlobals*)iter->data; + for ( iter = apg->globalsList; !!iter; iter = iter->next ) { + GtkGameGlobals* globals = (GtkGameGlobals*)iter->data; freeGlobals( globals ); } - g_slist_free( gg->globalsList ); + g_slist_free( apg->globalsList ); gtk_main_quit(); } @@ -217,27 +216,27 @@ addButton( gchar* label, GtkWidget* parent, GCallback proc, void* closure ) } static GtkWidget* -makeGamesWindow( GTKGamesGlobals* gg ) +makeGamesWindow( GtkAppGlobals* apg ) { GtkWidget* window; window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); g_signal_connect( G_OBJECT(window), "destroy", - G_CALLBACK(handle_destroy), gg ); + G_CALLBACK(handle_destroy), apg ); GtkWidget* vbox = gtk_vbox_new( FALSE, 0 ); gtk_container_add( GTK_CONTAINER(window), vbox ); gtk_widget_show( vbox ); - GtkWidget* list = init_games_list( gg ); - gg->listWidget = list; + GtkWidget* list = init_games_list( apg ); + apg->listWidget = list; gtk_container_add( GTK_CONTAINER(vbox), list ); gtk_widget_show( list ); - GSList* games = listGames( gg ); + GSList* games = listGames( apg ); for ( GSList* iter = games; !!iter; iter = iter->next ) { sqlite3_int64* rowid = (sqlite3_int64*)iter->data; - onNewData( gg, *rowid, XP_TRUE ); + onNewData( apg, *rowid, XP_TRUE ); } g_slist_free( games ); @@ -245,9 +244,9 @@ makeGamesWindow( GTKGamesGlobals* gg ) gtk_widget_show( hbox ); gtk_container_add( GTK_CONTAINER(vbox), hbox ); - addButton( "New game", hbox, G_CALLBACK(handle_newgame_button), gg ); - addButton( "Open", hbox, G_CALLBACK(handle_open_button), gg ); - addButton( "Quit", hbox, G_CALLBACK(handle_quit_button), gg ); + addButton( "New game", hbox, G_CALLBACK(handle_newgame_button), apg ); + addButton( "Open", hbox, G_CALLBACK(handle_open_button), apg ); + addButton( "Quit", hbox, G_CALLBACK(handle_quit_button), apg ); gtk_widget_show( window ); return window; @@ -257,26 +256,26 @@ static gint freeGameGlobals( gpointer data ) { LOG_FUNC(); - GtkAppGlobals* globals = (GtkAppGlobals*)data; - GTKGamesGlobals* gg = globals->gg; - recordClosed( gg, globals ); + GtkGameGlobals* globals = (GtkGameGlobals*)data; + GtkAppGlobals* apg = globals->apg; + recordClosed( apg, globals ); freeGlobals( globals ); return 0; /* don't run again */ } void -windowDestroyed( GtkAppGlobals* globals ) +windowDestroyed( GtkGameGlobals* globals ) { /* schedule to run after we're back to main loop */ (void)g_idle_add( freeGameGlobals, globals ); } static void -onNewData( GTKGamesGlobals* gg, sqlite3_int64 rowid, XP_Bool isNew ) +onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid, XP_Bool isNew ) { GameInfo gib; - if ( getGameInfo( gg, rowid, &gib ) ) { - add_to_list( gg->listWidget, rowid, isNew, &gib ); + if ( getGameInfo( apg, rowid, &gib ) ) { + add_to_list( apg->listWidget, rowid, isNew, &gib ); } } @@ -284,23 +283,23 @@ void onGameSaved( void* closure, sqlite3_int64 rowid, XP_Bool firstTime ) { - GtkAppGlobals* globals = (GtkAppGlobals*)closure; - GTKGamesGlobals* gg = globals->gg; - onNewData( gg, rowid, firstTime ); + GtkGameGlobals* globals = (GtkGameGlobals*)closure; + GtkAppGlobals* apg = globals->apg; + onNewData( apg, rowid, firstTime ); } int gtkmain( LaunchParams* params ) { - GTKGamesGlobals gg = {0}; - gg.selRow = -1; - gg.params = params; - gg.pDb = openGamesDB( params->dbName ); + GtkAppGlobals apg = {0}; + apg.selRow = -1; + apg.params = params; + apg.pDb = openGamesDB( params->dbName ); - (void)makeGamesWindow( &gg ); + (void)makeGamesWindow( &apg ); gtk_main(); - closeGamesDB( gg.pDb ); + closeGamesDB( apg.pDb ); return 0; } /* gtkmain */ diff --git a/xwords4/linux/gtkmain.h b/xwords4/linux/gtkmain.h index c7d5e13e3..547ce5df4 100644 --- a/xwords4/linux/gtkmain.h +++ b/xwords4/linux/gtkmain.h @@ -24,7 +24,7 @@ #include "gtkboard.h" int gtkmain( LaunchParams* params ); -void windowDestroyed( GtkAppGlobals* globals ); +void windowDestroyed( GtkGameGlobals* globals ); void onGameSaved( void* closure, sqlite3_int64 rowid, XP_Bool firstTime ); #endif diff --git a/xwords4/linux/gtknewgame.c b/xwords4/linux/gtknewgame.c index a491263b5..06b8f6085 100644 --- a/xwords4/linux/gtknewgame.c +++ b/xwords4/linux/gtknewgame.c @@ -32,7 +32,7 @@ #define MAX_SIZE_CHOICES 32 typedef struct GtkNewGameState { - GtkAppGlobals* globals; + GtkGameGlobals* globals; CurGameInfo* gi; NewGameCtx* newGameCtxt; @@ -544,7 +544,7 @@ gtk_newgame_attr_set( void* closure, NewGameAttr attr, NGValue value ) } gboolean -newGameDialog( GtkAppGlobals* globals, CurGameInfo* gi, CommsAddrRec* addr, +newGameDialog( GtkGameGlobals* globals, CurGameInfo* gi, CommsAddrRec* addr, XP_Bool isNewGame, XP_Bool fireConnDlg ) { GtkNewGameState state; diff --git a/xwords4/linux/gtknewgame.h b/xwords4/linux/gtknewgame.h index dad9e6074..0f2aed6fc 100644 --- a/xwords4/linux/gtknewgame.h +++ b/xwords4/linux/gtknewgame.h @@ -26,7 +26,7 @@ #include "gtkboard.h" -gboolean newGameDialog( GtkAppGlobals* globals, CurGameInfo* gi, +gboolean newGameDialog( GtkGameGlobals* globals, CurGameInfo* gi, CommsAddrRec* addr, XP_Bool isNewGame, XP_Bool fireConnDlg ); diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index 8533bb2a2..b2a68a694 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -218,12 +218,12 @@ struct CommonGlobals { XP_U16 curSaveToken; }; -typedef struct _GTKGamesGlobals { +typedef struct _GtkAppGlobals { sqlite3* pDb; sqlite3_int64 selRow; LaunchParams* params; GSList* globalsList; GtkWidget* listWidget; -} GTKGamesGlobals; +} GtkAppGlobals; #endif From f6e7ece578879ed901dc3db3539b856495e21458 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 16 Jan 2013 06:31:09 -0800 Subject: [PATCH 017/116] add comms_stop() and an assert to make sure it's called. Explicit stop and a message to the relay when using a UDP socket makes up for there being no notification of connection closing. --- xwords4/common/comms.c | 18 +++++++++++++++--- xwords4/common/comms.h | 1 + 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c index 4e7bcb387..245fcefa2 100644 --- a/xwords4/common/comms.c +++ b/xwords4/common/comms.c @@ -462,8 +462,11 @@ comms_transportFailed( CommsCtxt* comms ) void comms_destroy( CommsCtxt* comms ) { - CommsAddrRec aNew; - aNew.conType = COMMS_CONN_NONE; + /* did I call comms_stop()? */ + XP_ASSERT( COMMS_CONN_RELAY != comms->addr.conType + || COMMS_RELAYSTATE_UNCONNECTED == comms->r.relayState ); + + CommsAddrRec aNew = { .conType = COMMS_CONN_NONE }; util_addrChange( comms->util, &comms->addr, &aNew ); cleanupInternal( comms ); @@ -660,6 +663,15 @@ comms_start( CommsCtxt* comms ) sendConnect( comms, XP_FALSE ); } /* comms_start */ +void +comms_stop( CommsCtxt* comms ) +{ + if ( COMMS_CONN_RELAY == comms->addr.conType + && COMMS_RELAYSTATE_DENIED != comms->r.relayState ) { + relayDisconnect( comms ); + } +} + static void sendConnect( CommsCtxt* comms, XP_Bool breakExisting ) { @@ -2172,7 +2184,7 @@ msg_to_stream( CommsCtxt* comms, XWRELAY_Cmd cmd, XWHostID destID, case XWRELAY_MSG_TORELAY_NOCONN: stream_putU8( stream, comms->r.myHostID ); stream_putU8( stream, destID ); - XP_LOGF( "%s: wrote ids %d, %d", __func__, + XP_LOGF( "%s: wrote ids src %d, dest %d", __func__, comms->r.myHostID, destID ); if ( data != NULL && datalen > 0 ) { stream_putBytes( stream, data, datalen ); diff --git a/xwords4/common/comms.h b/xwords4/common/comms.h index 04234c995..b7062e337 100644 --- a/xwords4/common/comms.h +++ b/xwords4/common/comms.h @@ -198,6 +198,7 @@ CommsCtxt* comms_makeFromStream( MPFORMAL XWStreamCtxt* stream, XW_UtilCtxt* util, const TransportProcs* procs ); void comms_start( CommsCtxt* comms ); +void comms_stop( CommsCtxt* comms ); void comms_writeToStream( CommsCtxt* comms, XWStreamCtxt* stream, XP_U16 saveToken ); void comms_saveSucceeded( CommsCtxt* comms, XP_U16 saveToken ); From a6784464ff5515952bab527027801b1aafdf70ad Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 16 Jan 2013 06:46:33 -0800 Subject: [PATCH 018/116] first set of networking changes testing per-device communication with relay on behalf of a number of games. Works as long as all the games are open. --- xwords4/linux/Makefile | 1 + xwords4/linux/cursesmain.h | 7 -- xwords4/linux/gamesdb.c | 41 +++++++- xwords4/linux/gamesdb.h | 5 + xwords4/linux/gtkboard.c | 50 +++++++++- xwords4/linux/gtkboard.h | 2 +- xwords4/linux/gtkdraw.c | 3 +- xwords4/linux/gtkmain.c | 107 +++++++++++++++++++- xwords4/linux/linuxmain.c | 103 ++++++++++---------- xwords4/linux/main.h | 15 +++ xwords4/linux/relaycon.c | 193 +++++++++++++++++++++++++++++++++++++ xwords4/linux/relaycon.h | 36 +++++++ 12 files changed, 498 insertions(+), 65 deletions(-) create mode 100644 xwords4/linux/relaycon.c create mode 100644 xwords4/linux/relaycon.h diff --git a/xwords4/linux/Makefile b/xwords4/linux/Makefile index 38861248a..9a74cefbc 100644 --- a/xwords4/linux/Makefile +++ b/xwords4/linux/Makefile @@ -204,6 +204,7 @@ OBJ = \ $(BUILD_PLAT_DIR)/linuxdict.o \ $(BUILD_PLAT_DIR)/linuxutl.o \ $(BUILD_PLAT_DIR)/gamesdb.o \ + $(BUILD_PLAT_DIR)/relaycon.o \ $(CURSES_OBJS) $(GTK_OBJS) $(MAIN_OBJS) LIBS = -lm -luuid $(GPROFFLAG) diff --git a/xwords4/linux/cursesmain.h b/xwords4/linux/cursesmain.h index 74c5da17b..29e63e1e3 100644 --- a/xwords4/linux/cursesmain.h +++ b/xwords4/linux/cursesmain.h @@ -94,13 +94,6 @@ struct CursesAppGlobals { #endif }; -#ifdef USE_GLIBLOOP -typedef struct _SourceData { - GIOChannel* channel; - gint watch; -} SourceData; -#endif - DrawCtx* cursesDrawCtxtMake( WINDOW* boardWin ); /* Ports: Client and server pick a port at startup on which they'll listen. diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c index d52409c32..c507bde76 100644 --- a/xwords4/linux/gamesdb.c +++ b/xwords4/linux/gamesdb.c @@ -32,7 +32,7 @@ openGamesDB( const char* dbName ) int result = sqlite3_open( dbName, &pDb ); XP_ASSERT( SQLITE_OK == result ); - const char* createStr = + const char* createGamesStr = "CREATE TABLE games ( " "game BLOB" ",room VARCHAR(32)" @@ -41,10 +41,12 @@ openGamesDB( const char* dbName ) ",nmoves INT" ",nmissing INT(2)" ")"; + result = sqlite3_exec( pDb, createGamesStr, NULL, NULL, NULL ); - result = sqlite3_exec( pDb, createStr, NULL, NULL, NULL ); + const char* createValuesStr = + "CREATE TABLE pairs ( key TEXT UNIQUE,value TEXT )"; + result = sqlite3_exec( pDb, createValuesStr, NULL, NULL, NULL ); XP_LOGF( "sqlite3_exec=>%d", result ); - // XP_ASSERT( SQLITE_OK == result ); return pDb; } @@ -214,6 +216,39 @@ loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ) return XP_TRUE; } +void +store( sqlite3* pDb, const gchar* key, const gchar* value ) +{ + char buf[256]; + snprintf( buf, sizeof(buf), + "INSERT OR REPLACE INTO pairs (key, value) VALUES ('%s', '%s')", + key, value ); + sqlite3_stmt *ppStmt; + int result = sqlite3_prepare_v2( pDb, buf, -1, &ppStmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( ppStmt ); + XP_ASSERT( SQLITE_DONE == result ); + sqlite3_finalize( ppStmt ); +} + +void +fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen ) +{ + char query[256]; + snprintf( query, sizeof(query), + "SELECT value from pairs where key = '%s'", key ); + sqlite3_stmt *ppStmt; + int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( ppStmt ); + if ( SQLITE_ROW == result ) { + getColumnText( ppStmt, 0, buf, buflen ); + } else { + buf[0] = '\0'; + } + sqlite3_finalize( ppStmt ); +} + static void getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, int len ) { diff --git a/xwords4/linux/gamesdb.h b/xwords4/linux/gamesdb.h index 323a1ece2..7d517a655 100644 --- a/xwords4/linux/gamesdb.h +++ b/xwords4/linux/gamesdb.h @@ -48,4 +48,9 @@ GSList* listGames( GtkAppGlobals* apg ); XP_Bool getGameInfo( GtkAppGlobals* apg, sqlite3_int64 rowid, GameInfo* gib ); XP_Bool loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ); +#define KEY_RDEVID "RDEVID" + +void store( sqlite3* dbp, const gchar* key, const gchar* value ); +void fetch( sqlite3* dbp, const gchar* key, gchar* buf, gint buflen ); + #endif diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 8bdef1dcf..6f9f0b119 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -1,6 +1,7 @@ -/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ /* - * Copyright 2000-2009 by Eric House (xwords@eehouse.org). All rights reserved. + * Copyright 2000-2013 by Eric House (xwords@eehouse.org). All rights + * reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -498,6 +499,9 @@ createOrLoadObjects( GtkGameGlobals* globals ) #endif } + /* Need to save in order to have a valid selRow for the first send */ + saveGame( cGlobals ); + #ifndef XWFEATURE_STANDALONE_ONLY /* This may trigger network activity */ if ( !!cGlobals->game.comms ) { @@ -530,6 +534,12 @@ createOrLoadObjects( GtkGameGlobals* globals ) } } + if ( !params->fileName ) { + XP_UCHAR buf[64]; + snprintf( buf, sizeof(buf), "clientToken: %lld", cGlobals->selRow ); + gtk_window_set_title( GTK_WINDOW(globals->window), buf ); + } + #ifndef XWFEATURE_STANDALONE_ONLY if ( !!globals->cGlobals.game.comms ) { comms_start( globals->cGlobals.game.comms ); @@ -693,6 +703,7 @@ destroy_window( GtkWidget* XP_UNUSED(widget), gpointer data ) { LOG_FUNC(); GtkGameGlobals* globals = (GtkGameGlobals*)data; + comms_stop( globals->cGlobals.game.comms ); saveGame( &globals->cGlobals ); windowDestroyed( globals ); // gtk_main_quit(); @@ -2219,7 +2230,7 @@ newConnectionInput( GIOChannel *source, return keepSource; /* FALSE means to remove event source */ } /* newConnectionInput */ -typedef struct SockInfo { +typedef struct _SockInfo { GIOChannel* channel; guint watch; int socket; @@ -2547,4 +2558,37 @@ makeNewGame( GtkGameGlobals* globals ) return success; } +void +gameGotBuf( GtkGameGlobals* globals, XP_U8* buf, XP_U16 len ) +{ + XP_Bool redraw = XP_FALSE; + + XWStreamCtxt* stream = stream_from_msgbuf( &globals->cGlobals, buf, len ); + if ( !!stream ) { + if ( comms_checkIncomingStream( globals->cGlobals.game.comms, + stream, NULL ) ) { + redraw = + server_receiveMessage( globals->cGlobals.game.server, + stream ); + if ( redraw ) { + saveGame( &globals->cGlobals ); + } + } + stream_destroy( stream ); + } + + /* if there's something to draw resulting from the message, we + need to give the main loop time to reflect that on the screen + before giving the server another shot. So just call the idle + proc. */ + if ( redraw ) { + gtk_util_requestTime( globals->cGlobals.util ); + } else { + redraw = server_do( globals->cGlobals.game.server ); + } + if ( redraw ) { + board_draw( globals->cGlobals.game.board ); + } +} + #endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkboard.h b/xwords4/linux/gtkboard.h index c2e834242..e2ad10dac 100644 --- a/xwords4/linux/gtkboard.h +++ b/xwords4/linux/gtkboard.h @@ -169,7 +169,7 @@ typedef struct GtkGameGlobals { void initGlobals( GtkGameGlobals* globals, LaunchParams* params ); void freeGlobals( GtkGameGlobals* globals ); XP_Bool makeNewGame( GtkGameGlobals* globals ); - +void gameGotBuf( GtkGameGlobals* globals, XP_U8* buf, XP_U16 len ); #endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkdraw.c b/xwords4/linux/gtkdraw.c index 1f6099518..6a89bb740 100644 --- a/xwords4/linux/gtkdraw.c +++ b/xwords4/linux/gtkdraw.c @@ -515,7 +515,8 @@ gtk_draw_drawCell( DrawCtx* p_dctx, const XP_Rect* rect, const XP_UCHAR* letter, { GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; XP_Rect rectInset = *rect; - XP_Bool showGrid = dctx->globals->gridOn; + GtkGameGlobals* globals = dctx->globals; + XP_Bool showGrid = globals->gridOn; XP_Bool highlight = (flags & CELL_HIGHLIGHT) != 0; GdkColor* cursor = ((flags & CELL_ISCURSOR) != 0) ? &dctx->cursor : NULL; diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index 18a3693aa..2512e826c 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -20,11 +20,12 @@ #ifdef PLATFORM_GTK -#include "xptypes.h" #include "main.h" +#include "gtkmain.h" #include "gamesdb.h" #include "gtkboard.h" #include "linuxmain.h" +#include "relaycon.h" static void onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid, XP_Bool isNew ); @@ -54,6 +55,22 @@ gameIsOpen( GtkAppGlobals* apg, sqlite3_int64 rowid ) return found; } +static GtkGameGlobals* +findGame( const GtkAppGlobals* apg, XP_U32 clientToken ) +{ + GtkGameGlobals* result = NULL; + GSList* iter; + for ( iter = apg->globalsList; !!iter; iter = iter->next ) { + GtkGameGlobals* globals = (GtkGameGlobals*)iter->data; + CommonGlobals* cGlobals = &globals->cGlobals; + if ( cGlobals->selRow == clientToken ) { + result = globals; + break; + } + } + return result; +} + enum { CHECK_ITEM, ROW_ITEM, NAME_ITEM, ROOM_ITEM, OVER_ITEM, TURN_ITEM, NMOVES_ITEM, MISSING_ITEM, N_ITEMS }; @@ -279,6 +296,66 @@ onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid, XP_Bool isNew ) } } +static gboolean +app_socket_proc( GIOChannel* source, GIOCondition condition, gpointer data ) +{ + if ( 0 != (G_IO_IN & condition) ) { + GtkAppGlobals* apg = (GtkAppGlobals*)data; + int socket = g_io_channel_unix_get_fd( source ); + GList* iter; + for ( iter = apg->sources; !!iter; iter = iter->next ) { + SourceData* sd = (SourceData*)iter->data; + if ( sd->channel == source ) { + (*sd->proc)( sd->procClosure, socket ); + break; + } + } + XP_ASSERT( !!iter ); /* didn't fail to find it */ + } + return TRUE; +} + +static void +socketChanged( void* closure, int newSock, int XP_UNUSED(oldSock), SockReceiver proc, + void* procClosure ) +{ + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + SourceData* sd = g_malloc( sizeof(*sd) ); + sd->channel = g_io_channel_unix_new( newSock ); + sd->watch = g_io_add_watch( sd->channel, G_IO_IN | G_IO_ERR, app_socket_proc, apg ); + sd->proc = proc; + sd->procClosure = procClosure; + apg->sources = g_list_append( apg->sources, sd ); +} + +static void +gtkGotBuf( void* closure, XP_U8* buf, XP_U16 len ) +{ + LOG_FUNC(); + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + XP_U32 gameToken; + XP_ASSERT( sizeof(gameToken) < len ); + gameToken = ntohl(*(XP_U32*)&buf[0]); + buf += sizeof(gameToken); + len -= sizeof(gameToken); + + GtkGameGlobals* globals = findGame( apg, gameToken ); + if ( !!globals ) { + gameGotBuf( globals, buf, len ); + } else { + XP_LOGF( "%s: game with token %lu not found; not open or deleted", + __func__, gameToken ); + } +} + +static void +gtkDevIDChanged( void* closure, const XP_UCHAR* devID ) +{ + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + XP_LOGF( "%s(devID=%s)", __func__, devID ); + store( apg->pDb, KEY_RDEVID, devID ); +} + void onGameSaved( void* closure, sqlite3_int64 rowid, XP_Bool firstTime ) @@ -295,11 +372,39 @@ gtkmain( LaunchParams* params ) apg.selRow = -1; apg.params = params; apg.pDb = openGamesDB( params->dbName ); + params->socketChanged = socketChanged; + params->socketChangedClosure = &apg; + + RelayConnProcs procs = { + .msgReceived = gtkGotBuf, + .devIDChanged = gtkDevIDChanged, + }; + + XP_UCHAR devIDBuf[64] = {0}; + XP_UCHAR* devID; + DevIDType typ = ID_TYPE_RELAY; + if ( !!params->rDevID ) { + devID = params->rDevID; + } else { + fetch( apg.pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); + if ( '\0' != devIDBuf[0] ) { + devID = devIDBuf; + } else { + devID = params->devID; + typ = ID_TYPE_LINUX; + } + } + + relaycon_init( params, &procs, &apg, + params->connInfo.relay.relayName, + params->connInfo.relay.defaultSendPort, + devID, typ ); (void)makeGamesWindow( &apg ); gtk_main(); closeGamesDB( apg.pDb ); + relaycon_cleanup( params ); return 0; } /* gtkmain */ diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 5a8e0d73a..340af33ef 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -59,6 +59,7 @@ #include "main.h" #include "gamesdb.h" #include "linuxdict.h" +#include "relaycon.h" #ifdef PLATFORM_NCURSES # include "cursesmain.h" #endif @@ -849,44 +850,49 @@ linux_init_relay_socket( CommonGlobals* cGlobals, const CommsAddrRec* addrRec ) } /* linux_init_relay_socket */ static XP_S16 -linux_tcp_send( const XP_U8* buf, XP_U16 buflen, - CommonGlobals* globals, const CommsAddrRec* addrRec ) +linux_tcp_send( CommonGlobals* cGlobals, const XP_U8* buf, XP_U16 buflen, + const CommsAddrRec* addrRec ) { XP_S16 result = 0; - int sock = globals->socket; - - if ( sock == -1 ) { - XP_LOGF( "%s: socket uninitialized", __func__ ); - sock = linux_init_relay_socket( globals, addrRec ); - if ( sock != -1 ) { - assert( globals->socket == sock ); - (*globals->socketChanged)( globals->socketChangedClosure, - -1, sock, &globals->storage ); - } - } - - if ( sock != -1 ) { - XP_U16 netLen = htons( buflen ); - errno = 0; - - result = send( sock, &netLen, sizeof(netLen), 0 ); - if ( result == sizeof(netLen) ) { - result = send( sock, buf, buflen, 0 ); - } - if ( result <= 0 ) { - XP_STATUSF( "closing non-functional socket" ); - close( sock ); - (*globals->socketChanged)( globals->socketChangedClosure, - sock, -1, &globals->storage ); - globals->socket = -1; - } - - XP_STATUSF( "%s: send(sock=%d) returned %d of %d (err=%d)", - __func__, sock, result, buflen, errno ); + if ( !!cGlobals->pDb ) { + XP_ASSERT( -1 != cGlobals->selRow ); + result = relaycon_send( cGlobals->params, buf, buflen, + cGlobals->selRow, addrRec ); } else { - XP_LOGF( "%s: socket still -1", __func__ ); + int sock = cGlobals->socket; + + if ( sock == -1 ) { + XP_LOGF( "%s: socket uninitialized", __func__ ); + sock = linux_init_relay_socket( cGlobals, addrRec ); + if ( sock != -1 ) { + assert( cGlobals->socket == sock ); + (*cGlobals->socketChanged)( cGlobals->socketChangedClosure, + -1, sock, &cGlobals->storage ); + } + } + + if ( sock != -1 ) { + XP_U16 netLen = htons( buflen ); + errno = 0; + + result = send( sock, &netLen, sizeof(netLen), 0 ); + if ( result == sizeof(netLen) ) { + result = send( sock, buf, buflen, 0 ); + } + if ( result <= 0 ) { + XP_STATUSF( "closing non-functional socket" ); + close( sock ); + (*cGlobals->socketChanged)( cGlobals->socketChangedClosure, + sock, -1, &cGlobals->storage ); + cGlobals->socket = -1; + } + + XP_STATUSF( "%s: send(sock=%d) returned %d of %d (err=%d)", + __func__, sock, result, buflen, errno ); + } else { + XP_LOGF( "%s: socket still -1", __func__ ); + } } - return result; } /* linux_tcp_send */ #endif /* XWFEATURE_RELAY */ @@ -928,54 +934,53 @@ linux_reset( void* closure ) #endif XP_S16 -linux_send( const XP_U8* buf, XP_U16 buflen, - const CommsAddrRec* addrRec, +linux_send( const XP_U8* buf, XP_U16 buflen, const CommsAddrRec* addrRec, XP_U32 XP_UNUSED(gameID), void* closure ) { XP_S16 nSent = -1; - CommonGlobals* globals = (CommonGlobals*)closure; + CommonGlobals* cGlobals = (CommonGlobals*)closure; CommsConnType conType; if ( !!addrRec ) { conType = addrRec->conType; } else { - conType = globals->params->conType; + conType = cGlobals->params->conType; } if ( 0 ) { #ifdef XWFEATURE_RELAY } else if ( conType == COMMS_CONN_RELAY ) { - nSent = linux_tcp_send( buf, buflen, globals, addrRec ); - if ( nSent == buflen && globals->params->duplicatePackets ) { + nSent = linux_tcp_send( cGlobals, buf, buflen, addrRec ); + if ( nSent == buflen && cGlobals->params->duplicatePackets ) { #ifdef DEBUG XP_S16 sentAgain = #endif - linux_tcp_send( buf, buflen, globals, addrRec ); + linux_tcp_send( cGlobals, buf, buflen, addrRec ); XP_ASSERT( sentAgain == nSent ); } #endif #if defined XWFEATURE_BLUETOOTH } else if ( conType == COMMS_CONN_BT ) { - XP_Bool isServer = comms_getIsServer( globals->game.comms ); - linux_bt_open( globals, isServer ); - nSent = linux_bt_send( buf, buflen, addrRec, globals ); + XP_Bool isServer = comms_getIsServer( cGlobals->game.comms ); + linux_bt_open( cGlobals, isServer ); + nSent = linux_bt_send( buf, buflen, addrRec, cGlobals ); #endif #if defined XWFEATURE_IP_DIRECT } else if ( conType == COMMS_CONN_IP_DIRECT ) { CommsAddrRec addr; - comms_getAddr( globals->game.comms, &addr ); - linux_udp_open( globals, &addr ); - nSent = linux_udp_send( buf, buflen, addrRec, globals ); + comms_getAddr( cGlobals->game.comms, &addr ); + linux_udp_open( cGlobals, &addr ); + nSent = linux_udp_send( buf, buflen, addrRec, cGlobals ); #endif #if defined XWFEATURE_SMS } else if ( COMMS_CONN_SMS == conType ) { CommsAddrRec addr; if ( !addrRec ) { - comms_getAddr( globals->game.comms, &addr ); + comms_getAddr( cGlobals->game.comms, &addr ); addrRec = &addr; } - nSent = linux_sms_send( globals, buf, buflen, + nSent = linux_sms_send( cGlobals, buf, buflen, addrRec->u.sms.phone, addrRec->u.sms.port ); #endif } else { diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index b2a68a694..4a4832419 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -44,6 +44,10 @@ typedef struct LinuxUtilCtxt { UtilVtable* vtable; } LinuxUtilCtxt; +typedef void (*SockReceiver)( void* closure, int socket ); +typedef void (*NewSocketProc)( void* closure, int newSock, int oldSock, + SockReceiver proc, void* procClosure ); + typedef struct LaunchParams { /* CommPipeCtxt* pipe; */ CurGameInfo pgi; @@ -51,12 +55,15 @@ typedef struct LaunchParams { GSList* dictDirs; char* fileName; char* dbName; + NewSocketProc socketChanged; + void* socketChangedClosure; XP_U16 saveFailPct; const XP_UCHAR* playerDictNames[MAX_NUM_PLAYERS]; #ifdef USE_SQLITE char* dbFileName; XP_U32 dbFileID; #endif + void* relayConStorage; /* opaque outside of relaycon.c */ char* pipe; char* nbs; char* bonusFile; @@ -218,11 +225,19 @@ struct CommonGlobals { XP_U16 curSaveToken; }; +typedef struct _SourceData { + GIOChannel* channel; + gint watch; + SockReceiver proc; + void* procClosure; +} SourceData; + typedef struct _GtkAppGlobals { sqlite3* pDb; sqlite3_int64 selRow; LaunchParams* params; GSList* globalsList; + GList* sources; GtkWidget* listWidget; } GtkAppGlobals; diff --git a/xwords4/linux/relaycon.c b/xwords4/linux/relaycon.c new file mode 100644 index 000000000..5893f9047 --- /dev/null +++ b/xwords4/linux/relaycon.c @@ -0,0 +1,193 @@ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2013 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program 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 program 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. + */ + +#include +#include + +#include "relaycon.h" +#include "comtypes.h" + +typedef struct _RelayConStorage { + int socket; + RelayConnProcs procs; + void* procsClosure; +} RelayConStorage; + +static RelayConStorage* getStorage( LaunchParams* params ); +static void addressToServer( struct sockaddr_in* to, const CommsAddrRec* addr ); +static XP_U32 addrForHost( const CommsAddrRec* addr ); +static XP_U32 hostNameToIP( const XP_UCHAR* name ); +static void relaycon_receive( void* closure, int socket ); + +void +relaycon_init( LaunchParams* params, const RelayConnProcs* procs, + void* procsClosure, const char* host, int port, + const XP_UCHAR* devID, DevIDType typ ) +{ + XP_ASSERT( !params->relayConStorage ); + RelayConStorage* storage = getStorage( params ); + XP_MEMCPY( &storage->procs, procs, sizeof(storage->procs) ); + storage->procsClosure = procsClosure; + + storage->socket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); + (*params->socketChanged)( params, storage->socket, -1, + relaycon_receive, params ); + + XP_ASSERT( !!devID ); + XP_U16 idLen = XP_STRLEN( devID ); + XP_U16 lenNBO = XP_HTONS( idLen ); + XP_U8 tmpbuf[1 + 1 + 1 + sizeof(lenNBO) + idLen]; + tmpbuf[0] = XWREG_PROTO_VERSION; + tmpbuf[1] = XWRREG_REG; + tmpbuf[2] = typ; + XP_MEMCPY( &tmpbuf[3], &lenNBO, sizeof(lenNBO) ); + XP_MEMCPY( &tmpbuf[5], devID, idLen ); + + struct sockaddr_in to = {0}; + to.sin_family = PF_INET; + to.sin_addr.s_addr = htonl( hostNameToIP(host) ); + to.sin_port = htons(port); + + (void)sendto( storage->socket, tmpbuf, sizeof(tmpbuf), 0, /* flags */ + (struct sockaddr*)&to, sizeof(to) ); +} + +XP_S16 +relaycon_send( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, + XP_U32 gameToken, const CommsAddrRec* addrRec ) +{ + ssize_t nSent = -1; + RelayConStorage* storage = getStorage( params ); + + struct sockaddr_in to = {0}; + addressToServer( &to, addrRec ); + + XP_U8 tmpbuf[1 + 1 + sizeof(gameToken) + buflen]; + tmpbuf[0] = XWREG_PROTO_VERSION; + tmpbuf[1] = XWRREG_MSG; + XP_U32 inNBO = htonl(gameToken); + XP_MEMCPY( &tmpbuf[2], &inNBO, sizeof(inNBO) ); + XP_MEMCPY( &tmpbuf[1 + 1 + sizeof(gameToken)], buf, buflen ); + nSent = sendto( storage->socket, tmpbuf, sizeof(tmpbuf), 0, /* flags */ + (struct sockaddr*)&to, sizeof(to) ); + if ( 1 + 1 + sizeof(gameToken) < nSent ) { + nSent -= 1 + 1 + sizeof(gameToken); + } + LOG_RETURNF( "%d", nSent ); + return nSent; +} + +static void +relaycon_receive( void* closure, int socket ) +{ + LaunchParams* params = (LaunchParams*)closure; + XP_ASSERT( !!params->relayConStorage ); + RelayConStorage* storage = getStorage( params ); + XP_U8 buf[512]; + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + + XP_LOGF( "%s: calling recvfrom on socket %d", __func__, socket ); + + ssize_t nRead = recvfrom( socket, buf, sizeof(buf), 0, /* flags */ + (struct sockaddr*)&from, &fromlen ); + XP_LOGF( "%s: read %d bytes", __func__, nRead ); + if ( 0 <= nRead ) { + XP_U8* ptr = buf; + const XP_U8* end = buf + nRead; + XP_ASSERT( XWREG_PROTO_VERSION == *ptr++ ); + XWRelayReg cmd = *ptr++; + switch( cmd ) { + case XWRREG_REGRSP: { + XP_U16 len; + XP_MEMCPY( &len, ptr, sizeof(len) ); + len = ntohs( len ); + ptr += sizeof( len ); + XP_UCHAR devID[len+1]; + XP_MEMCPY( devID, ptr, len ); + devID[len] = '\0'; + (*storage->procs.devIDChanged)( storage->procsClosure, devID ); + } + break; + case XWRREG_MSG: + (*storage->procs.msgReceived)( storage->procsClosure, + ptr, end - ptr ); + break; + default: + XP_LOGF( "%s: Unexpected cmd %d", __func__, cmd ); + XP_ASSERT( 0 ); + } + } else { + XP_LOGF( "%s: error reading udp socket: %d (%s)", __func__, + errno, strerror(errno) ); + } +} + +void +relaycon_cleanup( LaunchParams* params ) +{ + XP_FREEP( params->mpool, ¶ms->relayConStorage ); +} + +static RelayConStorage* +getStorage( LaunchParams* params ) +{ + RelayConStorage* storage = (RelayConStorage*)params->relayConStorage; + if ( NULL == storage ) { + storage = XP_CALLOC( params->mpool, sizeof(*storage) ); + storage->socket = -1; + params->relayConStorage = storage; + } + return storage; +} + +static void +addressToServer( struct sockaddr_in* to, const CommsAddrRec* addr ) +{ + to->sin_family = PF_INET; + to->sin_addr.s_addr = htonl( addrForHost(addr) ); + to->sin_port = htons(addr->u.ip_relay.port); +} + +static XP_U32 +addrForHost( const CommsAddrRec* addr ) +{ + XP_U32 ip = addr->u.ip_relay.ipAddr; + if ( 0L == ip ) { + ip = hostNameToIP( addr->u.ip_relay.hostName ); + } + return ip; +} /* addrForHost */ + +static XP_U32 +hostNameToIP( const XP_UCHAR* name ) +{ + XP_U32 ip; + struct hostent* host; + XP_LOGF( "%s: looking up %s", __func__, name ); + host = gethostbyname( name ); + if ( NULL == host ) { + XP_WARNF( "gethostbyname returned NULL\n" ); + } else { + XP_MEMCPY( &ip, host->h_addr_list[0], sizeof(ip) ); + ip = ntohl(ip); + } + XP_LOGF( "%s found %lx for %s", __func__, ip, name ); + return ip; +} diff --git a/xwords4/linux/relaycon.h b/xwords4/linux/relaycon.h new file mode 100644 index 000000000..64c547580 --- /dev/null +++ b/xwords4/linux/relaycon.h @@ -0,0 +1,36 @@ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2013 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program 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 program 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. + */ + +#ifndef _RELAYCON_H_ +#define _RELAYCON_H_ + +#include "main.h" + +typedef struct _Procs { + void (*msgReceived)( void* closure, XP_U8* buf, XP_U16 len ); + void (*devIDChanged)( void* closure, const XP_UCHAR* devID ); +} RelayConnProcs; + +void relaycon_init( LaunchParams* params, const RelayConnProcs* procs, + void* procsClosure, const char* host, int port, + const XP_UCHAR* devID, DevIDType typ ); +XP_S16 relaycon_send( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, + XP_U32 gameID, const CommsAddrRec* addrRec ); +void relaycon_cleanup( LaunchParams* params ); +#endif From c33c50ee6b986dbffb7eef35297f08d7a787dd8a Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 16 Jan 2013 21:11:35 -0800 Subject: [PATCH 019/116] add deleteGame() --- xwords4/linux/gamesdb.c | 13 +++++++++++++ xwords4/linux/gamesdb.h | 1 + 2 files changed, 14 insertions(+) diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c index c507bde76..5e6bd9fa2 100644 --- a/xwords4/linux/gamesdb.c +++ b/xwords4/linux/gamesdb.c @@ -216,6 +216,19 @@ loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ) return XP_TRUE; } +void +deleteGame( sqlite3* pDb, sqlite3_int64 rowid ) +{ + char query[256]; + snprintf( query, sizeof(query), "DELETE FROM games WHERE rowid = %lld", rowid ); + sqlite3_stmt* ppStmt; + int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( ppStmt ); + XP_ASSERT( SQLITE_DONE == result ); + sqlite3_finalize( ppStmt ); +} + void store( sqlite3* pDb, const gchar* key, const gchar* value ) { diff --git a/xwords4/linux/gamesdb.h b/xwords4/linux/gamesdb.h index 7d517a655..6418ccc3f 100644 --- a/xwords4/linux/gamesdb.h +++ b/xwords4/linux/gamesdb.h @@ -47,6 +47,7 @@ void summarize( CommonGlobals* cGlobals ); GSList* listGames( GtkAppGlobals* apg ); XP_Bool getGameInfo( GtkAppGlobals* apg, sqlite3_int64 rowid, GameInfo* gib ); XP_Bool loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ); +void deleteGame( sqlite3* pDb, sqlite3_int64 rowid ); #define KEY_RDEVID "RDEVID" From 2f772c56b28d00f2d81b7e93361fe1eb351a6617 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 16 Jan 2013 21:16:07 -0800 Subject: [PATCH 020/116] make gtk app a better test bed: add db and row to window titles, implement delete button and multiple selections, etc. --- xwords4/linux/gtkboard.c | 9 ++- xwords4/linux/gtkmain.c | 120 ++++++++++++++++++++++++++++++--------- xwords4/linux/main.h | 6 +- xwords4/linux/relaycon.c | 2 +- 4 files changed, 104 insertions(+), 33 deletions(-) diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 6f9f0b119..e6c080af5 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -534,9 +534,10 @@ createOrLoadObjects( GtkGameGlobals* globals ) } } - if ( !params->fileName ) { + if ( !params->fileName && !!params->dbName ) { XP_UCHAR buf[64]; - snprintf( buf, sizeof(buf), "clientToken: %lld", cGlobals->selRow ); + snprintf( buf, sizeof(buf), "%s / %lld", params->dbName, + cGlobals->selRow ); gtk_window_set_title( GTK_WINDOW(globals->window), buf ); } @@ -703,7 +704,9 @@ destroy_window( GtkWidget* XP_UNUSED(widget), gpointer data ) { LOG_FUNC(); GtkGameGlobals* globals = (GtkGameGlobals*)data; - comms_stop( globals->cGlobals.game.comms ); + if ( !!globals->cGlobals.game.comms ) { + comms_stop( globals->cGlobals.game.comms ); + } saveGame( &globals->cGlobals ); windowDestroyed( globals ); // gtk_main_quit(); diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index 2512e826c..9d6e69f8c 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -29,6 +29,7 @@ static void onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid, XP_Bool isNew ); +static void updateButtons( GtkAppGlobals* apg ); static void recordOpened( GtkAppGlobals* apg, GtkGameGlobals* globals ) @@ -71,19 +72,46 @@ findGame( const GtkAppGlobals* apg, XP_U32 clientToken ) return result; } -enum { CHECK_ITEM, ROW_ITEM, NAME_ITEM, ROOM_ITEM, OVER_ITEM, TURN_ITEM, NMOVES_ITEM, MISSING_ITEM, - N_ITEMS }; +enum { ROW_ITEM, NAME_ITEM, ROOM_ITEM, OVER_ITEM, TURN_ITEM, NMOVES_ITEM, + MISSING_ITEM, N_ITEMS }; + +static void +foreachProc( GtkTreeModel* model, GtkTreePath* XP_UNUSED(path), + GtkTreeIter* iter, gpointer data ) +{ + GtkAppGlobals* apg = (GtkAppGlobals*)data; + sqlite3_int64 rowid; + gtk_tree_model_get( model, iter, ROW_ITEM, &rowid, -1 ); + apg->selRows = g_array_append_val( apg->selRows, rowid ); +} static void tree_selection_changed_cb( GtkTreeSelection* selection, gpointer data ) { GtkAppGlobals* apg = (GtkAppGlobals*)data; - GtkTreeIter iter; - GtkTreeModel *model; + apg->selRows = g_array_set_size( apg->selRows, 0 ); + gtk_tree_selection_selected_foreach( selection, foreachProc, apg ); - if ( gtk_tree_selection_get_selected( selection, &model, &iter ) ) { - gtk_tree_model_get( model, &iter, ROW_ITEM, &apg->selRow, -1 ); + updateButtons( apg ); +} + +static void +removeRow( GtkAppGlobals* apg, sqlite3_int64 rowid ) +{ + GtkTreeModel* model = + gtk_tree_view_get_model(GTK_TREE_VIEW(apg->listWidget)); + GtkTreeIter iter; + gboolean valid; + for ( valid = gtk_tree_model_get_iter_first( model, &iter ); + valid; + valid = gtk_tree_model_iter_next( model, &iter ) ) { + sqlite3_int64 tmpid; + gtk_tree_model_get( model, &iter, ROW_ITEM, &tmpid, -1 ); + if ( tmpid == rowid ) { + gtk_list_store_remove( GTK_LIST_STORE(model), &iter ); + break; + } } } @@ -101,14 +129,6 @@ static GtkWidget* init_games_list( GtkAppGlobals* apg ) { GtkWidget* list = gtk_tree_view_new(); - GtkCellRenderer* renderer; - GtkTreeViewColumn* column; - - renderer = gtk_cell_renderer_toggle_new(); - column = - gtk_tree_view_column_new_with_attributes( "", renderer, "active", - CHECK_ITEM, NULL ); - gtk_tree_view_append_column( GTK_TREE_VIEW(list), column ); addTextColumn( list, "Row", ROW_ITEM ); addTextColumn( list, "Name", NAME_ITEM ); @@ -118,7 +138,7 @@ init_games_list( GtkAppGlobals* apg ) addTextColumn( list, "NMoves", NMOVES_ITEM ); addTextColumn( list, "NMissing", MISSING_ITEM ); - GtkListStore* store = gtk_list_store_new( N_ITEMS, G_TYPE_BOOLEAN, + GtkListStore* store = gtk_list_store_new( N_ITEMS, G_TYPE_INT64, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INT, G_TYPE_INT, @@ -128,9 +148,9 @@ init_games_list( GtkAppGlobals* apg ) GtkTreeSelection* select = gtk_tree_view_get_selection( GTK_TREE_VIEW (list) ); - gtk_tree_selection_set_mode( select, GTK_SELECTION_SINGLE ); + gtk_tree_selection_set_mode( select, GTK_SELECTION_MULTIPLE ); g_signal_connect( G_OBJECT(select), "changed", - G_CALLBACK (tree_selection_changed_cb), apg ); + G_CALLBACK(tree_selection_changed_cb), apg ); return list; } @@ -167,6 +187,14 @@ add_to_list( GtkWidget* list, sqlite3_int64 rowid, XP_Bool isNew, XP_LOGF( "DONE adding" ); } +static void updateButtons( GtkAppGlobals* apg ) +{ + guint count = apg->selRows->len; + + gtk_widget_set_sensitive( apg->openButton, 1 == count ); + gtk_widget_set_sensitive( apg->deleteButton, 1 <= count ); +} + static void handle_newgame_button( GtkWidget* XP_UNUSED(widget), void* closure ) { @@ -190,22 +218,31 @@ static void handle_open_button( GtkWidget* XP_UNUSED(widget), void* closure ) { GtkAppGlobals* apg = (GtkAppGlobals*)closure; - if ( -1 != apg->selRow && !gameIsOpen( apg, apg->selRow ) ) { + sqlite3_int64 selRow = getSelRow( apg ); + if ( -1 != selRow && !gameIsOpen( apg, selRow ) ) { apg->params->needsNewGame = XP_FALSE; GtkGameGlobals* globals = malloc( sizeof(*globals) ); initGlobals( globals, apg->params ); globals->cGlobals.pDb = apg->pDb; - globals->cGlobals.selRow = apg->selRow; + globals->cGlobals.selRow = selRow; recordOpened( apg, globals ); gtk_widget_show( globals->window ); } } static void -handle_quit_button( GtkWidget* XP_UNUSED(widget), gpointer XP_UNUSED(data) ) +handle_delete_button( GtkWidget* XP_UNUSED(widget), void* closure ) { - // GtkAppGlobals* apg = (GtkAppGlobals*)data; - gtk_main_quit(); + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + guint len = apg->selRows->len; + for ( guint ii = 0; ii < len; ++ii ) { + sqlite3_int64 rowid = g_array_index( apg->selRows, sqlite3_int64, ii ); + removeRow( apg, rowid ); + deleteGame( apg->pDb, rowid ); + } + apg->selRows = g_array_set_size( apg->selRows, 0 ); + updateButtons( apg ); + /* Need now to update the selection and sync the buttons */ } static void @@ -223,6 +260,13 @@ handle_destroy( GtkWidget* XP_UNUSED(widget), gpointer data ) } static void +handle_quit_button( GtkWidget* XP_UNUSED(widget), gpointer data ) +{ + GtkAppGlobals* apg = (GtkAppGlobals*)data; + handle_destroy( NULL, apg ); +} + +static GtkWidget* addButton( gchar* label, GtkWidget* parent, GCallback proc, void* closure ) { GtkWidget* button = gtk_button_new_with_label( label ); @@ -230,6 +274,7 @@ addButton( gchar* label, GtkWidget* parent, GCallback proc, void* closure ) g_signal_connect( GTK_OBJECT(button), "clicked", G_CALLBACK(proc), closure ); gtk_widget_show( button ); + return button; } static GtkWidget* @@ -240,6 +285,10 @@ makeGamesWindow( GtkAppGlobals* apg ) window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); g_signal_connect( G_OBJECT(window), "destroy", G_CALLBACK(handle_destroy), apg ); + + if ( !!apg->params->dbName ) { + gtk_window_set_title( GTK_WINDOW(window), apg->params->dbName ); + } GtkWidget* vbox = gtk_vbox_new( FALSE, 0 ); gtk_container_add( GTK_CONTAINER(window), vbox ); @@ -261,9 +310,13 @@ makeGamesWindow( GtkAppGlobals* apg ) gtk_widget_show( hbox ); gtk_container_add( GTK_CONTAINER(vbox), hbox ); - addButton( "New game", hbox, G_CALLBACK(handle_newgame_button), apg ); - addButton( "Open", hbox, G_CALLBACK(handle_open_button), apg ); - addButton( "Quit", hbox, G_CALLBACK(handle_quit_button), apg ); + (void)addButton( "New game", hbox, G_CALLBACK(handle_newgame_button), apg ); + apg->openButton = addButton( "Open", hbox, + G_CALLBACK(handle_open_button), apg ); + apg->deleteButton = addButton( "Delete", hbox, + G_CALLBACK(handle_delete_button), apg ); + (void)addButton( "Quit", hbox, G_CALLBACK(handle_quit_button), apg ); + updateButtons( apg ); gtk_widget_show( window ); return window; @@ -316,8 +369,8 @@ app_socket_proc( GIOChannel* source, GIOCondition condition, gpointer data ) } static void -socketChanged( void* closure, int newSock, int XP_UNUSED(oldSock), SockReceiver proc, - void* procClosure ) +socketChanged( void* closure, int newSock, int XP_UNUSED(oldSock), + SockReceiver proc, void* procClosure ) { GtkAppGlobals* apg = (GtkAppGlobals*)closure; SourceData* sd = g_malloc( sizeof(*sd) ); @@ -365,11 +418,22 @@ onGameSaved( void* closure, sqlite3_int64 rowid, onNewData( apg, rowid, firstTime ); } +sqlite3_int64 +getSelRow( const GtkAppGlobals* apg ) +{ + sqlite3_int64 result = -1; + guint len = apg->selRows->len; + if ( 1 == len ) { + result = g_array_index( apg->selRows, sqlite3_int64, 0 ); + } + return result; +} + int gtkmain( LaunchParams* params ) { GtkAppGlobals apg = {0}; - apg.selRow = -1; + apg.selRows = g_array_new( FALSE, FALSE, sizeof( sqlite3_int64 ) ); apg.params = params; apg.pDb = openGamesDB( params->dbName ); params->socketChanged = socketChanged; diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index 4a4832419..942cf737c 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -234,11 +234,15 @@ typedef struct _SourceData { typedef struct _GtkAppGlobals { sqlite3* pDb; - sqlite3_int64 selRow; + GArray* selRows; LaunchParams* params; GSList* globalsList; GList* sources; GtkWidget* listWidget; + GtkWidget* openButton; + GtkWidget* deleteButton; } GtkAppGlobals; +sqlite3_int64 getSelRow( const GtkAppGlobals* apg ); + #endif diff --git a/xwords4/linux/relaycon.c b/xwords4/linux/relaycon.c index 5893f9047..f397e65a7 100644 --- a/xwords4/linux/relaycon.c +++ b/xwords4/linux/relaycon.c @@ -46,7 +46,7 @@ relaycon_init( LaunchParams* params, const RelayConnProcs* procs, storage->procsClosure = procsClosure; storage->socket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); - (*params->socketChanged)( params, storage->socket, -1, + (*params->socketChanged)( params->socketChangedClosure, storage->socket, -1, relaycon_receive, params ); XP_ASSERT( !!devID ); From 4163309dae1454e55317caf94a47eea15b4fb5e4 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 17 Jan 2013 06:37:53 -0800 Subject: [PATCH 021/116] Let's have just one set of signal handlers --- xwords4/linux/gtkboard.c | 13 ------------- xwords4/linux/gtkmain.c | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index e6c080af5..9e872af4e 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -2349,14 +2349,6 @@ drop_msg_toggle( GtkWidget* toggle, GtkGameGlobals* globals ) } /* drop_msg_toggle */ #endif -static GtkGameGlobals* g_globals_for_signal; -static void -handle_sigintterm( int XP_UNUSED(sig) ) -{ - LOG_FUNC(); - gtk_main_quit(); -} - /* int */ /* board_main( LaunchParams* params ) */ /* { */ @@ -2391,11 +2383,6 @@ initGlobals( GtkGameGlobals* globals, LaunchParams* params ) GtkWidget* dropCheck; #endif - g_globals_for_signal = globals; - struct sigaction act = { .sa_handler = handle_sigintterm }; - sigaction( SIGINT, &act, NULL ); - sigaction( SIGTERM, &act, NULL ); - memset( globals, 0, sizeof(*globals) ); globals->cGlobals.params = params; diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index 9d6e69f8c..6a5f21ac1 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -429,10 +429,24 @@ getSelRow( const GtkAppGlobals* apg ) return result; } +static GtkAppGlobals* g_globals_for_signal; +static void +handle_sigintterm( int XP_UNUSED(sig) ) +{ + LOG_FUNC(); + handle_destroy( NULL, g_globals_for_signal ); +} + int gtkmain( LaunchParams* params ) { GtkAppGlobals apg = {0}; + + g_globals_for_signal = &apg; + struct sigaction act = { .sa_handler = handle_sigintterm }; + sigaction( SIGINT, &act, NULL ); + sigaction( SIGTERM, &act, NULL ); + apg.selRows = g_array_new( FALSE, FALSE, sizeof( sqlite3_int64 ) ); apg.params = params; apg.pDb = openGamesDB( params->dbName ); From 932778f7009c2e237e9d4dbdbc91e73c8f39d42d Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 18 Jan 2013 06:57:57 -0800 Subject: [PATCH 022/116] stubbed-out thread manager for udp connection --- xwords4/relay/Makefile | 1 + xwords4/relay/udpqueue.cpp | 43 ++++++++++++++++++++++++++ xwords4/relay/udpqueue.h | 62 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 xwords4/relay/udpqueue.cpp create mode 100644 xwords4/relay/udpqueue.h diff --git a/xwords4/relay/Makefile b/xwords4/relay/Makefile index 1c4d12d37..2421f746c 100644 --- a/xwords4/relay/Makefile +++ b/xwords4/relay/Makefile @@ -32,6 +32,7 @@ SRC = \ cidlock.cpp \ addrinfo.cpp \ devmgr.cpp \ + udpqueue.cpp \ xwrelay.cpp \ # STATIC ?= -static diff --git a/xwords4/relay/udpqueue.cpp b/xwords4/relay/udpqueue.cpp new file mode 100644 index 000000000..9364f0e7b --- /dev/null +++ b/xwords4/relay/udpqueue.cpp @@ -0,0 +1,43 @@ +/* -*- compile-command: "make -k -j3"; -*- */ + +/* + * Copyright 2010-2012 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program 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 program 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. + */ + +#include "udpqueue.h" + +static UdpQueue* s_instance = NULL; + + +UdpQueue* +UdpQueue::get() +{ + if ( s_instance == NULL ) { + s_instance = new UdpQueue(); + } + return s_instance; +} + +void +UdpQueue::handle( const AddrInfo::AddrUnion* saddr, unsigned char* buf, int len, + QueueCallback cb ) +{ + logf( XW_LOGINFO, "%s: still running in same thread!!!", __func__ ); + UdpThreadClosure closure( saddr, buf, len ); + (*cb)( &closure ); +} diff --git a/xwords4/relay/udpqueue.h b/xwords4/relay/udpqueue.h new file mode 100644 index 000000000..3638018c1 --- /dev/null +++ b/xwords4/relay/udpqueue.h @@ -0,0 +1,62 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2013 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program 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. + * + * This program 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. + */ + + +#ifndef _UDPQUEUE_H_ +#define _UDPQUEUE_H_ + +#include + +#include "xwrelay_priv.h" +#include "addrinfo.h" + +using namespace std; + +class UdpThreadClosure { +public: + UdpThreadClosure( const AddrInfo::AddrUnion* saddr, unsigned char* buf, int len ) { + m_saddr = *saddr; + m_buf = new unsigned char[len]; + memcpy( m_buf, buf, len ); + m_len = len; + } + ~UdpThreadClosure() { delete m_buf; } + + const unsigned char* buf() const { return m_buf; } + int len() const { return m_len; } + const AddrInfo::AddrUnion* saddr() const { return &m_saddr; } + + private: + unsigned char* m_buf; + int m_len; + AddrInfo::AddrUnion m_saddr; +}; + +typedef void (*QueueCallback)( UdpThreadClosure* closure ); + +class UdpQueue { + public: + static UdpQueue* get(); + void handle( const AddrInfo::AddrUnion* saddr, unsigned char* buf, int len, + QueueCallback cb ); + + private: +}; + +#endif From 1c5fef6a38adf9edd8ded5346b2bb63134c5a509 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 18 Jan 2013 07:10:47 -0800 Subject: [PATCH 023/116] relay improvements for UDP connection: record addresses, prepare to run in separate thread, etc. --- xwords4/relay/addrinfo.cpp | 2 +- xwords4/relay/addrinfo.h | 13 ++- xwords4/relay/cref.cpp | 72 ++++++++++----- xwords4/relay/cref.h | 13 +-- xwords4/relay/crefmgr.cpp | 4 +- xwords4/relay/crefmgr.h | 4 +- xwords4/relay/dbmgr.cpp | 26 ++++++ xwords4/relay/dbmgr.h | 4 + xwords4/relay/devmgr.cpp | 16 ++-- xwords4/relay/devmgr.h | 3 + xwords4/relay/tpool.h | 2 +- xwords4/relay/xwrelay.cpp | 164 +++++++++++++++++++---------------- xwords4/relay/xwrelay_priv.h | 2 +- 13 files changed, 205 insertions(+), 120 deletions(-) diff --git a/xwords4/relay/addrinfo.cpp b/xwords4/relay/addrinfo.cpp index d92367719..e61f3595d 100644 --- a/xwords4/relay/addrinfo.cpp +++ b/xwords4/relay/addrinfo.cpp @@ -32,7 +32,7 @@ AddrInfo::equals( const AddrInfo& other ) const if ( isTCP() ) { equal = m_socket == other.m_socket; } else { - assert( m_socket == other.m_socket ); /* both same UDP socket */ + // assert( m_socket == other.m_socket ); /* both same UDP socket */ /* what does equal mean on udp addresses? Same host, or same host AND game */ equal = m_clientToken == other.m_clientToken && 0 == memcmp( &m_saddr, &other.m_saddr, sizeof(m_saddr) ); diff --git a/xwords4/relay/addrinfo.h b/xwords4/relay/addrinfo.h index 0432b40c9..84145397e 100644 --- a/xwords4/relay/addrinfo.h +++ b/xwords4/relay/addrinfo.h @@ -28,6 +28,8 @@ class AddrInfo { public: + typedef uint32_t ClientToken; + typedef union _AddrUnion { struct sockaddr addr; struct sockaddr_in addr_in; @@ -43,7 +45,11 @@ class AddrInfo { construct( socket, saddr, true ); } - AddrInfo( int socket, uint32_t clientToken, const AddrUnion* saddr ) { + AddrInfo( int socket, ClientToken clientToken, const AddrUnion* saddr ) { + init( socket, clientToken, saddr ); + } + + void init( int socket, ClientToken clientToken, const AddrUnion* saddr ) { construct( socket, saddr, false ); m_clientToken = clientToken; } @@ -51,9 +57,10 @@ class AddrInfo { void setIsTCP( bool val ) { m_isTCP = val; } bool isTCP() const { return m_isTCP; } /* later UDP will be here too */ int socket() const { assert(m_isValid); return m_socket; } - uint32_t clientToken() const { assert(m_isValid); return m_clientToken; } + ClientToken clientToken() const { assert(m_isValid); return m_clientToken; } struct in_addr sin_addr() const { return m_saddr.addr_in.sin_addr; } const struct sockaddr* sockaddr() const { assert(m_isValid); return &m_saddr.addr; } + const AddrUnion* saddr() const { assert(m_isValid); return &m_saddr; } bool equals( const AddrInfo& other ) const; @@ -71,7 +78,7 @@ class AddrInfo { int m_socket; bool m_isTCP; bool m_isValid; - uint32_t m_clientToken; /* must be 32 bit */ + ClientToken m_clientToken; /* must be 32 bit */ AddrUnion m_saddr; }; diff --git a/xwords4/relay/cref.cpp b/xwords4/relay/cref.cpp index bb44126ee..bd7064182 100644 --- a/xwords4/relay/cref.cpp +++ b/xwords4/relay/cref.cpp @@ -40,6 +40,7 @@ #include "timermgr.h" #include "configs.h" #include "crefmgr.h" +#include "devmgr.h" #include "permid.h" using namespace std; @@ -261,7 +262,7 @@ CookieRef::_HandleAck( HostID hostID ) void CookieRef::_PutMsg( HostID srcID, const AddrInfo* addr, HostID destID, - unsigned char* buf, int buflen ) + const unsigned char* buf, int buflen ) { CRefEvent evt( XWE_PROXYMSG, addr ); evt.u.fwd.src = srcID; @@ -308,14 +309,14 @@ CookieRef::_Shutdown() HostID CookieRef::HostForSocket( const AddrInfo* addr ) { - HostID hid = -1; + HostID hid = HOST_ID_NONE; ASSERT_LOCKED(); RWReadLock rrl( &m_socketsRWLock ); vector::const_iterator iter; for ( iter = m_sockets.begin(); iter != m_sockets.end(); ++iter ) { if ( iter->m_addr.equals( *addr ) ) { hid = iter->m_hostID; - logf( XW_LOGINFO, "%s: assigning hid of %d", __func__, hid ); + assert( HOST_ID_NONE != hid ); break; } } @@ -404,7 +405,7 @@ CookieRef::notifyDisconn( const CRefEvent* evt ) evt->u.disnote.why }; - send_with_length( &evt->addr, buf, sizeof(buf), true ); + send_with_length( &evt->addr, HOST_ID_NONE, buf, sizeof(buf), true ); } /* notifyDisconn */ void @@ -522,7 +523,7 @@ CookieRef::_CheckHeartbeats( time_t now ) void CookieRef::_Forward( HostID src, const AddrInfo* addr, - HostID dest, unsigned char* buf, int buflen ) + HostID dest, const unsigned char* buf, int buflen ) { pushForwardEvent( src, addr, dest, buf, buflen ); handleEvents(); @@ -587,7 +588,7 @@ CookieRef::pushHeartFailedEvent( int socket ) void CookieRef::pushForwardEvent( HostID src, const AddrInfo* addr, HostID dest, - unsigned char* buf, int buflen ) + const unsigned char* buf, int buflen ) { logf( XW_LOGVERBOSE1, "pushForwardEvent: %d -> %d", src, dest ); CRefEvent evt( XWE_FORWARDMSG, addr ); @@ -807,12 +808,19 @@ CookieRef::handleEvents() } /* handleEvents */ bool -CookieRef::send_with_length( const AddrInfo* addr, - unsigned char* buf, int bufLen, bool cascade ) +CookieRef::send_with_length( const AddrInfo* addr, HostID dest, + const unsigned char* buf, int bufLen, bool cascade ) { bool failed = false; if ( send_with_length_unsafe( addr, buf, bufLen ) ) { - DBMgr::Get()->RecordSent( ConnName(), HostForSocket(addr), bufLen ); + if ( HOST_ID_NONE == dest ) { + dest = HostForSocket(addr); + } + if ( HOST_ID_NONE != dest ) { + DBMgr::Get()->RecordSent( ConnName(), dest, bufLen ); + } else { + logf( XW_LOGERROR, "%s: no hid for addr", __func__ ); + } } else { failed = true; } @@ -857,7 +865,7 @@ CookieRef::send_stored_messages( HostID dest, const AddrInfo* addr ) buf, &buflen, &msgID ) ) { break; } - if ( ! send_with_length( addr, buf, buflen, true ) ) { + if ( ! send_with_length( addr, dest, buf, buflen, true ) ) { break; } DBMgr::Get()->RemoveStoredMessages( &msgID, 1 ); @@ -908,6 +916,8 @@ CookieRef::increasePlayerCounts( CRefEvent* evt, bool reconn, HostID* hidp, evt->u.con.clientVersion, nPlayersH, seed, &evt->addr, devID, reconn ); + DevMgr::Get()->Remember( devID, &evt->addr ); + HostID hostid = evt->u.con.srcID; if ( NULL != hidp ) { *hidp = hostid; @@ -915,8 +925,7 @@ CookieRef::increasePlayerCounts( CRefEvent* evt, bool reconn, HostID* hidp, /* first add the rec here, whether it'll get ack'd or not */ logf( XW_LOGINFO, "%s: remembering pair: hostid=%x, " - "socket=%d (size=%d)", - __func__, hostid, socket, m_sockets.size()); + "(size=%d)", __func__, hostid, m_sockets.size()); assert( m_sockets.size() < 4 ); @@ -1104,7 +1113,7 @@ CookieRef::sendResponse( const CRefEvent* evt, bool initial, } } - send_with_length( &evt->addr, buf, bufp - buf, true ); + send_with_length( &evt->addr, evt->u.con.srcID, buf, bufp - buf, true ); logf( XW_LOGVERBOSE0, "sent %s", cmdToStr( XWRELAY_Cmd(buf[0]) ) ); } /* sendResponse */ @@ -1120,11 +1129,13 @@ CookieRef::sendAnyStored( const CRefEvent* evt ) void CookieRef::forward_or_store( const CRefEvent* evt ) { - unsigned char* buf = evt->u.fwd.buf; + AddrInfo addr; // invalid unless assigned to + const unsigned char* cbuf = evt->u.fwd.buf; do { - /* This is an ugly hack!!!! */ + int buflen = evt->u.fwd.buflen; + unsigned char buf[buflen]; if ( *buf == XWRELAY_MSG_TORELAY ) { - *buf = XWRELAY_MSG_FROMRELAY; + buf[0] = XWRELAY_MSG_FROMRELAY; } else if ( *buf == XWRELAY_MSG_TORELAY_NOCONN ) { *buf = XWRELAY_MSG_FROMRELAY_NOCONN; } else { @@ -1133,7 +1144,8 @@ CookieRef::forward_or_store( const CRefEvent* evt ) break; } - int buflen = evt->u.fwd.buflen; + memcpy( &buf[1], &cbuf[1], buflen-1 ); + HostID dest = evt->u.fwd.dest; const AddrInfo* destAddr = SocketForHost( dest ); @@ -1141,8 +1153,22 @@ CookieRef::forward_or_store( const CRefEvent* evt ) usleep( m_delayMicros ); } + // If recipient GAME isn't connected, see if owner device is and can + // receive + if ( NULL == destAddr) { + DevIDRelay devid; + AddrInfo::ClientToken token; + if ( DBMgr::Get()->TokenFor( ConnName(), dest, &devid, &token ) ) { + const AddrInfo::AddrUnion* saddr = DevMgr::Get()->get( devid ); + if ( !!saddr ) { + addr.init( -1, token, saddr ); + destAddr = &addr; + } + } + } + if ( (NULL == destAddr) - || !send_with_length( destAddr, buf, buflen, true ) ) { + || !send_with_length( destAddr, dest, buf, buflen, true ) ) { store_message( dest, buf, buflen ); } @@ -1162,7 +1188,7 @@ CookieRef::send_denied( const CRefEvent* evt, XWREASON why ) } void -CookieRef::send_msg( const AddrInfo* addr, HostID id, +CookieRef::send_msg( const AddrInfo* addr, HostID hid, XWRelayMsg msg, XWREASON why, bool cascade ) { unsigned char buf[10]; @@ -1173,7 +1199,7 @@ CookieRef::send_msg( const AddrInfo* addr, HostID id, switch ( msg ) { case XWRELAY_DISCONNECT_OTHER: buf[len++] = why; - tmp = htons( id ); + tmp = htons( hid ); memcpy( &buf[len], &tmp, 2 ); len += 2; break; @@ -1183,7 +1209,7 @@ CookieRef::send_msg( const AddrInfo* addr, HostID id, } assert( len <= sizeof(buf) ); - send_with_length( addr, buf, len, cascade ); + send_with_length( addr, HOST_ID_NONE, buf, len, cascade ); } /* send_msg */ void @@ -1210,7 +1236,7 @@ CookieRef::notifyGameDead( const AddrInfo* addr ) ,XWRELAY_ERROR_DELETED }; - send_with_length( addr, buf, sizeof(buf), true ); + send_with_length( addr, HOST_ID_NONE, buf, sizeof(buf), true ); } /* void */ @@ -1263,7 +1289,7 @@ CookieRef::sendAllHere( bool initial ) vector::const_iterator iter; for ( iter = m_sockets.begin(); iter != m_sockets.end(); ++iter ) { if ( iter->m_hostID == dest ) { - sent = send_with_length( &iter->m_addr, buf, bufp-buf, true ); + sent = send_with_length( &iter->m_addr, dest, buf, bufp-buf, true ); break; } } diff --git a/xwords4/relay/cref.h b/xwords4/relay/cref.h index 254c33a89..45cd35863 100644 --- a/xwords4/relay/cref.h +++ b/xwords4/relay/cref.h @@ -33,6 +33,7 @@ #include "devid.h" #include "dbmgr.h" #include "states.h" +#include "addrinfo.h" typedef vector MsgBuffer; typedef deque MsgBufQueue; @@ -131,14 +132,14 @@ class CookieRef { int seed, const AddrInfo* addr, bool gameDead ); void _HandleAck( HostID hostID ); void _PutMsg( HostID srcID, const AddrInfo* addr, HostID destID, - unsigned char* buf, int buflen ); + const unsigned char* buf, int buflen ); void _Disconnect( const AddrInfo* addr, HostID hostID ); void _DeviceGone( HostID hostID, int seed ); void _Shutdown(); void _HandleHeartbeat( HostID id, const AddrInfo* addr ); void _CheckHeartbeats( time_t now ); void _Forward( HostID src, const AddrInfo* addr, HostID dest, - unsigned char* buf, int buflen ); + const unsigned char* buf, int buflen ); void _Remove( const AddrInfo* addr ); void _CheckAllConnected(); void _CheckNotAcked( HostID hid ); @@ -159,7 +160,7 @@ class CookieRef { struct { HostID src; HostID dest; - unsigned char* buf; + const unsigned char* buf; int buflen; } fwd; struct { @@ -194,8 +195,8 @@ class CookieRef { } u; }; - bool send_with_length( const AddrInfo* addr, - unsigned char* buf, int bufLen, bool cascade ); + bool send_with_length( const AddrInfo* addr, HostID hid, + const unsigned char* buf, int bufLen, bool cascade ); void send_msg( const AddrInfo* addr, HostID id, XWRelayMsg msg, XWREASON why, bool cascade ); void pushConnectEvent( int clientVersion, DevID* devID, @@ -208,7 +209,7 @@ class CookieRef { void pushHeartFailedEvent( const AddrInfo* addr ); void pushForwardEvent( HostID src, const AddrInfo* addr, - HostID dest, unsigned char* buf, int buflen ); + HostID dest, const unsigned char* buf, int buflen ); void pushDestBadEvent(); void pushLastSocketGoneEvent(); void pushGameDead( const AddrInfo* addr ); diff --git a/xwords4/relay/crefmgr.cpp b/xwords4/relay/crefmgr.cpp index 01c0fdc81..290475b9b 100644 --- a/xwords4/relay/crefmgr.cpp +++ b/xwords4/relay/crefmgr.cpp @@ -405,11 +405,11 @@ CidInfo* CRefMgr::getCookieRef( CookieID cid, bool failOk ) { CidInfo* cinfo = NULL; - for ( ; ; ) { + for ( int count = 0; ; ++count ) { cinfo = m_cidlock->Claim( cid ); if ( NULL != cinfo->GetRef() ) { break; - } else if ( failOk ) { + } else if ( failOk || count > 20 ) { break; } m_cidlock->Relinquish( cinfo, true ); diff --git a/xwords4/relay/crefmgr.h b/xwords4/relay/crefmgr.h index a4020e6dd..7f657c2ab 100644 --- a/xwords4/relay/crefmgr.h +++ b/xwords4/relay/crefmgr.h @@ -186,7 +186,7 @@ class SafeCref { ~SafeCref(); bool Forward( HostID src, const AddrInfo* addr, HostID dest, - unsigned char* buf, int buflen ) { + const unsigned char* buf, int buflen ) { if ( IsValid() ) { CookieRef* cref = m_cinfo->GetRef(); assert( 0 != cref->GetCid() ); @@ -198,7 +198,7 @@ class SafeCref { } void PutMsg( HostID srcID, const AddrInfo* addr, HostID destID, - unsigned char* buf, int buflen ) { + const unsigned char* buf, int buflen ) { if ( IsValid() ) { CookieRef* cref = m_cinfo->GetRef(); assert( 0 != cref->GetCid() ); diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index 8e22035ae..a94fd3a2e 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -577,6 +577,32 @@ DBMgr::PublicRooms( int lang, int nPlayers, int* nNames, string& names ) *nNames = nTuples; } +bool +DBMgr::TokenFor( const char* const connName, int hid, DevIDRelay* devid, + AddrInfo::ClientToken* token ) +{ + bool found = false; + const char* fmt = "SELECT tokens[%d], devids[%d] FROM " GAMES_TABLE + " WHERE connName='%s'"; + string query; + string_printf( query, fmt, hid, hid, connName ); + PGresult* result = PQexec( getThreadConn(), query.c_str() ); + if ( 1 == PQntuples( result ) ) { + AddrInfo::ClientToken token_tmp = atoi( PQgetvalue( result, 0, 0 ) ); + DevIDRelay devid_tmp = atoi( PQgetvalue( result, 0, 1 ) ); + if ( 0 != token_tmp // 0 is illegal (legacy/unset) value + && 0 != devid_tmp ) { + *token = token_tmp; + *devid = devid_tmp; + found = true; + } + } + PQclear( result ); + logf( XW_LOGINFO, "%s(%s,%d)=>%s (%d, %d)", __func__, connName, hid, + (found?"true":"false"), *devid, *token ); + return found; +} + int DBMgr::PendingMsgCount( const char* connName, int hid ) { diff --git a/xwords4/relay/dbmgr.h b/xwords4/relay/dbmgr.h index 49ab1066a..dbf1eb02d 100644 --- a/xwords4/relay/dbmgr.h +++ b/xwords4/relay/dbmgr.h @@ -88,6 +88,10 @@ class DBMgr { queries.*/ void PublicRooms( int lang, int nPlayers, int* nNames, string& names ); + /* Get stored address info, if available and valid */ + bool TokenFor( const char* const connName, int hid, DevIDRelay* devid, + AddrInfo::ClientToken* token ); + /* Return number of messages pending for connName:hostid pair passed in */ int PendingMsgCount( const char* const connName, int hid ); diff --git a/xwords4/relay/devmgr.cpp b/xwords4/relay/devmgr.cpp index f76a02233..2069ede63 100644 --- a/xwords4/relay/devmgr.cpp +++ b/xwords4/relay/devmgr.cpp @@ -26,7 +26,7 @@ static DevMgr* s_instance = NULL; /* static */ DevMgr* DevMgr::Get() { - if ( s_instance == NULL ) { + if ( NULL == s_instance ) { s_instance = new DevMgr(); } return s_instance; @@ -35,27 +35,33 @@ DevMgr::Get() void DevMgr::Remember( DevIDRelay devid, const AddrInfo::AddrUnion* saddr ) { + logf( XW_LOGINFO, "%s(devid=%d)", __func__, devid ); time_t now = time( NULL ); - MutexLock ml( &m_mapLock ); UDPAddrRec rec( saddr, now ); + MutexLock ml( &m_mapLock ); m_devAddrMap.insert( pair( devid, rec ) ); logf( XW_LOGINFO, "dev->addr map now contains %d entries", m_devAddrMap.size() ); } -#if 0 // not used yet +void +DevMgr::Remember( DevIDRelay devid, const AddrInfo* addr ) +{ + Remember( devid, addr->saddr() ); +} + const AddrInfo::AddrUnion* DevMgr::get( DevIDRelay devid ) { const AddrInfo::AddrUnion* result = NULL; MutexLock ml( &m_mapLock ); - map::iterator iter; + map::const_iterator iter; iter = m_devAddrMap.find( devid ); if ( m_devAddrMap.end() != iter ) { result = &iter->second.m_addr; logf( XW_LOGINFO, "%s: found addr for %.8x; is %d seconds old", __func__, devid, time(NULL) - iter->second.m_added ); } + logf( XW_LOGINFO, "%s(devid=%d)=>%p", __func__, devid, result ); return result; } -#endif diff --git a/xwords4/relay/devmgr.h b/xwords4/relay/devmgr.h index 799d6d4f7..7f843bd8b 100644 --- a/xwords4/relay/devmgr.h +++ b/xwords4/relay/devmgr.h @@ -32,11 +32,14 @@ class DevMgr { public: static DevMgr* Get(); void Remember( DevIDRelay devid, const AddrInfo::AddrUnion* saddr ); + void Remember( DevIDRelay devid, const AddrInfo* addr ); const AddrInfo::AddrUnion* get( DevIDRelay devid ); private: DevMgr() { pthread_mutex_init( &m_mapLock, NULL ); } + /* destructor's never called.... ~DevMgr() { pthread_mutex_destroy( &m_mapLock ); } + */ class UDPAddrRec { public: diff --git a/xwords4/relay/tpool.h b/xwords4/relay/tpool.h index 2c720628b..9aa38f164 100644 --- a/xwords4/relay/tpool.h +++ b/xwords4/relay/tpool.h @@ -51,7 +51,7 @@ class XWThreadPool { } ThreadInfo; static XWThreadPool* GetTPool(); - typedef bool (*packet_func)( unsigned char* buf, int bufLen, + typedef bool (*packet_func)( const unsigned char* buf, int bufLen, const AddrInfo* from ); typedef void (*kill_func)( const AddrInfo* addr ); diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index 221a7dbe7..d7ddc4b62 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -77,6 +77,7 @@ #include "dbmgr.h" #include "addrinfo.h" #include "devmgr.h" +#include "udpqueue.h" static int s_nSpawns = 0; static int g_maxsocks = -1; @@ -180,7 +181,7 @@ cmdToStr( XWRELAY_Cmd cmd ) } static bool -parseRelayID( unsigned char** const inp, const unsigned char* const end, +parseRelayID( const unsigned char** const inp, const unsigned char* const end, char* buf, int buflen, HostID* hid ) { const char* hidp = strchr( (char*)*inp, '/' ); @@ -207,7 +208,7 @@ parseRelayID( unsigned char** const inp, const unsigned char* const end, } static bool -getNetShort( unsigned char** bufpp, const unsigned char* end, +getNetShort( const unsigned char** bufpp, const unsigned char* end, unsigned short* out ) { unsigned short tmp; @@ -221,7 +222,7 @@ getNetShort( unsigned char** bufpp, const unsigned char* end, } /* getNetShort */ static bool -getNetByte( unsigned char** bufpp, const unsigned char* end, +getNetByte( const unsigned char** bufpp, const unsigned char* end, unsigned char* out ) { bool ok = *bufpp < end; @@ -233,7 +234,7 @@ getNetByte( unsigned char** bufpp, const unsigned char* end, } /* getNetByte */ static bool -getNetString( unsigned char** bufpp, const unsigned char* end, string& out ) +getNetString( const unsigned char** bufpp, const unsigned char* end, string& out ) { char* str = (char*)*bufpp; size_t len = 1 + strlen( str ); @@ -247,7 +248,7 @@ getNetString( unsigned char** bufpp, const unsigned char* end, string& out ) } static void -getDevID( unsigned char** bufpp, const unsigned char* end, +getDevID( const unsigned char** bufpp, const unsigned char* end, unsigned short flags, DevID* devID ) { if ( XWRELAY_PROTO_VERSION_CLIENTID <= flags ) { @@ -285,7 +286,7 @@ processHeartbeat( unsigned char* buf, int bufLen, int socket ) #endif static bool -readStr( unsigned char** bufp, const unsigned char* end, +readStr( const unsigned char** bufp, const unsigned char* end, char* outBuf, int bufLen ) { unsigned char clen = **bufp; @@ -300,7 +301,7 @@ readStr( unsigned char** bufp, const unsigned char* end, } /* readStr */ static XWREASON -flagsOK( unsigned char** bufp, unsigned char const* end, +flagsOK( const unsigned char** bufp, unsigned char const* end, unsigned short* clientVersion, unsigned short* flagsp ) { XWREASON err = XWRELAY_ERROR_OLDFLAGS; @@ -348,12 +349,13 @@ send_via_udp( int socket, const struct sockaddr *dest_addr, /* No mutex here. Caller better be ensuring no other thread can access this * socket. */ bool -send_with_length_unsafe( const AddrInfo* addr, unsigned char* buf, +send_with_length_unsafe( const AddrInfo* addr, const unsigned char* buf, size_t bufLen ) { assert( !!addr ); bool ok = false; int socket = addr->socket(); + if ( addr->isTCP() ) { unsigned short len = htons( bufLen ); ssize_t nSent = send( socket, &len, 2, 0 ); @@ -365,7 +367,7 @@ send_with_length_unsafe( const AddrInfo* addr, unsigned char* buf, } } } else { - uint32_t clientToken = addr->clientToken(); + AddrInfo::ClientToken clientToken = addr->clientToken(); assert( 0 != clientToken ); unsigned char tmpbuf[1 + 1 + sizeof(clientToken) + bufLen]; tmpbuf[0] = XWREG_PROTO_VERSION; @@ -374,7 +376,11 @@ send_with_length_unsafe( const AddrInfo* addr, unsigned char* buf, memcpy( &tmpbuf[2], &clientToken, sizeof(clientToken) ); memcpy( &tmpbuf[2 + sizeof(clientToken)], buf, bufLen ); const struct sockaddr* saddr = addr->sockaddr(); - send_via_udp( g_udpsock, saddr, tmpbuf, sizeof(tmpbuf) ); + assert( g_udpsock == socket || socket == -1 ); + if ( -1 == socket ) { + socket = g_udpsock; + } + send_via_udp( socket, saddr, tmpbuf, sizeof(tmpbuf) ); logf( XW_LOGINFO, "sent %d bytes on UDP socket %d", bufLen, socket ); ok = true; } @@ -399,10 +405,10 @@ send_with_length_unsafe( const AddrInfo* addr, unsigned char* buf, * game? */ static bool -processConnect( unsigned char* bufp, int bufLen, const AddrInfo* addr ) +processConnect( const unsigned char* bufp, int bufLen, const AddrInfo* addr ) { char cookie[MAX_INVITE_LEN+1]; - unsigned char* end = bufp + bufLen; + const unsigned char* end = bufp + bufLen; bool success = false; cookie[0] = '\0'; @@ -456,9 +462,9 @@ processConnect( unsigned char* bufp, int bufLen, const AddrInfo* addr ) } /* processConnect */ static bool -processReconnect( unsigned char* bufp, int bufLen, const AddrInfo* addr ) +processReconnect( const unsigned char* bufp, int bufLen, const AddrInfo* addr ) { - unsigned char* end = bufp + bufLen; + const unsigned char* end = bufp + bufLen; bool success = false; logf( XW_LOGINFO, "%s()", __func__ ); @@ -511,10 +517,10 @@ processReconnect( unsigned char* bufp, int bufLen, const AddrInfo* addr ) } /* processReconnect */ static bool -processAck( unsigned char* bufp, int bufLen, const AddrInfo* addr ) +processAck( const unsigned char* bufp, int bufLen, const AddrInfo* addr ) { bool success = false; - unsigned char* end = bufp + bufLen; + const unsigned char* end = bufp + bufLen; HostID srcID; if ( getNetByte( &bufp, end, &srcID ) ) { SafeCref scr( addr ); @@ -524,9 +530,9 @@ processAck( unsigned char* bufp, int bufLen, const AddrInfo* addr ) } static bool -processDisconnect( unsigned char* bufp, int bufLen, const AddrInfo* addr ) +processDisconnect( const unsigned char* bufp, int bufLen, const AddrInfo* addr ) { - unsigned char* end = bufp + bufLen; + const unsigned char* end = bufp + bufLen; CookieID cookieID; HostID hostID; bool success = false; @@ -577,11 +583,11 @@ GetNSpawns(void) /* forward the message. Need only change the command after looking up the * socket and it's ready to go. */ static bool -forwardMessage( unsigned char* buf, int buflen, const AddrInfo* addr ) +forwardMessage( const unsigned char* buf, int buflen, const AddrInfo* addr ) { bool success = false; - unsigned char* bufp = buf + 1; /* skip cmd */ - unsigned char* end = buf + buflen; + const unsigned char* bufp = buf + 1; /* skip cmd */ + const unsigned char* end = buf + buflen; CookieID cookieID; HostID src; HostID dest; @@ -603,7 +609,7 @@ forwardMessage( unsigned char* buf, int buflen, const AddrInfo* addr ) } /* forwardMessage */ static bool -processMessage( unsigned char* buf, int bufLen, const AddrInfo* addr ) +processMessage( const unsigned char* buf, int bufLen, const AddrInfo* addr ) { bool success = false; /* default is failure */ XWRELAY_Cmd cmd = *buf; @@ -839,7 +845,7 @@ pushMsgs( vector& out, DBMgr* dbmgr, const char* connName, static void handleMsgsMsg( const AddrInfo* addr, bool sendFull, - unsigned char* bufp, const unsigned char* end ) + const unsigned char* bufp, const unsigned char* end ) { unsigned short nameCount; int ii; @@ -939,8 +945,8 @@ log_hex( const unsigned char* memp, int len, const char* tag ) } // log_hex static void -handleProxyMsgs( int sock, const AddrInfo* addr, unsigned char* bufp, - unsigned char* end ) +handleProxyMsgs( int sock, const AddrInfo* addr, const unsigned char* bufp, + const unsigned char* end ) { // log_hex( bufp, end-bufp, __func__ ); unsigned short nameCount; @@ -975,7 +981,7 @@ handleProxyMsgs( int sock, const AddrInfo* addr, unsigned char* bufp, HostID dest; XWRELAY_Cmd cmd; if ( getNetShort( &bufp, end, &len ) ) { - unsigned char* start = bufp; + const unsigned char* start = bufp; if ( getNetByte( &bufp, end, &cmd ) && getNetByte( &bufp, end, &src ) && getNetByte( &bufp, end, &dest ) ) { @@ -1001,8 +1007,8 @@ handle_proxy_packet( unsigned char* buf, int len, const AddrInfo* addr ) if ( len > 0 ) { assert( addr->isTCP() ); int socket = addr->socket(); - unsigned char* bufp = buf; - unsigned char* end = bufp + len; + const unsigned char* bufp = buf; + const unsigned char* end = bufp + len; if ( (0 == *bufp++) ) { /* protocol */ XWPRXYCMD cmd = (XWPRXYCMD)*bufp++; switch( cmd ) { @@ -1097,12 +1103,58 @@ registerDevice( const DevID* devID, const AddrInfo::AddrUnion* saddr ) DevMgr::Get()->Remember( relayID, saddr ); } +static void +udp_thread_proc( UdpThreadClosure* utc ) +{ + const unsigned char* ptr = utc->buf(); + const unsigned char* end = ptr + utc->len(); + + unsigned char proto = *ptr++; + if ( XWREG_PROTO_VERSION != 0 ) { + logf( XW_LOGERROR, "unexpected proto %d", __func__, (int) proto ); + } else { + int msg = *ptr++; + switch( msg ) { + case XWRREG_REG: { + DevIDType typ = (DevIDType)*ptr++; + unsigned short idLen; + if ( !getNetShort( &ptr, end, &idLen ) ) { + break; + } + if ( end - ptr > idLen ) { + logf( XW_LOGERROR, "full devID not received" ); + break; + } + DevID devID( typ ); + devID.m_devIDString.append( (const char*)ptr, idLen ); + ptr += idLen; + registerDevice( &devID, utc->saddr() ); + } + break; + case XWRREG_MSG: { + AddrInfo::ClientToken clientToken; + memcpy( &clientToken, ptr, sizeof(clientToken) ); + ptr += sizeof(clientToken); + clientToken = ntohl( clientToken ); + if ( 0 != clientToken ) { + AddrInfo addr( g_udpsock, clientToken, utc->saddr() ); + (void)processMessage( ptr, end - ptr, &addr ); + } else { + logf( XW_LOGERROR, "%s: dropping packet with token of 0" ); + } + } + break; + default: + logf( XW_LOGERROR, "%s: unexpected msg %d", __func__, msg ); + } + } +} + // This will need to be done in a thread before there can be simulaneous // connections. static void handle_udp_packet( int udpsock ) { - bool success = false; logf( XW_LOGINFO, "%s()", __func__ ); unsigned char buf[512]; AddrInfo::AddrUnion saddr; @@ -1111,52 +1163,10 @@ handle_udp_packet( int udpsock ) ssize_t nRead = recvfrom( udpsock, buf, sizeof(buf), 0 /*flags*/, &saddr.addr, &fromlen ); - if ( 2 <= nRead ) { - unsigned char* ptr = buf; - unsigned char* end = buf + nRead; - logf( XW_LOGINFO, "%s: recvfrom=>%d", __func__, nRead ); - - unsigned char proto = *ptr++; - if ( XWREG_PROTO_VERSION != 0 ) { - logf( XW_LOGERROR, "unexpected proto %d", __func__, (int) proto ); - } else { - int msg = *ptr++; - switch( msg ) { - case XWRREG_REG: { - DevIDType typ = (DevIDType)*ptr++; - unsigned short idLen; - if ( !getNetShort( &ptr, end, &idLen ) ) { - break; - } - if ( end - ptr > idLen ) { - logf( XW_LOGERROR, "full devID not received" ); - break; - } - DevID devID( typ ); - devID.m_devIDString.append( (const char*)ptr, idLen ); - ptr += idLen; - registerDevice( &devID, &saddr ); - } - break; - case XWRREG_MSG: { - uint32_t clientToken; - memcpy( &clientToken, ptr, sizeof(clientToken) ); - ptr += sizeof(clientToken); - clientToken = ntohl( clientToken ); - if ( 0 != clientToken ) { - AddrInfo addr( udpsock, clientToken, &saddr ); - success = processMessage( ptr, end - ptr, &addr ); - } else { - logf( XW_LOGERROR, "%s: dropping packet with token of 0" ); - } - } - break; - default: - logf( XW_LOGERROR, "%s: unexpected msg %d", __func__, msg ); - } - } - } - logf( XW_LOGINFO, "%s()=>%d", __func__, success ); + logf( XW_LOGINFO, "%s: recvfrom=>%d", __func__, utc->len() ); + if ( 0 < nRead ) { + UdpQueue::get()->handle( &saddr, buf, nRead, udp_thread_proc ); + } } /* From stack overflow, toward a snprintf with an expanding buffer. @@ -1498,7 +1508,9 @@ main( int argc, char** argv ) saddr.sin_addr.s_addr = htonl(INADDR_ANY); saddr.sin_port = htons(udpport); int err = bind( g_udpsock, (struct sockaddr*)&saddr, sizeof(saddr) ); - if ( 0 != err ) { + if ( 0 == err ) { + err = fcntl( g_udpsock, F_SETFL, O_NONBLOCK ); + } else { logf( XW_LOGERROR, "bind()=>%s", strerror(errno) ); g_udpsock = -1; } diff --git a/xwords4/relay/xwrelay_priv.h b/xwords4/relay/xwrelay_priv.h index 351d5ba0e..51d8da001 100644 --- a/xwords4/relay/xwrelay_priv.h +++ b/xwords4/relay/xwrelay_priv.h @@ -45,7 +45,7 @@ void logf( XW_LogLevel level, const char* format, ... ); void denyConnection( const AddrInfo* addr, XWREASON err ); bool send_with_length_unsafe( const AddrInfo* addr, - unsigned char* buf, size_t bufLen ); + const unsigned char* buf, size_t bufLen ); time_t uptime(void); From 3984d8579569045b3de49422f328ffa7ba059a0e Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 18 Jan 2013 07:12:26 -0800 Subject: [PATCH 024/116] oops -- prev checkin didn't compile --- xwords4/relay/xwrelay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index d7ddc4b62..faf99ba15 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -1163,7 +1163,7 @@ handle_udp_packet( int udpsock ) ssize_t nRead = recvfrom( udpsock, buf, sizeof(buf), 0 /*flags*/, &saddr.addr, &fromlen ); - logf( XW_LOGINFO, "%s: recvfrom=>%d", __func__, utc->len() ); + logf( XW_LOGINFO, "%s: recvfrom=>%d", __func__, nRead ); if ( 0 < nRead ) { UdpQueue::get()->handle( &saddr, buf, nRead, udp_thread_proc ); } From c999bbae9257ade0c55816609aeff660fe8516bf Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 18 Jan 2013 07:22:56 -0800 Subject: [PATCH 025/116] always test those last-minute pre-checkin cleanups --- xwords4/relay/cref.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xwords4/relay/cref.cpp b/xwords4/relay/cref.cpp index bd7064182..5f3a47ab4 100644 --- a/xwords4/relay/cref.cpp +++ b/xwords4/relay/cref.cpp @@ -1134,9 +1134,9 @@ CookieRef::forward_or_store( const CRefEvent* evt ) do { int buflen = evt->u.fwd.buflen; unsigned char buf[buflen]; - if ( *buf == XWRELAY_MSG_TORELAY ) { + if ( *cbuf == XWRELAY_MSG_TORELAY ) { buf[0] = XWRELAY_MSG_FROMRELAY; - } else if ( *buf == XWRELAY_MSG_TORELAY_NOCONN ) { + } else if ( *cbuf == XWRELAY_MSG_TORELAY_NOCONN ) { *buf = XWRELAY_MSG_FROMRELAY_NOCONN; } else { logf( XW_LOGERROR, "%s: got XWRELAY type of %d", __func__, From 1519a00004b9da8835e77726b68f059075365dd3 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 18 Jan 2013 07:49:00 -0800 Subject: [PATCH 026/116] toward being able to handle messages received for closed game. Receipt seems to work but send in that state doesn't. --- xwords4/linux/Makefile | 1 + xwords4/linux/cursesmain.c | 12 ++- xwords4/linux/gtkboard.c | 176 ++++++++++++++++++++++++++----------- xwords4/linux/gtkboard.h | 6 +- xwords4/linux/gtkmain.c | 18 ++-- xwords4/linux/linuxmain.c | 1 - 6 files changed, 156 insertions(+), 58 deletions(-) diff --git a/xwords4/linux/Makefile b/xwords4/linux/Makefile index 9a74cefbc..27a8f34be 100644 --- a/xwords4/linux/Makefile +++ b/xwords4/linux/Makefile @@ -113,6 +113,7 @@ DEFINES += -DXWFEATURE_HILITECELL DEFINES += -DXWFEATURE_CHANGEDICT DEFINES += -DXWFEATURE_DEVID DEFINES += -DXWFEATURE_COMMSACK +DEFINES += -DCOMMS_XPORT_FLAGSPROC # MAX_ROWS controls STREAM_VERS_BIGBOARD and with it move hashing DEFINES += -DMAX_ROWS=32 diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index 73c1e1e3e..54466f653 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -1700,6 +1700,14 @@ handle_stdin( GIOChannel* XP_UNUSED_DBG(source), GIOCondition condition, } #endif +#ifdef COMMS_XPORT_FLAGSPROC +static XP_U32 +curses_getFlags( void* XP_UNUSED(closure) ) +{ + return COMMS_XPORT_FLAGS_HASNOCONN; +} +#endif + void cursesmain( XP_Bool isServer, LaunchParams* params ) { @@ -1776,7 +1784,9 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) .rconnd = relay_connd_curses, .rerror = relay_error_curses, .sendNoConn = relay_sendNoConn_curses, - .flags = COMMS_XPORT_FLAGS_HASNOCONN, +# ifdef COMMS_XPORT_FLAGSPROC + .getFlags = curses_getFlags, +# endif #endif }; diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 9e872af4e..1421c6dfc 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -391,6 +391,47 @@ relay_error_gtk( void* closure, XWREASON relayErr ) } } +static XP_Bool +relay_sendNoConn_gtk( const XP_U8* XP_UNUSED(msg), XP_U16 XP_UNUSED(len), + const XP_UCHAR* XP_UNUSED(relayID), void* closure ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)closure; + XP_Bool success = XP_FALSE; + if ( !!globals->cGlobals.pDb && !globals->draw ) { + XP_ASSERT( 0 ); /* implement me!!! */ + } + return success; +} /* relay_sendNoConn_gtk */ + +#ifdef COMMS_XPORT_FLAGSPROC +static XP_U32 +gtk_getFlags( void* closure ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)closure; + return (!!globals->draw) ? COMMS_XPORT_FLAGS_NONE + : COMMS_XPORT_FLAGS_HASNOCONN; +} +#endif + +static void +setTransportProcs( TransportProcs* procs, GtkGameGlobals* globals ) +{ + procs->closure = globals; + procs->send = LINUX_SEND; +#ifdef COMMS_XPORT_FLAGSPROC + procs->getFlags = gtk_getFlags; +#endif +#ifdef COMMS_HEARTBEAT + procs->reset = linux_reset; +#endif +#ifdef XWFEATURE_RELAY + procs->rstatus = relay_status_gtk; + procs->rconnd = relay_connd_gtk; + procs->rerror = relay_error_gtk; + procs->sendNoConn = relay_sendNoConn_gtk; +#endif +} + static void createOrLoadObjects( GtkGameGlobals* globals ) { @@ -405,18 +446,8 @@ createOrLoadObjects( GtkGameGlobals* globals ) globals->draw = (GtkDrawCtx*)gtkDrawCtxtMake( globals->drawing_area, globals ); - TransportProcs procs = { - .closure = globals, - .send = LINUX_SEND, -#ifdef COMMS_HEARTBEAT - .reset = linux_reset, -#endif -#ifdef XWFEATURE_RELAY - .rstatus = relay_status_gtk, - .rconnd = relay_connd_gtk, - .rerror = relay_error_gtk, -#endif - }; + TransportProcs procs; + setTransportProcs( &procs, globals ); if ( !!params->fileName && file_exists( params->fileName ) ) { stream = streamFromFile( cGlobals, params->fileName, globals ); @@ -699,11 +730,10 @@ handle_client_event( GtkWidget *widget, GdkEventClient *event, } /* handle_client_event */ #endif -static void -destroy_window( GtkWidget* XP_UNUSED(widget), gpointer data ) +void +destroy_board_window( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) { LOG_FUNC(); - GtkGameGlobals* globals = (GtkGameGlobals*)data; if ( !!globals->cGlobals.game.comms ) { comms_stop( globals->cGlobals.game.comms ); } @@ -1520,7 +1550,7 @@ gtk_util_notifyGameOver( XW_UtilCtxt* uc, XP_S16 quitter ) if ( cGlobals->params->quitAfter >= 0 ) { sleep( cGlobals->params->quitAfter ); - destroy_window( NULL, globals ); + destroy_board_window( NULL, globals ); } else if ( cGlobals->params->undoWhenDone ) { server_handleUndo( cGlobals->game.server, 0 ); board_draw( cGlobals->game.board ); @@ -1734,7 +1764,8 @@ idle_func( gpointer data ) and bad things can happen. So kill the idle proc asap. */ gtk_idle_remove( globals->idleID ); - if ( server_do( globals->cGlobals.game.server ) ) { + ServerCtxt* server = globals->cGlobals.game.server; + if ( !!server && server_do( server ) ) { if ( !!globals->cGlobals.game.board ) { board_draw( globals->cGlobals.game.board ); } @@ -2369,20 +2400,9 @@ drop_msg_toggle( GtkWidget* toggle, GtkGameGlobals* globals ) /* return 0; */ /* } */ -void -initGlobals( GtkGameGlobals* globals, LaunchParams* params ) +static void +initGlobalsNoDraw( GtkGameGlobals* globals, LaunchParams* params ) { - short width, height; - GtkWidget* window; - GtkWidget* drawing_area; - GtkWidget* menubar; - GtkWidget* buttonbar; - GtkWidget* vbox; - GtkWidget* hbox; -#ifndef XWFEATURE_STANDALONE_ONLY - GtkWidget* dropCheck; -#endif - memset( globals, 0, sizeof(*globals) ); globals->cGlobals.params = params; @@ -2418,6 +2438,24 @@ initGlobals( GtkGameGlobals* globals, LaunchParams* params ) setupUtil( &globals->cGlobals ); setupGtkUtilCallbacks( globals, globals->cGlobals.util ); +} + +void +initGlobals( GtkGameGlobals* globals, LaunchParams* params ) +{ + short width, height; + GtkWidget* window; + GtkWidget* drawing_area; + GtkWidget* menubar; + GtkWidget* buttonbar; + GtkWidget* vbox; + GtkWidget* hbox; +#ifndef XWFEATURE_STANDALONE_ONLY + GtkWidget* dropCheck; +#endif + + initGlobalsNoDraw( globals, params ); + globals->window = window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); if ( !!params->fileName ) { gtk_window_set_title( GTK_WINDOW(window), params->fileName ); @@ -2428,7 +2466,7 @@ initGlobals( GtkGameGlobals* globals, LaunchParams* params ) gtk_widget_show( vbox ); g_signal_connect( G_OBJECT (window), "destroy", - G_CALLBACK( destroy_window ), globals ); + G_CALLBACK( destroy_board_window ), globals ); menubar = makeMenus( globals ); gtk_box_pack_start( GTK_BOX(vbox), menubar, FALSE, TRUE, 0); @@ -2525,6 +2563,44 @@ freeGlobals( GtkGameGlobals* globals ) cleanup( globals ); } +XP_Bool +loadGameNoDraw( GtkGameGlobals* globals, LaunchParams* params, + sqlite3* pDb, sqlite3_int64 rowid ) +{ + LOG_FUNC(); + initGlobalsNoDraw( globals, params ); + + TransportProcs procs; + setTransportProcs( &procs, globals ); + + CommonGlobals* cGlobals = &globals->cGlobals; + cGlobals->selRow = rowid; + cGlobals->pDb = pDb; + XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool) + params->vtMgr, cGlobals, + CHANNEL_NONE, NULL ); + XP_Bool loaded = loadGame( stream, cGlobals->pDb, rowid ); + if ( loaded ) { + if ( NULL == cGlobals->dict ) { + cGlobals->dict = makeDictForStream( cGlobals, stream ); + } + loaded = game_makeFromStream( MEMPOOL stream, &cGlobals->game, + &cGlobals->gi, + cGlobals->dict, &cGlobals->dicts, cGlobals->util, + (DrawCtx*)NULL, &cGlobals->cp, &procs ); + if ( loaded ) { + XP_LOGF( "%s: game loaded", __func__ ); +#ifndef XWFEATURE_STANDALONE_ONLY + if ( !!globals->cGlobals.game.comms ) { + comms_resendAll( globals->cGlobals.game.comms, XP_FALSE ); + } +#endif + } + } + stream_destroy( stream ); + return loaded; +} + XP_Bool makeNewGame( GtkGameGlobals* globals ) { @@ -2549,35 +2625,35 @@ makeNewGame( GtkGameGlobals* globals ) } void -gameGotBuf( GtkGameGlobals* globals, XP_U8* buf, XP_U16 len ) +gameGotBuf( GtkGameGlobals* globals, XP_Bool hasDraw, XP_U8* buf, XP_U16 len ) { + XP_LOGF( "%s(hasDraw=%d)", __func__, hasDraw ); XP_Bool redraw = XP_FALSE; - + XWGame* game = &globals->cGlobals.game; XWStreamCtxt* stream = stream_from_msgbuf( &globals->cGlobals, buf, len ); if ( !!stream ) { - if ( comms_checkIncomingStream( globals->cGlobals.game.comms, - stream, NULL ) ) { - redraw = - server_receiveMessage( globals->cGlobals.game.server, - stream ); + if ( comms_checkIncomingStream( game->comms, stream, NULL ) ) { + redraw = server_receiveMessage( game->server, stream ); if ( redraw ) { saveGame( &globals->cGlobals ); } } stream_destroy( stream ); - } - /* if there's something to draw resulting from the message, we - need to give the main loop time to reflect that on the screen - before giving the server another shot. So just call the idle - proc. */ - if ( redraw ) { - gtk_util_requestTime( globals->cGlobals.util ); - } else { - redraw = server_do( globals->cGlobals.game.server ); - } - if ( redraw ) { - board_draw( globals->cGlobals.game.board ); + /* if there's something to draw resulting from the message, we + need to give the main loop time to reflect that on the screen + before giving the server another shot. So just call the idle + proc. */ + if ( hasDraw && redraw ) { + gtk_util_requestTime( globals->cGlobals.util ); + } else { + for ( int ii = 0; ii < 4; ++ii ) { + redraw = server_do( game->server ) || redraw; + } + } + if ( hasDraw && redraw ) { + board_draw( game->board ); + } } } diff --git a/xwords4/linux/gtkboard.h b/xwords4/linux/gtkboard.h index e2ad10dac..4861f912e 100644 --- a/xwords4/linux/gtkboard.h +++ b/xwords4/linux/gtkboard.h @@ -169,7 +169,11 @@ typedef struct GtkGameGlobals { void initGlobals( GtkGameGlobals* globals, LaunchParams* params ); void freeGlobals( GtkGameGlobals* globals ); XP_Bool makeNewGame( GtkGameGlobals* globals ); -void gameGotBuf( GtkGameGlobals* globals, XP_U8* buf, XP_U16 len ); +void gameGotBuf( GtkGameGlobals* globals, XP_Bool haveDraw, + XP_U8* buf, XP_U16 len ); +XP_Bool loadGameNoDraw( GtkGameGlobals* globals, LaunchParams* params, + sqlite3* pDb, sqlite3_int64 rowid ); +void destroy_board_window( GtkWidget* widget, GtkGameGlobals* globals ); #endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index 6a5f21ac1..b68e2d6b5 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -253,7 +253,8 @@ handle_destroy( GtkWidget* XP_UNUSED(widget), gpointer data ) GSList* iter; for ( iter = apg->globalsList; !!iter; iter = iter->next ) { GtkGameGlobals* globals = (GtkGameGlobals*)iter->data; - freeGlobals( globals ); + destroy_board_window( NULL, globals ); + // freeGlobals( globals ); } g_slist_free( apg->globalsList ); gtk_main_quit(); @@ -394,10 +395,14 @@ gtkGotBuf( void* closure, XP_U8* buf, XP_U16 len ) GtkGameGlobals* globals = findGame( apg, gameToken ); if ( !!globals ) { - gameGotBuf( globals, buf, len ); + gameGotBuf( globals, XP_TRUE, buf, len ); } else { - XP_LOGF( "%s: game with token %lu not found; not open or deleted", - __func__, gameToken ); + GtkGameGlobals tmpGlobals; + if ( loadGameNoDraw( &tmpGlobals, apg->params, apg->pDb, gameToken ) ) { + gameGotBuf( &tmpGlobals, XP_FALSE, buf, len ); + saveGame( &tmpGlobals.cGlobals ); + } + freeGlobals( &tmpGlobals ); } } @@ -415,7 +420,10 @@ onGameSaved( void* closure, sqlite3_int64 rowid, { GtkGameGlobals* globals = (GtkGameGlobals*)closure; GtkAppGlobals* apg = globals->apg; - onNewData( apg, rowid, firstTime ); + /* May not be recorded */ + if ( !!apg ) { + onNewData( apg, rowid, firstTime ); + } } sqlite3_int64 diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 340af33ef..083035540 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -1000,7 +1000,6 @@ linux_close_socket( CommonGlobals* cGlobals ) XP_ASSERT( -1 == cGlobals->socket ); - XP_LOGF( "linux_close_socket" ); close( socket ); } From 99307e45a0f7b6cd33df389a3cdb765f03efc340 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 18 Jan 2013 19:56:21 -0800 Subject: [PATCH 027/116] queue incoming packets and process them in order in a separate thread --- xwords4/relay/udpqueue.cpp | 74 ++++++++++++++++++++++++++++++++++++-- xwords4/relay/udpqueue.h | 17 +++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/xwords4/relay/udpqueue.cpp b/xwords4/relay/udpqueue.cpp index 9364f0e7b..8d768cc1d 100644 --- a/xwords4/relay/udpqueue.cpp +++ b/xwords4/relay/udpqueue.cpp @@ -20,10 +20,40 @@ */ #include "udpqueue.h" +#include "mlock.h" + static UdpQueue* s_instance = NULL; +void +UdpThreadClosure::logStats() +{ + time_t now = time( NULL ); + if ( 1 < now - m_created ) { + logf( XW_LOGERROR, "packet waited %d s for processing which then took %d s", + m_dequed - m_created, now - m_dequed ); + } +} + +UdpQueue::UdpQueue() +{ + pthread_mutex_init ( &m_queueMutex, NULL ); + pthread_cond_init( &m_queueCondVar, NULL ); + + pthread_t thread; + int result = pthread_create( &thread, NULL, thread_main_static, this ); + assert( result == 0 ); + result = pthread_detach( thread ); + assert( result == 0 ); +} + +UdpQueue::~UdpQueue() +{ + pthread_cond_destroy( &m_queueCondVar ); + pthread_mutex_destroy ( &m_queueMutex ); +} + UdpQueue* UdpQueue::get() { @@ -37,7 +67,45 @@ void UdpQueue::handle( const AddrInfo::AddrUnion* saddr, unsigned char* buf, int len, QueueCallback cb ) { - logf( XW_LOGINFO, "%s: still running in same thread!!!", __func__ ); - UdpThreadClosure closure( saddr, buf, len ); - (*cb)( &closure ); + UdpThreadClosure* utc = new UdpThreadClosure( saddr, buf, len ); + MutexLock ml( &m_queueMutex ); + setCB( cb ); + m_queue.push_back( utc ); + pthread_cond_signal( &m_queueCondVar ); +} + +void* +UdpQueue::thread_main() +{ + for ( ; ; ) { + pthread_mutex_lock( &m_queueMutex ); + while ( m_queue.size() == 0 ) { + pthread_cond_wait( &m_queueCondVar, &m_queueMutex ); + } + UdpThreadClosure* utc = m_queue.front(); + m_queue.pop_front(); + pthread_mutex_unlock( &m_queueMutex ); + + utc->noteDequeued(); + (*m_cb)( utc ); + utc->logStats(); + delete utc; + } + return NULL; +} + +/* static */ void* +UdpQueue::thread_main_static( void* closure ) +{ + blockSignals(); + + UdpQueue* me = (UdpQueue*)closure; + return me->thread_main(); +} + +void +UdpQueue::setCB( QueueCallback cb ) +{ + assert( cb == m_cb || !m_cb ); + m_cb = cb; } diff --git a/xwords4/relay/udpqueue.h b/xwords4/relay/udpqueue.h index 3638018c1..04f05f956 100644 --- a/xwords4/relay/udpqueue.h +++ b/xwords4/relay/udpqueue.h @@ -22,6 +22,7 @@ #define _UDPQUEUE_H_ #include +#include #include "xwrelay_priv.h" #include "addrinfo.h" @@ -35,17 +36,22 @@ public: m_buf = new unsigned char[len]; memcpy( m_buf, buf, len ); m_len = len; + m_created = time( NULL ); } ~UdpThreadClosure() { delete m_buf; } const unsigned char* buf() const { return m_buf; } int len() const { return m_len; } const AddrInfo::AddrUnion* saddr() const { return &m_saddr; } + void noteDequeued() { m_dequed = time( NULL ); } + void logStats(); private: unsigned char* m_buf; int m_len; AddrInfo::AddrUnion m_saddr; + time_t m_created; + time_t m_dequed; }; typedef void (*QueueCallback)( UdpThreadClosure* closure ); @@ -53,10 +59,21 @@ typedef void (*QueueCallback)( UdpThreadClosure* closure ); class UdpQueue { public: static UdpQueue* get(); + UdpQueue(); + ~UdpQueue(); void handle( const AddrInfo::AddrUnion* saddr, unsigned char* buf, int len, QueueCallback cb ); private: + static void* thread_main_static( void* closure ); + void* thread_main(); + void setCB( QueueCallback cb ); + + pthread_mutex_t m_queueMutex; + pthread_cond_t m_queueCondVar; + deque m_queue; + + QueueCallback m_cb; }; #endif From 63d3f70cdfae51345fc70f290a84e768dbefe025 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 19 Jan 2013 14:34:04 -0800 Subject: [PATCH 028/116] more changes to storage and fetching of messages and to protocol so presence of messages is reported on connect (as are bad relayIDs). Now a game with a robot player in a "closed" game can continue. Once the next set of linux-side chances is committed. --- xwords4/relay/cref.cpp | 29 +++-- xwords4/relay/cref.h | 2 + xwords4/relay/dbmgr.cpp | 198 ++++++++++++++++++++++------- xwords4/relay/dbmgr.h | 10 ++ xwords4/relay/states.cpp | 7 ++ xwords4/relay/states.h | 3 + xwords4/relay/xwrelay.cpp | 233 +++++++++++++++++++++++++++++------ xwords4/relay/xwrelay.h | 32 +++-- xwords4/relay/xwrelay.sh | 2 + xwords4/relay/xwrelay_priv.h | 1 + 10 files changed, 421 insertions(+), 96 deletions(-) diff --git a/xwords4/relay/cref.cpp b/xwords4/relay/cref.cpp index 5f3a47ab4..cbd7558c0 100644 --- a/xwords4/relay/cref.cpp +++ b/xwords4/relay/cref.cpp @@ -590,7 +590,7 @@ void CookieRef::pushForwardEvent( HostID src, const AddrInfo* addr, HostID dest, const unsigned char* buf, int buflen ) { - logf( XW_LOGVERBOSE1, "pushForwardEvent: %d -> %d", src, dest ); + logf( XW_LOGVERBOSE1, "%s: %d -> %d", __func__, src, dest ); CRefEvent evt( XWE_FORWARDMSG, addr ); evt.u.fwd.src = src; evt.u.fwd.dest = dest; @@ -704,6 +704,10 @@ CookieRef::handleEvents() forward_or_store/*_proxy*/( &evt ); break; + case XWA_TRYTELL: + send_havemsgs( &evt.addr ); + break; + case XWA_TIMERDISCONN: disconnectSockets( XWRELAY_ERROR_TIMEOUT ); break; @@ -993,6 +997,14 @@ CookieRef::postDropDevice( HostID hostID ) handleEvents(); } +void +CookieRef::postTellHaveMsgs( const AddrInfo* addr ) +{ + CRefEvent evt( XWE_TRYTELL, addr ); + m_eventQueue.push_back( evt ); + assert( m_in_handleEvents ); +} + void CookieRef::setAllConnectedTimer() { @@ -1129,7 +1141,6 @@ CookieRef::sendAnyStored( const CRefEvent* evt ) void CookieRef::forward_or_store( const CRefEvent* evt ) { - AddrInfo addr; // invalid unless assigned to const unsigned char* cbuf = evt->u.fwd.buf; do { int buflen = evt->u.fwd.buflen; @@ -1153,6 +1164,11 @@ CookieRef::forward_or_store( const CRefEvent* evt ) usleep( m_delayMicros ); } + if ( (NULL == destAddr) + || !send_with_length( destAddr, dest, buf, buflen, true ) ) { + store_message( dest, buf, buflen ); + } + // If recipient GAME isn't connected, see if owner device is and can // receive if ( NULL == destAddr) { @@ -1161,17 +1177,12 @@ CookieRef::forward_or_store( const CRefEvent* evt ) if ( DBMgr::Get()->TokenFor( ConnName(), dest, &devid, &token ) ) { const AddrInfo::AddrUnion* saddr = DevMgr::Get()->get( devid ); if ( !!saddr ) { - addr.init( -1, token, saddr ); - destAddr = &addr; + AddrInfo addr( -1, token, saddr ); + postTellHaveMsgs( &addr ); } } } - if ( (NULL == destAddr) - || !send_with_length( destAddr, dest, buf, buflen, true ) ) { - store_message( dest, buf, buflen ); - } - /* also note that we've heard from src recently */ HostID src = evt->u.fwd.src; DBMgr::Get()->RecordAddress( ConnName(), src, &evt->addr ); diff --git a/xwords4/relay/cref.h b/xwords4/relay/cref.h index 45cd35863..48d1d2b80 100644 --- a/xwords4/relay/cref.h +++ b/xwords4/relay/cref.h @@ -230,6 +230,7 @@ class CookieRef { void postCheckAllHere(); void postDropDevice( HostID hostID ); + void postTellHaveMsgs( const AddrInfo* addr ); void setAllConnectedTimer(); void cancelAllConnectedTimer(); @@ -238,6 +239,7 @@ class CookieRef { void forward_or_store( const CRefEvent* evt ); void send_denied( const CRefEvent* evt, XWREASON why ); + void send_trytell( const CRefEvent* evt ); void checkFromServer( const CRefEvent* evt ); void notifyOthers( const AddrInfo* addr, XWRelayMsg msg, XWREASON why ); diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index a94fd3a2e..8f82b5514 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -262,7 +262,9 @@ DBMgr::RegisterDevice( const DevID* host ) // If it's not present *and* of type ID_TYPE_RELAY, we can do nothing. // Otherwise proceed. - if ( DEVID_NONE == devID && ID_TYPE_RELAY < host->m_devIDType ) { + if ( DEVID_NONE != devID ) { + (void)updateDevice( devID, false ); + } else if ( ID_TYPE_RELAY < host->m_devIDType ) { // loop until we're successful inserting the unique key. Ship with this // coming from random, but test with increasing values initially to make // sure duplicates are detected. @@ -299,6 +301,25 @@ DBMgr::RegisterDevice( const DevID* host ) return devID; } +bool +DBMgr::updateDevice( DevIDRelay relayID, bool check ) +{ + bool exists = !check; + if ( !exists ) { + string test; + string_printf( test, "id = %d", relayID ); + exists = 1 == getCountWhere( DEVICES_TABLE, test ); + } + + if ( exists ) { + const char* fmt = "UPDATE " DEVICES_TABLE " SET mtime='now' WHERE id = %d"; + string query; + string_printf( query, fmt, relayID ); + execSql( query ); + } + return exists; +} + HostID DBMgr::AddDevice( const char* connName, HostID curID, int clientVersion, int nToAdd, unsigned short seed, const AddrInfo* addr, @@ -606,23 +627,12 @@ DBMgr::TokenFor( const char* const connName, int hid, DevIDRelay* devid, int DBMgr::PendingMsgCount( const char* connName, int hid ) { - int count = 0; - const char* fmt = "SELECT COUNT(*) FROM " MSGS_TABLE - " WHERE connName = '%s' AND hid = %d " + string test; + string_printf( test, "connName = '%s' AND hid = %d ", connName, hid ); #ifdef HAVE_STIME - "AND stime IS NULL" + string_printf( test, " AND stime IS NULL" ); #endif - ; - string query; - string_printf( query, fmt, connName, hid ); - logf( XW_LOGVERBOSE0, "%s: query: %s", __func__, query.c_str() ); - - PGresult* result = PQexec( getThreadConn(), query.c_str() ); - if ( 1 == PQntuples( result ) ) { - count = atoi( PQgetvalue( result, 0, 0 ) ); - } - PQclear( result ); - return count; + return getCountWhere( MSGS_TABLE, test ); } bool @@ -703,6 +713,8 @@ DBMgr::getDevID( const DevID* devID ) } PQclear( result ); } + logf( XW_LOGINFO, "%s(in=%s)=>%d (0x.8X)", __func__, + devID->m_devIDString.c_str(), rDevID, rDevID ); return rDevID; } @@ -717,25 +729,16 @@ DBMgr::getDevID( const DevID* devID ) int DBMgr::CountStoredMessages( const char* const connName, int hid ) { - const char* fmt = "SELECT count(*) FROM " MSGS_TABLE - " WHERE connname = '%s' " + string test; + string_printf( test, "connname = '%s'", connName ); #ifdef HAVE_STIME - "AND stime IS NULL" + string_printf( test, " AND stime IS NULL" ); #endif - ; - - string query; - string_printf( query, fmt, connName ); - if ( hid != -1 ) { - string_printf( query, "AND hid = %d", hid ); + string_printf( test, " AND hid = %d", hid ); } - PGresult* result = PQexec( getThreadConn(), query.c_str() ); - assert( 1 == PQntuples( result ) ); - int count = atoi( PQgetvalue( result, 0, 0 ) ); - PQclear( result ); - return count; + return getCountWhere( MSGS_TABLE, test ); } int @@ -744,6 +747,36 @@ DBMgr::CountStoredMessages( const char* const connName ) return CountStoredMessages( connName, -1 ); } /* CountStoredMessages */ +int +DBMgr::CountStoredMessages( DevIDRelay relayID ) +{ + string test; + string_printf( test, "devid = %d", relayID ); +#ifdef HAVE_STIME + string_printf( test, "AND stime IS NULL" ); +#endif + + return getCountWhere( MSGS_TABLE, test ); +} + +void +DBMgr::GetStoredMessageIDs( DevIDRelay relayID, vector& ids ) +{ + const char* fmt = "SELECT id FROM " MSGS_TABLE " WHERE devid=%d"; + string query; + string_printf( query, fmt, relayID ); + // logf( XW_LOGINFO, "%s: query=\"%s\"", __func__, query.c_str() ); + PGresult* result = PQexec( getThreadConn(), query.c_str() ); + int nTuples = PQntuples( result ); + for ( int ii = 0; ii < nTuples; ++ii ) { + int id = atoi( PQgetvalue( result, ii, 0 ) ); + // logf( XW_LOGINFO, "%s: adding id %d", __func__, id ); + ids.push_back( id ); + } + PQclear( result ); + logf( XW_LOGINFO, "%s(relayID=%d)=>%d ids", __func__, relayID, ids.size() ); +} + void DBMgr::StoreMessage( const char* const connName, int hid, const unsigned char* buf, int len ) @@ -752,15 +785,18 @@ DBMgr::StoreMessage( const char* const connName, int hid, size_t newLen; const char* fmt = "INSERT INTO " MSGS_TABLE - " (connname, hid, devid, msg, msglen)" - " VALUES( '%s', %d, %d, E'%s', %d)"; + " (connname, hid, devid, token, msg, msglen)" + " VALUES( '%s', %d, %d, " + "(SELECT tokens[%d] from " GAMES_TABLE " where connname='%s'), " + "E'%s', %d)"; unsigned char* bytes = PQescapeByteaConn( getThreadConn(), buf, len, &newLen ); assert( NULL != bytes ); string query; - string_printf( query, fmt, connName, hid, devID, bytes, len ); + string_printf( query, fmt, connName, hid, devID, hid, connName, + bytes, len ); PQfreemem( bytes ); @@ -815,6 +851,58 @@ DBMgr::GetStoredMessage( const char* const connName, int hid, return GetNthStoredMessage( connName, hid, 0, buf, buflen, msgID ); } +bool +DBMgr::GetStoredMessage( int msgID, unsigned char* buf, size_t* buflen, + AddrInfo::ClientToken* token ) +{ + const char* fmt = "SELECT token, msg, msglen FROM " MSGS_TABLE + " WHERE id = %d " +#ifdef HAVE_STIME + "AND stime IS NULL " +#endif + ; + string query; + string_printf( query, fmt, msgID ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); + + PGresult* result = PQexec( getThreadConn(), query.c_str() ); + int nTuples = PQntuples( result ); + assert( nTuples <= 1 ); + + bool found = nTuples == 1; + if ( found ) { + *token = atoi( PQgetvalue( result, 0, 0 ) ); + size_t msglen = atoi( PQgetvalue( result, 0, 2 ) ); + const unsigned char* from = + (const unsigned char* )PQgetvalue( result, 0, 1 ); + size_t to_length; + unsigned char* bytes = PQunescapeBytea( from, &to_length ); + assert( to_length <= *buflen ); + memcpy( buf, bytes, to_length ); + PQfreemem( bytes ); + *buflen = to_length; + assert( 0 == msglen || to_length == msglen ); + } + PQclear( result ); + return found; +} + +void +DBMgr::RemoveStoredMessages( string& msgids ) +{ + const char* fmt = +#ifdef HAVE_STIME + "UPDATE " MSGS_TABLE " SET stime='now' " +#else + "DELETE FROM " MSGS_TABLE +#endif + " WHERE id IN (%s)"; + string query; + string_printf( query, fmt, msgids.c_str() ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); + execSql( query ); +} + void DBMgr::RemoveStoredMessages( const int* msgIDs, int nMsgIDs ) { @@ -831,21 +919,41 @@ DBMgr::RemoveStoredMessages( const int* msgIDs, int nMsgIDs ) ids.append( "," ); } } - - const char* fmt = -#ifdef HAVE_STIME - "UPDATE " MSGS_TABLE " SET stime='now' " -#else - "DELETE FROM " MSGS_TABLE -#endif - " WHERE id IN (%s)"; - string query; - string_printf( query, fmt, ids.c_str() ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); - execSql( query ); + RemoveStoredMessages( ids ); } } +void +DBMgr::RemoveStoredMessages( vector& idv ) +{ + if ( 0 < idv.size() ) { + string ids; + vector::const_iterator iter = idv.begin(); + for ( ; ; ) { + string_printf( ids, "%d", *iter ); + if ( ++iter == idv.end() ) { + break; + } + string_printf( ids, "," ); + } + RemoveStoredMessages( ids ); + } +} + +int +DBMgr::getCountWhere( const char* table, string& test ) +{ + string query; + string_printf( query, "SELECT count(*) FROM %s WHERE %s", table, test.c_str() ); + + PGresult* result = PQexec( getThreadConn(), query.c_str() ); + assert( 1 == PQntuples( result ) ); + int count = atoi( PQgetvalue( result, 0, 0 ) ); + PQclear( result ); + logf( XW_LOGINFO, "%s(%s)=>%d", __func__, query.c_str(), count ); + return count; +} + static void formatParams( char* paramValues[], int nParams, const char* fmt, char* buf, int bufLen, ... ) diff --git a/xwords4/relay/dbmgr.h b/xwords4/relay/dbmgr.h index dbf1eb02d..ce3a0abc1 100644 --- a/xwords4/relay/dbmgr.h +++ b/xwords4/relay/dbmgr.h @@ -62,6 +62,7 @@ class DBMgr { bool AllDevsAckd( const char* const connName ); DevIDRelay RegisterDevice( const DevID* host ); + bool updateDevice( DevIDRelay relayID, bool check ); HostID AddDevice( const char* const connName, HostID curID, int clientVersion, int nToAdd, unsigned short seed, const AddrInfo* addr, @@ -98,13 +99,20 @@ class DBMgr { /* message storage -- different DB */ int CountStoredMessages( const char* const connName ); int CountStoredMessages( const char* const connName, int hid ); + int CountStoredMessages( DevIDRelay relayID ); void StoreMessage( const char* const connName, int hid, const unsigned char* const buf, int len ); + void GetStoredMessageIDs( DevIDRelay relayID, vector& ids ); + bool GetStoredMessage( const char* const connName, int hid, unsigned char* buf, size_t* buflen, int* msgID ); bool GetNthStoredMessage( const char* const connName, int hid, int nn, unsigned char* buf, size_t* buflen, int* msgID ); + bool GetStoredMessage( int msgID, unsigned char* buf, size_t* buflen, + AddrInfo::ClientToken* token ); + void RemoveStoredMessages( const int* msgID, int nMsgIDs ); + void RemoveStoredMessages( vector& ids ); private: DBMgr(); @@ -113,6 +121,8 @@ class DBMgr { void readArray( const char* const connName, int arr[] ); DevIDRelay getDevID( const char* connName, int hid ); DevIDRelay getDevID( const DevID* devID ); + int getCountWhere( const char* table, string& test ); + void RemoveStoredMessages( string& msgIDs ); PGconn* getThreadConn( void ); diff --git a/xwords4/relay/states.cpp b/xwords4/relay/states.cpp index f133edb10..ab1ffe97e 100644 --- a/xwords4/relay/states.cpp +++ b/xwords4/relay/states.cpp @@ -79,6 +79,11 @@ static StateTable g_stateTable[] = { { XWS_WAITMORE, XWE_PROXYMSG, XWA_PROXYMSG, XWS_SAME }, { XWS_ALLCONND, XWE_PROXYMSG, XWA_PROXYMSG, XWS_SAME }, +{ XWS_EMPTY, XWE_TRYTELL, XWA_TRYTELL, XWS_SAME }, +{ XWS_WAITMORE, XWE_TRYTELL, XWA_TRYTELL, XWS_SAME }, +{ XWS_ALLCONND, XWE_TRYTELL, XWA_TRYTELL, XWS_SAME }, + + /* { XWS_WAITMORE, XWE_GAMEFULL, XWA_SENDALLHERE, XWS_ALLCONND }, */ /* { XWS_WAITMORE, XWE_CHECKFULL, XWA_, XWS_WAITMORE }, */ /* { XWS_INITED, XWE_DEVCONNECT, XWA_SEND_NO_ROOM, XWS_DEAD }, */ @@ -229,6 +234,7 @@ eventString( XW_RELAY_EVENT evt ) CASESTR(XWE_RECONNECT); CASESTR(XWE_GOTONEACK); CASESTR(XWE_PROXYMSG); + CASESTR(XWE_TRYTELL); CASESTR(XWE_GOTLASTACK); CASESTR(XWE_ACKTIMEOUT); CASESTR(XWE_DISCONN); @@ -276,6 +282,7 @@ actString( XW_RELAY_ACTION act ) CASESTR(XWA_SNDALLHERE_2); CASESTR(XWA_FWD); CASESTR(XWA_PROXYMSG); + CASESTR(XWA_TRYTELL); CASESTR(XWA_NOTEHEART); CASESTR(XWA_TIMERDISCONN); CASESTR(XWA_DISCONNECT); diff --git a/xwords4/relay/states.h b/xwords4/relay/states.h index 2171a54eb..7f86b1d89 100644 --- a/xwords4/relay/states.h +++ b/xwords4/relay/states.h @@ -86,6 +86,7 @@ typedef enum { this object */ ,XWE_PROXYMSG /* msg when game may not be connected */ + ,XWE_TRYTELL /* tell the addressee to check for stored messages */ ,XWE_GOTONEACK ,XWE_GOTLASTACK @@ -147,6 +148,8 @@ typedef enum { ,XWA_PROXYMSG /* out-of-band message */ + ,XWA_TRYTELL /* Tell the addresses to check for messages */ + ,XWA_NOTEHEART /* Record heartbeat received */ ,XWA_NOTE_EMPTY /* No sockets left; check if can delete */ diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index faf99ba15..aa98d48f0 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -207,6 +207,20 @@ parseRelayID( const unsigned char** const inp, const unsigned char* const end, return ok; } +static bool +getNetLong( const unsigned char** bufpp, const unsigned char* end, + uint32_t* out ) +{ + uint32_t tmp; + bool ok = *bufpp + sizeof(tmp) <= end; + if ( ok ) { + memcpy( &tmp, *bufpp, sizeof(tmp) ); + *bufpp += sizeof(tmp); + *out = ntohl( tmp ); + } + return ok; +} /* getNetShort */ + static bool getNetShort( const unsigned char** bufpp, const unsigned char* end, unsigned short* out ) @@ -343,6 +357,7 @@ send_via_udp( int socket, const struct sockaddr *dest_addr, { ssize_t nSent = sendto( socket, buf, buflen, 0, /* flags */ dest_addr, sizeof(*dest_addr) ); + logf( XW_LOGINFO, "%s()=>%d", __func__, nSent ); return nSent; } @@ -370,8 +385,8 @@ send_with_length_unsafe( const AddrInfo* addr, const unsigned char* buf, AddrInfo::ClientToken clientToken = addr->clientToken(); assert( 0 != clientToken ); unsigned char tmpbuf[1 + 1 + sizeof(clientToken) + bufLen]; - tmpbuf[0] = XWREG_PROTO_VERSION; - tmpbuf[1] = XWRREG_MSG; + tmpbuf[0] = XWPDEV_PROTO_VERSION; + tmpbuf[1] = XWPDEV_MSG; clientToken = htonl(clientToken); memcpy( &tmpbuf[2], &clientToken, sizeof(clientToken) ); memcpy( &tmpbuf[2 + sizeof(clientToken)], buf, bufLen ); @@ -392,6 +407,23 @@ send_with_length_unsafe( const AddrInfo* addr, const unsigned char* buf, return ok; } /* send_with_length_unsafe */ +void +send_havemsgs( const AddrInfo* addr ) +{ + logf( XW_LOGINFO, "%s()", __func__ ); + int socket = addr->socket(); + if ( -1 == socket ) { + socket = g_udpsock; + } + + AddrInfo::ClientToken clientToken = htonl(addr->clientToken()); + unsigned char tmpbuf[1 + 1 + sizeof(clientToken)]; + tmpbuf[0] = XWPDEV_PROTO_VERSION; + tmpbuf[1] = XWPDEV_HAVEMSGS; + memcpy( &tmpbuf[2], &clientToken, sizeof(clientToken) ); + + send_via_udp( socket, addr->sockaddr(), tmpbuf, sizeof(tmpbuf) ); +} /* A CONNECT message from a device gives us the hostID and socket we'll * associate with one participant in a relayed session. We'll store this @@ -944,6 +976,31 @@ log_hex( const unsigned char* memp, int len, const char* tag ) } } // log_hex +static bool +handlePutMessage( SafeCref& scr, HostID hid, const AddrInfo* addr, + unsigned short len, const unsigned char** bufp, + const unsigned char* end ) +{ + logf( XW_LOGINFO, "%s()", __func__ ); + bool success = false; + const unsigned char* start = *bufp; + HostID src; + HostID dest; + XWRELAY_Cmd cmd; + // sanity check that cmd and hostids are there + if ( getNetByte( bufp, end, &cmd ) + && getNetByte( bufp, end, &src ) + && getNetByte( bufp, end, &dest ) ) { + assert( cmd == XWRELAY_MSG_TORELAY_NOCONN ); + assert( hid == dest ); + scr.PutMsg( src, addr, dest, start, len ); + *bufp = start + len; + success = true; + } + logf( XW_LOGINFO, "%s()=>%d", __func__, success ); + return success; +} + static void handleProxyMsgs( int sock, const AddrInfo* addr, const unsigned char* bufp, const unsigned char* end ) @@ -975,20 +1032,10 @@ handleProxyMsgs( int sock, const AddrInfo* addr, const unsigned char* bufp, unsigned short nMsgs; if ( getNetShort( &bufp, end, &nMsgs ) ) { SafeCref scr( connName ); - while ( nMsgs-- > 0 ) { + while ( scr.IsValid() && nMsgs-- > 0 ) { unsigned short len; - HostID src; - HostID dest; - XWRELAY_Cmd cmd; if ( getNetShort( &bufp, end, &len ) ) { - const unsigned char* start = bufp; - if ( getNetByte( &bufp, end, &cmd ) - && getNetByte( &bufp, end, &src ) - && getNetByte( &bufp, end, &dest ) ) { - assert( cmd == XWRELAY_MSG_TORELAY_NOCONN ); - assert( hid == dest ); - scr.PutMsg( src, addr, dest, start, len ); - bufp = start + len; + if ( handlePutMessage( scr, hid, addr, len, &bufp, end ) ) { continue; } } @@ -1077,30 +1124,106 @@ handle_proxy_packet( unsigned char* buf, int len, const AddrInfo* addr ) } } /* handle_proxy_packet */ +static short +addRegID( unsigned char* ptr, DevIDRelay relayID ) +{ + short used = 0; + char idbuf[9]; + int idLen = snprintf( idbuf, sizeof(idbuf), "%.8X", relayID ); + short lenNBO = htons(idLen); + memcpy( &ptr[used], &lenNBO, sizeof(lenNBO) ); + used += sizeof(lenNBO); + memcpy( &ptr[used], idbuf, idLen ); + used += idLen; + return used; +} + static void registerDevice( const DevID* devID, const AddrInfo::AddrUnion* saddr ) { DevIDRelay relayID; - if ( ID_TYPE_RELAY != devID->m_devIDType ) { // known to us; just update the time - relayID = DBMgr::Get()->RegisterDevice( devID ); - if ( ID_TYPE_NONE != relayID ) { + DBMgr* dbMgr = DBMgr::Get(); + short indx = 0; + unsigned char buf[32]; + if ( ID_TYPE_RELAY == devID->m_devIDType ) { // known to us; just update the time + relayID = devID->asRelayID(); + if ( dbMgr->updateDevice( relayID, true ) ) { + int nMsgs = dbMgr->CountStoredMessages( relayID ); + if ( 0 < nMsgs ) { + AddrInfo addr( -1, 0, saddr ); + send_havemsgs( &addr ); + } + } else { + relayID = DBMgr::DEVID_NONE; + + buf[indx++] = XWPDEV_PROTO_VERSION; + buf[indx++] = XWPDEV_BADREG; + indx += addRegID( &buf[indx], relayID ); + send_via_udp( g_udpsock, &saddr->addr, buf, indx ); + } + } else { + relayID = dbMgr->RegisterDevice( devID ); + if ( DBMgr::DEVID_NONE != relayID ) { // send it back to the device - char idbuf[9]; - int len = snprintf( idbuf, sizeof(idbuf), "%.8X", relayID ); - logf( XW_LOGERROR, "%s: len(%s) => %d", __func__, idbuf, len ); - unsigned char buf[1 + 1 + 2 + len]; - buf[0] = XWREG_PROTO_VERSION; - buf[1] = XWRREG_REGRSP; - short lenNBO = htons(len); - memcpy( &buf[2], &lenNBO, sizeof(lenNBO)); - memcpy( &buf[4], idbuf, len ); + buf[indx++] = XWPDEV_PROTO_VERSION; + buf[indx++] = XWPDEV_REGRSP; + indx += addRegID( &buf[indx], relayID ); send_via_udp( g_udpsock, &saddr->addr, buf, sizeof(buf) ); } - } else { - relayID = devID->asRelayID(); } + // Now let's map the address to the devid for future sending purposes. - DevMgr::Get()->Remember( relayID, saddr ); + if ( DBMgr::DEVID_NONE != relayID ) { + DevMgr::Get()->Remember( relayID, saddr ); + } +} + +static void +retrieveMessages( DevID& devID, const AddrInfo::AddrUnion* saddr ) +{ + logf( XW_LOGINFO, "%s()", __func__ ); + DBMgr* dbMgr = DBMgr::Get(); + vector ids; + vector sentIDs; + dbMgr->GetStoredMessageIDs( devID.asRelayID(), ids ); + vector::const_iterator iter; + for ( iter = ids.begin(); iter != ids.end(); ++iter ) { + unsigned char buf[MAX_MSG_LEN]; + size_t buflen = sizeof(buf); + AddrInfo::ClientToken clientToken; + if ( dbMgr->GetStoredMessage( *iter, buf, &buflen, &clientToken ) ) { + AddrInfo addr( -1, clientToken, saddr ); + if ( ! send_with_length_unsafe( &addr, buf, buflen ) ) { + break; + } + sentIDs.push_back( *iter ); + } + } + dbMgr->RemoveStoredMessages( sentIDs ); +} + +static const char* +msgToStr( XWRelayReg msg ) +{ + const char* str; +# define CASE_STR(c) case c: str = #c; break + switch( msg ) { + CASE_STR(XWPDEV_REG); + CASE_STR(XWPDEV_REGRSP); + CASE_STR(XWPDEV_PING); + CASE_STR(XWPDEV_HAVEMSGS); + CASE_STR(XWPDEV_RQSTMSGS); + CASE_STR(XWPDEV_MSG); + CASE_STR(XWPDEV_MSGNOCONN); + CASE_STR(XWPDEV_MSGRSP); + CASE_STR(XWPDEV_BADREG); + default: + assert(0); + break; + } +# undef CASE_STR + return str; + } static void @@ -1110,12 +1233,13 @@ udp_thread_proc( UdpThreadClosure* utc ) const unsigned char* end = ptr + utc->len(); unsigned char proto = *ptr++; - if ( XWREG_PROTO_VERSION != 0 ) { + if ( XWPDEV_PROTO_VERSION != 0 ) { logf( XW_LOGERROR, "unexpected proto %d", __func__, (int) proto ); } else { - int msg = *ptr++; + XWRelayReg msg = (XWRelayReg)*ptr++; + logf( XW_LOGINFO, "%s(msg=%s)", __func__, msgToStr( msg ) ); switch( msg ) { - case XWRREG_REG: { + case XWPDEV_REG: { DevIDType typ = (DevIDType)*ptr++; unsigned short idLen; if ( !getNetShort( &ptr, end, &idLen ) ) { @@ -1131,7 +1255,7 @@ udp_thread_proc( UdpThreadClosure* utc ) registerDevice( &devID, utc->saddr() ); } break; - case XWRREG_MSG: { + case XWPDEV_MSG: { AddrInfo::ClientToken clientToken; memcpy( &clientToken, ptr, sizeof(clientToken) ); ptr += sizeof(clientToken); @@ -1142,8 +1266,48 @@ udp_thread_proc( UdpThreadClosure* utc ) } else { logf( XW_LOGERROR, "%s: dropping packet with token of 0" ); } - } break; + } + case XWPDEV_MSGNOCONN: { + AddrInfo::ClientToken clientToken; + if ( getNetLong( &ptr, end, &clientToken ) ) { + HostID hid; + char connName[MAX_CONNNAME_LEN+1]; + if ( !parseRelayID( &ptr, end, connName, + sizeof( connName ), &hid ) ) { + logf( XW_LOGERROR, "parse failed!!!" ); + break; + } + SafeCref scr( connName ); + if ( scr.IsValid() ) { + AddrInfo addr( g_udpsock, clientToken, utc->saddr() ); + handlePutMessage( scr, hid, &addr, end - ptr, &ptr, end ); + assert( ptr == end ); // DON'T CHECK THIS IN!!! + } else { + logf( XW_LOGERROR, "%s: invalid scr for %s", __func__, connName ); + } + } else { + logf( XW_LOGERROR, "no clientToken found!!!" ); + } + break; + } + + case XWPDEV_RQSTMSGS: { + unsigned short idLen; + if ( !getNetShort( &ptr, end, &idLen ) ) { + break; + } + if ( end - ptr > idLen ) { + logf( XW_LOGERROR, "full devID not received" ); + break; + } + DevID devID( ID_TYPE_RELAY ); + devID.m_devIDString.append( (const char*)ptr, idLen ); + ptr += idLen; + retrieveMessages( devID, utc->saddr() ); + break; + } + default: logf( XW_LOGERROR, "%s: unexpected msg %d", __func__, msg ); } @@ -1155,7 +1319,6 @@ udp_thread_proc( UdpThreadClosure* utc ) static void handle_udp_packet( int udpsock ) { - logf( XW_LOGINFO, "%s()", __func__ ); unsigned char buf[512]; AddrInfo::AddrUnion saddr; memset( &saddr, 0, sizeof(saddr) ); diff --git a/xwords4/relay/xwrelay.h b/xwords4/relay/xwrelay.h index 2f30c5068..c06682295 100644 --- a/xwords4/relay/xwrelay.h +++ b/xwords4/relay/xwrelay.h @@ -28,30 +28,48 @@ #define FLAGS_SERVER_BIT 0x01 /* message types for the udp-based per-device (not per-game) protocol */ -#define XWREG_PROTO_VERSION 0 +#define XWPDEV_PROTO_VERSION 0 #ifndef CANT_DO_TYPEDEF typedef #endif -enum { XWRREG_NONE /* 0 is an illegal value */ - ,XWRREG_REG /* dev->relay: device registers self and +enum { XWPDEV_NONE /* 0 is an illegal value */ + ,XWPDEV_REG /* dev->relay: device registers self and self-selected (e.g. gcm) or assigned devid format: proto: 1; this enum: 1; idType: 1, idLen: 2, id: */ - ,XWRREG_REGRSP /* relay->device: if non-relay-assigned devid + ,XWPDEV_REGRSP /* relay->device: if non-relay-assigned devid type was given, this gives the relay-assigned one to be used from now on. format: proto: 1, this enum: 1, idLen: 2, id: */ - ,XWRREG_PING /* device->relay: keep the UDP connection + ,XWPDEV_PING /* device->relay: keep the UDP connection open. format: proto: 1, this enum: 1. */ - ,XWRREG_MSG /* dev->relay and relay->dev: norm: a message from a game to + ,XWPDEV_HAVEMSGS /* Relay->device: check messages for this + game. format: proto: 1, this enum: 1; clientToken: 4 */ + + ,XWPDEV_RQSTMSGS /* device->relay: got any messages for me? + format: proto: 1, this enum 1, devID: 4 [, clientToken: 4] + */ + + ,XWPDEV_MSG /* dev->relay and relay->dev: norm: a message from a game to the relay format: proto: 1, this enum: 1, clientToken: 4, message*/ - ,XWRREG_MSGRSP /* relay->dev: conveys error on receipt of XWRREG_MSG */ + ,XWPDEV_MSGNOCONN /* dev->relay in the proxy format that + includes relayID (connname:hid) and seems + to be reserved for relay FWD messages. + format: proto: 1, this enum: 1, + clientToken: 4; -terminated-connname: + varies, message: varies */ + + ,XWPDEV_MSGRSP /* relay->dev: conveys error on receipt of XWPDEV_MSG */ + + ,XWPDEV_BADREG /* relay->dev. You sent me a relayID via + XWPDEV_REG but I've never heard of it */ + } #ifndef CANT_DO_TYPEDEF XWRelayReg diff --git a/xwords4/relay/xwrelay.sh b/xwords4/relay/xwrelay.sh index f39efbdac..52da03349 100755 --- a/xwords4/relay/xwrelay.sh +++ b/xwords4/relay/xwrelay.sh @@ -67,6 +67,7 @@ CREATE TABLE msgs ( id SERIAL ,connName VARCHAR(64) ,hid INTEGER +,token INTEGER ,ctime TIMESTAMP DEFAULT CURRENT_TIMESTAMP ,stime TIMESTAMP DEFAULT NULL ,devid INTEGER @@ -82,6 +83,7 @@ id INTEGER UNIQUE PRIMARY KEY ,devType INTEGER ,devid TEXT ,ctime TIMESTAMP DEFAULT CURRENT_TIMESTAMP +,mtime TIMESTAMP ,unreg BOOLEAN DEFAULT FALSE ); EOF diff --git a/xwords4/relay/xwrelay_priv.h b/xwords4/relay/xwrelay_priv.h index 51d8da001..84ae9d696 100644 --- a/xwords4/relay/xwrelay_priv.h +++ b/xwords4/relay/xwrelay_priv.h @@ -46,6 +46,7 @@ void logf( XW_LogLevel level, const char* format, ... ); void denyConnection( const AddrInfo* addr, XWREASON err ); bool send_with_length_unsafe( const AddrInfo* addr, const unsigned char* buf, size_t bufLen ); +void send_havemsgs( const AddrInfo* addr ); time_t uptime(void); From 1bb9f9b1bf940a66d59773dd42027291173de432 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 19 Jan 2013 14:37:49 -0800 Subject: [PATCH 029/116] handle sending of messages generated by games opened only to receive messages from relay. And handle report that relayID is bad. --- xwords4/linux/gamesdb.c | 29 +++++-- xwords4/linux/gamesdb.h | 5 +- xwords4/linux/gtkboard.c | 9 ++- xwords4/linux/gtkmain.c | 74 +++++++++++++----- xwords4/linux/relaycon.c | 158 +++++++++++++++++++++++++++------------ xwords4/linux/relaycon.h | 9 ++- 6 files changed, 203 insertions(+), 81 deletions(-) diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c index 5e6bd9fa2..c0f217e10 100644 --- a/xwords4/linux/gamesdb.c +++ b/xwords4/linux/gamesdb.c @@ -208,12 +208,14 @@ loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ) int result = sqlite3_prepare_v2( pDb, buf, -1, &ppStmt, NULL ); XP_ASSERT( SQLITE_OK == result ); result = sqlite3_step( ppStmt ); - XP_ASSERT( SQLITE_ROW == result ); - const void* ptr = sqlite3_column_blob( ppStmt, 0 ); - int size = sqlite3_column_bytes( ppStmt, 0 ); - stream_putBytes( stream, ptr, size ); + XP_Bool success = SQLITE_ROW == result; + if ( success ) { + const void* ptr = sqlite3_column_blob( ppStmt, 0 ); + int size = sqlite3_column_bytes( ppStmt, 0 ); + stream_putBytes( stream, ptr, size ); + } sqlite3_finalize( ppStmt ); - return XP_TRUE; + return success; } void @@ -230,7 +232,7 @@ deleteGame( sqlite3* pDb, sqlite3_int64 rowid ) } void -store( sqlite3* pDb, const gchar* key, const gchar* value ) +db_store( sqlite3* pDb, const gchar* key, const gchar* value ) { char buf[256]; snprintf( buf, sizeof(buf), @@ -245,7 +247,7 @@ store( sqlite3* pDb, const gchar* key, const gchar* value ) } void -fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen ) +db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen ) { char query[256]; snprintf( query, sizeof(query), @@ -262,6 +264,19 @@ fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen ) sqlite3_finalize( ppStmt ); } +void +db_remove( sqlite3* pDb, const gchar* key ) +{ + char query[256]; + snprintf( query, sizeof(query), "DELETE FROM pairs WHERE key = '%s'", key ); + sqlite3_stmt *ppStmt; + int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( ppStmt ); + XP_ASSERT( SQLITE_DONE == result ); + sqlite3_finalize( ppStmt ); +} + static void getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, int len ) { diff --git a/xwords4/linux/gamesdb.h b/xwords4/linux/gamesdb.h index 6418ccc3f..97fccee8f 100644 --- a/xwords4/linux/gamesdb.h +++ b/xwords4/linux/gamesdb.h @@ -51,7 +51,8 @@ void deleteGame( sqlite3* pDb, sqlite3_int64 rowid ); #define KEY_RDEVID "RDEVID" -void store( sqlite3* dbp, const gchar* key, const gchar* value ); -void fetch( sqlite3* dbp, const gchar* key, gchar* buf, gint buflen ); +void db_store( sqlite3* dbp, const gchar* key, const gchar* value ); +void db_fetch( sqlite3* dbp, const gchar* key, gchar* buf, gint buflen ); +void db_remove( sqlite3* dbp, const gchar* key ); #endif diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 1421c6dfc..1b7457a38 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -66,6 +66,7 @@ #include "memstream.h" #include "filestream.h" #include "gamesdb.h" +#include "relaycon.h" /* static guint gtkSetupClientSocket( GtkGameGlobals* globals, int sock ); */ static void setCtrlsForTray( GtkGameGlobals* globals ); @@ -392,13 +393,15 @@ relay_error_gtk( void* closure, XWREASON relayErr ) } static XP_Bool -relay_sendNoConn_gtk( const XP_U8* XP_UNUSED(msg), XP_U16 XP_UNUSED(len), - const XP_UCHAR* XP_UNUSED(relayID), void* closure ) +relay_sendNoConn_gtk( const XP_U8* msg, XP_U16 len, const XP_UCHAR* relayID, + void* closure ) { GtkGameGlobals* globals = (GtkGameGlobals*)closure; XP_Bool success = XP_FALSE; if ( !!globals->cGlobals.pDb && !globals->draw ) { - XP_ASSERT( 0 ); /* implement me!!! */ + XP_S16 nSent = relaycon_sendnoconn( globals->cGlobals.params, msg, len, + relayID, globals->cGlobals.selRow ); + success = nSent == len; } return success; } /* relay_sendNoConn_gtk */ diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index b68e2d6b5..62ddc65aa 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -30,6 +30,7 @@ static void onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid, XP_Bool isNew ); static void updateButtons( GtkAppGlobals* apg ); +static void sendRelayReg( GtkAppGlobals* apg ); static void recordOpened( GtkAppGlobals* apg, GtkGameGlobals* globals ) @@ -406,12 +407,40 @@ gtkGotBuf( void* closure, XP_U8* buf, XP_U16 len ) } } +static gint +requestMsgs( gpointer data ) +{ + GtkAppGlobals* apg = (GtkAppGlobals*)data; + XP_UCHAR devIDBuf[64] = {0}; + db_fetch( apg->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); + if ( '\0' != devIDBuf[0] ) { + relaycon_requestMsgs( apg->params, devIDBuf ); + } else { + XP_LOGF( "%s: not requesting messages as don't have relay id", __func__ ); + } + return 0; /* don't run again */ +} + +static void +gtkNoticeRcvd( void* closure, XP_U32 XP_UNUSED(gameToken) ) +{ + LOG_FUNC(); + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + (void)g_idle_add( requestMsgs, apg ); +} + static void gtkDevIDChanged( void* closure, const XP_UCHAR* devID ) { GtkAppGlobals* apg = (GtkAppGlobals*)closure; - XP_LOGF( "%s(devID=%s)", __func__, devID ); - store( apg->pDb, KEY_RDEVID, devID ); + if ( !!devID ) { + XP_LOGF( "%s(devID=%s)", __func__, devID ); + db_store( apg->pDb, KEY_RDEVID, devID ); + } else { + XP_LOGF( "%s: bad relayid", __func__ ); + db_remove( apg->pDb, KEY_RDEVID ); + sendRelayReg( apg ); + } } void @@ -445,6 +474,27 @@ handle_sigintterm( int XP_UNUSED(sig) ) handle_destroy( NULL, g_globals_for_signal ); } +static void +sendRelayReg( GtkAppGlobals* apg ) +{ + LaunchParams* params = apg->params; + XP_UCHAR devIDBuf[64] = {0}; + XP_UCHAR* devID; + DevIDType typ = ID_TYPE_RELAY; + if ( !!params->rDevID ) { + devID = params->rDevID; + } else { + db_fetch( apg->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); + if ( '\0' != devIDBuf[0] ) { + devID = devIDBuf; + } else { + devID = params->devID; + typ = ID_TYPE_LINUX; + } + } + relaycon_reg( params, devID, typ ); +} + int gtkmain( LaunchParams* params ) { @@ -463,28 +513,14 @@ gtkmain( LaunchParams* params ) RelayConnProcs procs = { .msgReceived = gtkGotBuf, + .msgNoticeReceived = gtkNoticeRcvd, .devIDChanged = gtkDevIDChanged, }; - XP_UCHAR devIDBuf[64] = {0}; - XP_UCHAR* devID; - DevIDType typ = ID_TYPE_RELAY; - if ( !!params->rDevID ) { - devID = params->rDevID; - } else { - fetch( apg.pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); - if ( '\0' != devIDBuf[0] ) { - devID = devIDBuf; - } else { - devID = params->devID; - typ = ID_TYPE_LINUX; - } - } - relaycon_init( params, &procs, &apg, params->connInfo.relay.relayName, - params->connInfo.relay.defaultSendPort, - devID, typ ); + params->connInfo.relay.defaultSendPort ); + sendRelayReg( &apg ); (void)makeGamesWindow( &apg ); gtk_main(); diff --git a/xwords4/linux/relaycon.c b/xwords4/linux/relaycon.c index f397e65a7..eb5422f83 100644 --- a/xwords4/linux/relaycon.c +++ b/xwords4/linux/relaycon.c @@ -27,18 +27,18 @@ typedef struct _RelayConStorage { int socket; RelayConnProcs procs; void* procsClosure; + struct sockaddr_in saddr; } RelayConStorage; static RelayConStorage* getStorage( LaunchParams* params ); -static void addressToServer( struct sockaddr_in* to, const CommsAddrRec* addr ); -static XP_U32 addrForHost( const CommsAddrRec* addr ); static XP_U32 hostNameToIP( const XP_UCHAR* name ); static void relaycon_receive( void* closure, int socket ); +static ssize_t sendIt( RelayConStorage* storage, const XP_U8* msgbuf, XP_U16 len ); +static size_t addStrWithLength( XP_U8* buf, XP_U8* end, const XP_UCHAR* str ); void relaycon_init( LaunchParams* params, const RelayConnProcs* procs, - void* procsClosure, const char* host, int port, - const XP_UCHAR* devID, DevIDType typ ) + void* procsClosure, const char* host, int port ) { XP_ASSERT( !params->relayConStorage ); RelayConStorage* storage = getStorage( params ); @@ -49,50 +49,100 @@ relaycon_init( LaunchParams* params, const RelayConnProcs* procs, (*params->socketChanged)( params->socketChangedClosure, storage->socket, -1, relaycon_receive, params ); + XP_MEMSET( &storage->saddr, 0, sizeof(storage->saddr) ); + storage->saddr.sin_family = PF_INET; + storage->saddr.sin_addr.s_addr = htonl( hostNameToIP(host) ); + storage->saddr.sin_port = htons(port); +} + +void +relaycon_reg( LaunchParams* params, const XP_UCHAR* devID, DevIDType typ ) +{ + LOG_FUNC(); + XP_U8 tmpbuf[32]; + int indx = 0; + + RelayConStorage* storage = getStorage( params ); XP_ASSERT( !!devID ); XP_U16 idLen = XP_STRLEN( devID ); XP_U16 lenNBO = XP_HTONS( idLen ); - XP_U8 tmpbuf[1 + 1 + 1 + sizeof(lenNBO) + idLen]; - tmpbuf[0] = XWREG_PROTO_VERSION; - tmpbuf[1] = XWRREG_REG; - tmpbuf[2] = typ; - XP_MEMCPY( &tmpbuf[3], &lenNBO, sizeof(lenNBO) ); - XP_MEMCPY( &tmpbuf[5], devID, idLen ); + tmpbuf[indx++] = XWPDEV_PROTO_VERSION; + tmpbuf[indx++] = XWPDEV_REG; + tmpbuf[indx++] = typ; + XP_MEMCPY( &tmpbuf[indx], &lenNBO, sizeof(lenNBO) ); + indx += sizeof(lenNBO); + XP_MEMCPY( &tmpbuf[indx], devID, idLen ); + indx += idLen; - struct sockaddr_in to = {0}; - to.sin_family = PF_INET; - to.sin_addr.s_addr = htonl( hostNameToIP(host) ); - to.sin_port = htons(port); - - (void)sendto( storage->socket, tmpbuf, sizeof(tmpbuf), 0, /* flags */ - (struct sockaddr*)&to, sizeof(to) ); + sendIt( storage, tmpbuf, indx ); } XP_S16 relaycon_send( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, - XP_U32 gameToken, const CommsAddrRec* addrRec ) + XP_U32 gameToken, const CommsAddrRec* XP_UNUSED(addrRec) ) { ssize_t nSent = -1; RelayConStorage* storage = getStorage( params ); - struct sockaddr_in to = {0}; - addressToServer( &to, addrRec ); - XP_U8 tmpbuf[1 + 1 + sizeof(gameToken) + buflen]; - tmpbuf[0] = XWREG_PROTO_VERSION; - tmpbuf[1] = XWRREG_MSG; + tmpbuf[0] = XWPDEV_PROTO_VERSION; + tmpbuf[1] = XWPDEV_MSG; XP_U32 inNBO = htonl(gameToken); XP_MEMCPY( &tmpbuf[2], &inNBO, sizeof(inNBO) ); XP_MEMCPY( &tmpbuf[1 + 1 + sizeof(gameToken)], buf, buflen ); - nSent = sendto( storage->socket, tmpbuf, sizeof(tmpbuf), 0, /* flags */ - (struct sockaddr*)&to, sizeof(to) ); - if ( 1 + 1 + sizeof(gameToken) < nSent ) { - nSent -= 1 + 1 + sizeof(gameToken); + nSent = sendIt( storage, tmpbuf, sizeof(tmpbuf) ); + if ( nSent > buflen ) { + nSent = buflen; } LOG_RETURNF( "%d", nSent ); return nSent; } +XP_S16 +relaycon_sendnoconn( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, + const XP_UCHAR* relayID, XP_U32 gameToken ) +{ + XP_LOGF( "%s(relayID=%s)", __func__, relayID ); + XP_U16 indx = 0; + ssize_t nSent = -1; + RelayConStorage* storage = getStorage( params ); + + XP_U16 idLen = XP_STRLEN( relayID ); + XP_U8 tmpbuf[1 + 1 + + 1 + idLen + + sizeof(gameToken) + buflen]; + tmpbuf[indx++] = XWPDEV_PROTO_VERSION; + tmpbuf[indx++] = XWPDEV_MSGNOCONN; + gameToken = htonl( gameToken ); + XP_MEMCPY( &tmpbuf[indx], &gameToken, sizeof(gameToken) ); + indx += sizeof(gameToken); + XP_MEMCPY( &tmpbuf[indx], relayID, idLen ); + indx += idLen; + tmpbuf[indx++] = '\n'; + XP_MEMCPY( &tmpbuf[indx], buf, buflen ); + nSent = sendIt( storage, tmpbuf, sizeof(tmpbuf) ); + if ( nSent > buflen ) { + nSent = buflen; + } + LOG_RETURNF( "%d", nSent ); + return nSent; +} + +void +relaycon_requestMsgs( LaunchParams* params, const XP_UCHAR* devID ) +{ + XP_LOGF( "%s(devID=%s)", __func__, devID ); + RelayConStorage* storage = getStorage( params ); + + XP_U8 tmpbuf[128]; + int indx = 0; + tmpbuf[indx++] = XWPDEV_PROTO_VERSION; + tmpbuf[indx++] = XWPDEV_RQSTMSGS; + indx += addStrWithLength( &tmpbuf[indx], tmpbuf + sizeof(tmpbuf), devID ); + + sendIt( storage, tmpbuf, indx ); +} + static void relaycon_receive( void* closure, int socket ) { @@ -111,10 +161,10 @@ relaycon_receive( void* closure, int socket ) if ( 0 <= nRead ) { XP_U8* ptr = buf; const XP_U8* end = buf + nRead; - XP_ASSERT( XWREG_PROTO_VERSION == *ptr++ ); + XP_ASSERT( XWPDEV_PROTO_VERSION == *ptr++ ); XWRelayReg cmd = *ptr++; switch( cmd ) { - case XWRREG_REGRSP: { + case XWPDEV_REGRSP: { XP_U16 len; XP_MEMCPY( &len, ptr, sizeof(len) ); len = ntohs( len ); @@ -125,10 +175,20 @@ relaycon_receive( void* closure, int socket ) (*storage->procs.devIDChanged)( storage->procsClosure, devID ); } break; - case XWRREG_MSG: + case XWPDEV_MSG: (*storage->procs.msgReceived)( storage->procsClosure, ptr, end - ptr ); break; + case XWPDEV_BADREG: + (*storage->procs.devIDChanged)( storage->procsClosure, NULL ); + break; + case XWPDEV_HAVEMSGS: { + XP_U32 gameToken; + XP_MEMCPY( &gameToken, ptr, sizeof(gameToken) ); + ptr += sizeof( gameToken ); + (*storage->procs.msgNoticeReceived)( storage->procsClosure, ntohl(gameToken) ); + break; + } default: XP_LOGF( "%s: Unexpected cmd %d", __func__, cmd ); XP_ASSERT( 0 ); @@ -157,24 +217,6 @@ getStorage( LaunchParams* params ) return storage; } -static void -addressToServer( struct sockaddr_in* to, const CommsAddrRec* addr ) -{ - to->sin_family = PF_INET; - to->sin_addr.s_addr = htonl( addrForHost(addr) ); - to->sin_port = htons(addr->u.ip_relay.port); -} - -static XP_U32 -addrForHost( const CommsAddrRec* addr ) -{ - XP_U32 ip = addr->u.ip_relay.ipAddr; - if ( 0L == ip ) { - ip = hostNameToIP( addr->u.ip_relay.hostName ); - } - return ip; -} /* addrForHost */ - static XP_U32 hostNameToIP( const XP_UCHAR* name ) { @@ -191,3 +233,23 @@ hostNameToIP( const XP_UCHAR* name ) XP_LOGF( "%s found %lx for %s", __func__, ip, name ); return ip; } + +static ssize_t +sendIt( RelayConStorage* storage, const XP_U8* msgbuf, XP_U16 len ) +{ + return sendto( storage->socket, msgbuf, len, 0, /* flags */ + (struct sockaddr*)&storage->saddr, sizeof(storage->saddr) ); +} + +static size_t +addStrWithLength( XP_U8* buf, XP_U8* end, const XP_UCHAR* str ) +{ + XP_U16 len = XP_STRLEN( str ); + if ( buf + len + sizeof(len) <= end ) { + XP_U16 lenNBO = htons( len ); + XP_MEMCPY( buf, &lenNBO, sizeof(lenNBO) ); + buf += sizeof(lenNBO); + XP_MEMCPY( buf, str, len ); + } + return len + sizeof(len); +} diff --git a/xwords4/linux/relaycon.h b/xwords4/linux/relaycon.h index 64c547580..962fe9fed 100644 --- a/xwords4/linux/relaycon.h +++ b/xwords4/linux/relaycon.h @@ -24,13 +24,18 @@ typedef struct _Procs { void (*msgReceived)( void* closure, XP_U8* buf, XP_U16 len ); + void (*msgNoticeReceived)( void* closure, XP_U32 gameToken ); void (*devIDChanged)( void* closure, const XP_UCHAR* devID ); } RelayConnProcs; void relaycon_init( LaunchParams* params, const RelayConnProcs* procs, - void* procsClosure, const char* host, int port, - const XP_UCHAR* devID, DevIDType typ ); + void* procsClosure, const char* host, int port ); +void relaycon_reg( LaunchParams* params, const XP_UCHAR* devID, DevIDType typ ); XP_S16 relaycon_send( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, XP_U32 gameID, const CommsAddrRec* addrRec ); +XP_S16 relaycon_sendnoconn( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, + const XP_UCHAR* relayID, XP_U32 gameToken ); +void relaycon_requestMsgs( LaunchParams* params, const XP_UCHAR* devID ); + void relaycon_cleanup( LaunchParams* params ); #endif From 65e9789c3f2d93c7b6ae257edecc5494880dd2c4 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 19 Jan 2013 15:17:51 -0800 Subject: [PATCH 030/116] add mainentance mode, triggered by a commandline flag, that does nothing but loop waiting for connections on the UDP socket and sending them back the error message provided on the commandline. --- xwords4/relay/xwrelay.cpp | 87 ++++++++++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 19 deletions(-) diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index aa98d48f0..9a354e0a0 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -736,6 +736,7 @@ usage( char* arg0 ) "\t-h (print this help)\\\n" "\t-i (file where next global id stored)\\\n" "\t-l (write logs here, not stderr)\\\n" + "\t-M (Put in maintenance mode, and return this string to all callers)\\\n" "\t-m (max number of simultaneous sockets to have open)\\\n" "\t-n (used in permID generation)\\\n" "\t-p (port to listen on)\\\n" @@ -1415,12 +1416,51 @@ enable_keepalive( int sock ) */ } +static void +maint_str_loop( int udpsock, const char* str ) +{ + assert( -1 != udpsock ); + int len = 1 + strlen(str); + + fd_set rfds; + for ( ; ; ) { + FD_ZERO(&rfds); + FD_SET( udpsock, &rfds ); + int retval = select( udpsock + 1, &rfds, NULL, NULL, NULL ); + if ( 0 > retval ) { + logf( XW_LOGERROR, "%s: select=>%d (errno=%d/%s)", __func__, retval, + errno, strerror(errno) ); + break; + } + if ( FD_ISSET( udpsock, &rfds ) ) { + unsigned char buf[512]; + AddrInfo::AddrUnion saddr; + memset( &saddr, 0, sizeof(saddr) ); + socklen_t fromlen = sizeof(saddr.addr_in); + + ssize_t nRead = recvfrom( udpsock, buf, sizeof(buf), 0 /*flags*/, + &saddr.addr, &fromlen ); + + if ( 2 <= nRead ) { + if ( buf[0] == XWPDEV_PROTO_VERSION && buf[1] == XWPDEV_REG ) { + // reply, reusing buffer + buf[1] = XWPDEV_MSGRSP; + memcpy( &buf[2], str, len ); + send_via_udp( udpsock, &saddr.addr, buf, 2 + len ); + } + } + } + + } + +} + int main( int argc, char** argv ) { int port = 0; int ctrlport = 0; - int udpport = 0; + int udpport = -1; #ifdef DO_HTTP int httpport = 0; const char* cssFile = NULL; @@ -1430,6 +1470,7 @@ main( int argc, char** argv ) const char* serverName = NULL; // const char* idFileName = NULL; const char* logFile = NULL; + const char* maint_str = NULL; bool doDaemon = true; bool doFork = true; @@ -1443,7 +1484,7 @@ main( int argc, char** argv ) first. */ for ( ; ; ) { - int opt = getopt(argc, argv, "h?c:p:m:n:f:l:t:s:u:w:" + int opt = getopt(argc, argv, "h?c:p:M:m:n:f:l:t:s:u:w:" "DF" ); if ( opt == -1 ) { @@ -1485,6 +1526,9 @@ main( int argc, char** argv ) case 'l': logFile = optarg; break; + case 'M': + maint_str = optarg; + break; case 'm': g_maxsocks = atoi( optarg ); break; @@ -1522,7 +1566,7 @@ main( int argc, char** argv ) if ( ctrlport == 0 ) { (void)cfg->GetValueFor( "CTLPORT", &ctrlport ); } - if ( 0 == udpport ) { + if ( -1 == udpport ) { (void)cfg->GetValueFor( "UDPPORT", &udpport ); } #ifdef DO_HTTP @@ -1581,7 +1625,7 @@ main( int argc, char** argv ) #ifdef SPAWN_SELF /* loop forever, relaunching children as they die. */ - while ( doFork ) { + while ( doFork && !maint_str ) { ++s_nSpawns; /* increment in parent *before* copy */ pid_t pid = fork(); if ( pid == 0 ) { /* child */ @@ -1603,6 +1647,26 @@ main( int argc, char** argv ) } #endif + if ( 0 != udpport ) { + struct sockaddr_in saddr; + g_udpsock = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); + saddr.sin_family = PF_INET; + saddr.sin_addr.s_addr = htonl(INADDR_ANY); + saddr.sin_port = htons(udpport); + int err = bind( g_udpsock, (struct sockaddr*)&saddr, sizeof(saddr) ); + if ( 0 == err ) { + err = fcntl( g_udpsock, F_SETFL, O_NONBLOCK ); + } else { + logf( XW_LOGERROR, "bind()=>%s", strerror(errno) ); + g_udpsock = -1; + } + } + + if ( !!maint_str ) { + maint_str_loop( g_udpsock, maint_str ); + exit( 0 ); + } + /* Needs to be reset after a crash/respawn */ PermID::SetStartTime( time(NULL) ); @@ -1664,21 +1728,6 @@ main( int argc, char** argv ) exit( 1 ); } - if ( 0 != udpport ) { - struct sockaddr_in saddr; - g_udpsock = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); - saddr.sin_family = PF_INET; - saddr.sin_addr.s_addr = htonl(INADDR_ANY); - saddr.sin_port = htons(udpport); - int err = bind( g_udpsock, (struct sockaddr*)&saddr, sizeof(saddr) ); - if ( 0 == err ) { - err = fcntl( g_udpsock, F_SETFL, O_NONBLOCK ); - } else { - logf( XW_LOGERROR, "bind()=>%s", strerror(errno) ); - g_udpsock = -1; - } - } - #ifdef DO_HTTP HttpState http_state; int addr; From 0ef3549a4ee14754694d1f418318ea918e039315 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 19 Jan 2013 15:18:36 -0800 Subject: [PATCH 031/116] handle (display) the relay's new error message --- xwords4/linux/gtkmain.c | 11 ++++++++++- xwords4/linux/main.h | 5 +++-- xwords4/linux/relaycon.c | 3 +++ xwords4/linux/relaycon.h | 1 + 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index 62ddc65aa..c620f2552 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -26,6 +26,7 @@ #include "gtkboard.h" #include "linuxmain.h" #include "relaycon.h" +#include "gtkask.h" static void onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid, XP_Bool isNew ); @@ -443,6 +444,13 @@ gtkDevIDChanged( void* closure, const XP_UCHAR* devID ) } } +static void +gtkErrorMsgRcvd( void* closure, const XP_UCHAR* msg ) +{ + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + (void)gtkask( apg->window, msg, GTK_BUTTONS_OK ); +} + void onGameSaved( void* closure, sqlite3_int64 rowid, XP_Bool firstTime ) @@ -515,6 +523,7 @@ gtkmain( LaunchParams* params ) .msgReceived = gtkGotBuf, .msgNoticeReceived = gtkNoticeRcvd, .devIDChanged = gtkDevIDChanged, + .msgErrorMsg = gtkErrorMsgRcvd, }; relaycon_init( params, &procs, &apg, @@ -522,7 +531,7 @@ gtkmain( LaunchParams* params ) params->connInfo.relay.defaultSendPort ); sendRelayReg( &apg ); - (void)makeGamesWindow( &apg ); + apg.window = makeGamesWindow( &apg ); gtk_main(); closeGamesDB( apg.pDb ); diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index 942cf737c..d0cd1c0fa 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -1,6 +1,6 @@ -/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ /* - * Copyright 2001-2007 by Eric House (xwords@eehouse.org). All rights + * Copyright 2001-2013 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or @@ -238,6 +238,7 @@ typedef struct _GtkAppGlobals { LaunchParams* params; GSList* globalsList; GList* sources; + GtkWidget* window; GtkWidget* listWidget; GtkWidget* openButton; GtkWidget* deleteButton; diff --git a/xwords4/linux/relaycon.c b/xwords4/linux/relaycon.c index eb5422f83..f6e4127c6 100644 --- a/xwords4/linux/relaycon.c +++ b/xwords4/linux/relaycon.c @@ -189,6 +189,9 @@ relaycon_receive( void* closure, int socket ) (*storage->procs.msgNoticeReceived)( storage->procsClosure, ntohl(gameToken) ); break; } + case XWPDEV_MSGRSP: + (*storage->procs.msgErrorMsg)( storage->procsClosure, (XP_UCHAR*)ptr ); + break; default: XP_LOGF( "%s: Unexpected cmd %d", __func__, cmd ); XP_ASSERT( 0 ); diff --git a/xwords4/linux/relaycon.h b/xwords4/linux/relaycon.h index 962fe9fed..f1e67f725 100644 --- a/xwords4/linux/relaycon.h +++ b/xwords4/linux/relaycon.h @@ -26,6 +26,7 @@ typedef struct _Procs { void (*msgReceived)( void* closure, XP_U8* buf, XP_U16 len ); void (*msgNoticeReceived)( void* closure, XP_U32 gameToken ); void (*devIDChanged)( void* closure, const XP_UCHAR* devID ); + void (*msgErrorMsg)( void* closure, const XP_UCHAR* msg ); } RelayConnProcs; void relaycon_init( LaunchParams* params, const RelayConnProcs* procs, From 360602b3a0c5a2380636bed92f049385fadc3646 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 19 Jan 2013 16:02:58 -0800 Subject: [PATCH 032/116] fix compile failure: never meant to check this change in. --- .../eehouse/android/xw4/CommsTransport.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java index 8a7d132c8..ba89de474 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java @@ -377,16 +377,14 @@ public class CommsTransport implements TransportProcs, switch ( addr.conType ) { case COMMS_CONN_RELAY: - nSent = RelayService.sendPacket( m_context, addr.sms_phone, - gameID, buf ); - // if ( NetStateCache.netAvail( m_context ) ) { - // putOut( buf ); // add to queue - // if ( null == m_thread ) { - // m_thread = new CommsThread(); - // m_thread.start(); - // } - // nSent = buf.length; - // } + if ( NetStateCache.netAvail( m_context ) ) { + putOut( buf ); // add to queue + if ( null == m_thread ) { + m_thread = new CommsThread(); + m_thread.start(); + } + nSent = buf.length; + } break; case COMMS_CONN_SMS: nSent = SMSService.sendPacket( m_context, addr.sms_phone, From 3bcf5d504b99ff8a84b42d0ab72770fd6db7ddc3 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 19 Jan 2013 16:03:29 -0800 Subject: [PATCH 033/116] add a debug pref for turning on UDP-based networking (ignored so far) --- xwords4/android/XWords4/res/values/common_rsrc.xml | 2 ++ xwords4/android/XWords4/res/xml/xwprefs.xml | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/xwords4/android/XWords4/res/values/common_rsrc.xml b/xwords4/android/XWords4/res/values/common_rsrc.xml index 15324501a..d73dcd305 100644 --- a/xwords4/android/XWords4/res/values/common_rsrc.xml +++ b/xwords4/android/XWords4/res/values/common_rsrc.xml @@ -38,6 +38,7 @@ key_sms_port key_dict_host3 key_logging_on + key_udp_relay key_show_sms key_init_hintsallowed key_init_nethintsallowed @@ -126,6 +127,7 @@ %1$s/%2$s Write DB to SD card Load DB from SD card + New/Experimental relay connection diff --git a/xwords4/android/XWords4/res/xml/xwprefs.xml b/xwords4/android/XWords4/res/xml/xwprefs.xml index 2cb350166..5bf240ba9 100644 --- a/xwords4/android/XWords4/res/xml/xwprefs.xml +++ b/xwords4/android/XWords4/res/xml/xwprefs.xml @@ -295,6 +295,10 @@ android:summary="@string/git_rev" android:enabled="false" /> + Date: Sat, 19 Jan 2013 22:53:25 -0800 Subject: [PATCH 034/116] call comms_close in curses too to avoid new assert --- xwords4/linux/cursesmain.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index 54466f653..24eece97d 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -1926,6 +1926,9 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) } #endif } + if ( !!g_globals.cGlobals.game.comms ) { + comms_stop( g_globals.cGlobals.game.comms ); + } saveGame( &g_globals.cGlobals ); game_dispose( &g_globals.cGlobals.game ); /* takes care of the dict */ From b768d8fccc10293a1c31f840d3f6aadeffb13da0 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 20 Jan 2013 09:22:35 -0800 Subject: [PATCH 035/116] fall through to using hard-coded maxsocks value --- xwords4/relay/xwrelay.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index 9a354e0a0..d54678463 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -1577,9 +1577,7 @@ main( int argc, char** argv ) if ( nWorkerThreads == 0 ) { (void)cfg->GetValueFor( "NTHREADS", &nWorkerThreads ); } - if ( g_maxsocks == -1 ) { - (void)cfg->GetValueFor( "MAXSOCKS", &g_maxsocks ); - } else { + if ( g_maxsocks == -1 && !cfg->GetValueFor( "MAXSOCKS", &g_maxsocks ) ) { g_maxsocks = 100; } char serverNameBuf[128]; From f06d129c2425563e424cab4d7b9dd7866b8c9609 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 20 Jan 2013 09:53:26 -0800 Subject: [PATCH 036/116] change env variable name; provided inital board size and dict choices --- xwords4/linux/gtknewgame.c | 15 ++++++++------- xwords4/linux/linuxmain.c | 9 +++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/xwords4/linux/gtknewgame.c b/xwords4/linux/gtknewgame.c index 06b8f6085..dace64d3f 100644 --- a/xwords4/linux/gtknewgame.c +++ b/xwords4/linux/gtknewgame.c @@ -357,18 +357,16 @@ makeNewGameDialog( GtkNewGameState* state ) for ( iter = dicts, ii = 0; !!iter; iter = iter->next, ++ii ) { const gchar* name = iter->data; gtk_combo_box_append_text( GTK_COMBO_BOX(dictCombo), name ); - if ( !!gi->dictName && !strcmp( name, gi->dictName ) ) { + if ( !!gi->dictName ) { + if ( !strcmp( name, gi->dictName ) ) { + gtk_combo_box_set_active( GTK_COMBO_BOX(dictCombo), ii ); + } + } else if ( 0 == ii ) { gtk_combo_box_set_active( GTK_COMBO_BOX(dictCombo), ii ); } } g_slist_free( dicts ); - /* if ( !!gi->dictName ) { */ - /* gtk_box_pack_start( GTK_BOX(hbox), */ - /* gtk_label_new(gi->dictName), */ - /* FALSE, TRUE, 0 ); */ - /* } */ - gtk_widget_show( hbox ); gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); @@ -571,6 +569,9 @@ newGameDialog( GtkGameGlobals* globals, CurGameInfo* gi, CommsAddrRec* addr, state.revert = FALSE; state.loaded = XP_FALSE; state.nCols = gi->boardSize; + if ( 0 == state.nCols ) { + state.nCols = globals->cGlobals.params->pgi.boardSize; + } state.role = gi->serverRole; XP_MEMCPY( &state.addr, addr, sizeof(state.addr) ); diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 083035540..e5510a2c8 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -1512,10 +1512,10 @@ getDictPath( const LaunchParams *params, const char* name, char* result, int resultLen ) { XP_Bool success = XP_FALSE; - GSList* dictDirs; + GSList* iter; result[0] = '\0'; - for ( dictDirs = params->dictDirs; !!dictDirs; dictDirs = dictDirs->next ) { - const char* path = dictDirs->data; + for ( iter = params->dictDirs; !!iter; iter = iter->next ) { + const char* path = iter->data; char buf[256]; int len = snprintf( buf, VSIZE(buf), "%s/%s.xwd", path, name ); if ( len < VSIZE(buf) && file_exists( buf ) ) { @@ -1734,7 +1734,8 @@ main( int argc, char** argv ) mainParams.showRobotScores = XP_FALSE; mainParams.useMmap = XP_TRUE; - char* envDictPath = getenv( "XW_DICTSPATH" ); + char* envDictPath = getenv( "XW_DICTDIR" ); + XP_LOGF( "%s: envDictPath=%s", __func__, envDictPath ); if ( !!envDictPath ) { char *saveptr; for ( ; ; ) { From 9023440a8886df3f57ac9d7d144ceacec9a402f4 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 20 Jan 2013 10:03:20 -0800 Subject: [PATCH 037/116] reply with alert to any connection attempt when in maint mode --- xwords4/relay/xwrelay.cpp | 19 ++++++++----------- xwords4/relay/xwrelay.h | 5 +++++ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index d54678463..c1ff4fd4f 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -1421,6 +1421,10 @@ maint_str_loop( int udpsock, const char* str ) { assert( -1 != udpsock ); int len = 1 + strlen(str); + unsigned char outbuf[len + 2]; + outbuf[0] = XWPDEV_PROTO_VERSION; + outbuf[1] = XWPDEV_ALERT; + memcpy( &outbuf[2], str, len ); fd_set rfds; for ( ; ; ) { @@ -1441,18 +1445,11 @@ maint_str_loop( int udpsock, const char* str ) ssize_t nRead = recvfrom( udpsock, buf, sizeof(buf), 0 /*flags*/, &saddr.addr, &fromlen ); - if ( 2 <= nRead ) { - if ( buf[0] == XWPDEV_PROTO_VERSION && buf[1] == XWPDEV_REG ) { - // reply, reusing buffer - buf[1] = XWPDEV_MSGRSP; - memcpy( &buf[2], str, len ); - send_via_udp( udpsock, &saddr.addr, buf, 2 + len ); - } + if ( 2 <= nRead && buf[0] == XWPDEV_PROTO_VERSION ) { + send_via_udp( udpsock, &saddr.addr, outbuf, sizeof(outbuf) ); } } - - } - + } // for } int @@ -1662,7 +1659,7 @@ main( int argc, char** argv ) if ( !!maint_str ) { maint_str_loop( g_udpsock, maint_str ); - exit( 0 ); + exit( 1 ); // should never exit } /* Needs to be reset after a crash/respawn */ diff --git a/xwords4/relay/xwrelay.h b/xwords4/relay/xwrelay.h index c06682295..a7f27a230 100644 --- a/xwords4/relay/xwrelay.h +++ b/xwords4/relay/xwrelay.h @@ -33,6 +33,11 @@ typedef #endif enum { XWPDEV_NONE /* 0 is an illegal value */ + ,XWPDEV_ALERT /* relay->device: provides a string message to + present to the user (with device allowed not + to present the same string more than once) + format: proto: 1, this enum: 1, + null-terminnated string: varies */ ,XWPDEV_REG /* dev->relay: device registers self and self-selected (e.g. gcm) or assigned devid format: proto: 1; this enum: 1; idType: 1, From 3fc31a6fe177f829ec53502bc655620d3ebcae42 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 20 Jan 2013 10:04:24 -0800 Subject: [PATCH 038/116] new enum for alert --- xwords4/linux/relaycon.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/xwords4/linux/relaycon.c b/xwords4/linux/relaycon.c index f6e4127c6..b051e4830 100644 --- a/xwords4/linux/relaycon.c +++ b/xwords4/linux/relaycon.c @@ -186,10 +186,11 @@ relaycon_receive( void* closure, int socket ) XP_U32 gameToken; XP_MEMCPY( &gameToken, ptr, sizeof(gameToken) ); ptr += sizeof( gameToken ); - (*storage->procs.msgNoticeReceived)( storage->procsClosure, ntohl(gameToken) ); + (*storage->procs.msgNoticeReceived)( storage->procsClosure, + ntohl(gameToken) ); break; } - case XWPDEV_MSGRSP: + case XWPDEV_ALERT: (*storage->procs.msgErrorMsg)( storage->procsClosure, (XP_UCHAR*)ptr ); break; default: @@ -240,8 +241,11 @@ hostNameToIP( const XP_UCHAR* name ) static ssize_t sendIt( RelayConStorage* storage, const XP_U8* msgbuf, XP_U16 len ) { - return sendto( storage->socket, msgbuf, len, 0, /* flags */ - (struct sockaddr*)&storage->saddr, sizeof(storage->saddr) ); + ssize_t nSent = sendto( storage->socket, msgbuf, len, 0, /* flags */ + (struct sockaddr*)&storage->saddr, + sizeof(storage->saddr) ); + XP_LOGF( "%s()=>%d", __func__, nSent ); + return nSent; } static size_t From 016b16a8b2f933e4859194802ccffb6a36a40ef8 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 20 Jan 2013 13:06:34 -0800 Subject: [PATCH 039/116] change relay alert message format to length-plus-string rather than null-terminated for easier handling on the java end. --- xwords4/relay/xwrelay.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index c1ff4fd4f..9bd037abf 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -1420,11 +1420,13 @@ static void maint_str_loop( int udpsock, const char* str ) { assert( -1 != udpsock ); - int len = 1 + strlen(str); - unsigned char outbuf[len + 2]; + short len = strlen(str); + unsigned char outbuf[2 + sizeof(len) + len]; outbuf[0] = XWPDEV_PROTO_VERSION; outbuf[1] = XWPDEV_ALERT; - memcpy( &outbuf[2], str, len ); + short lenNS = htons( len ); + memcpy( &outbuf[2], &lenNS, sizeof(lenNS) ); + memcpy( &outbuf[2+sizeof(len)], str, len ); fd_set rfds; for ( ; ; ) { From 93ff4f4fcb962e6f2c1473b3178b3cca0ef711f0 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 20 Jan 2013 13:07:01 -0800 Subject: [PATCH 040/116] handle the new length-plus-string format --- xwords4/linux/relaycon.c | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/xwords4/linux/relaycon.c b/xwords4/linux/relaycon.c index b051e4830..7025b5cc2 100644 --- a/xwords4/linux/relaycon.c +++ b/xwords4/linux/relaycon.c @@ -35,6 +35,8 @@ static XP_U32 hostNameToIP( const XP_UCHAR* name ); static void relaycon_receive( void* closure, int socket ); static ssize_t sendIt( RelayConStorage* storage, const XP_U8* msgbuf, XP_U16 len ); static size_t addStrWithLength( XP_U8* buf, XP_U8* end, const XP_UCHAR* str ); +static void getNetString( XP_U8** ptr, XP_U16 len, XP_UCHAR* buf ); +static XP_U16 getNetShort( XP_U8** ptr ); void relaycon_init( LaunchParams* params, const RelayConnProcs* procs, @@ -165,13 +167,9 @@ relaycon_receive( void* closure, int socket ) XWRelayReg cmd = *ptr++; switch( cmd ) { case XWPDEV_REGRSP: { - XP_U16 len; - XP_MEMCPY( &len, ptr, sizeof(len) ); - len = ntohs( len ); - ptr += sizeof( len ); + XP_U16 len = getNetShort( &ptr ); XP_UCHAR devID[len+1]; - XP_MEMCPY( devID, ptr, len ); - devID[len] = '\0'; + getNetString( &ptr, len, devID ); (*storage->procs.devIDChanged)( storage->procsClosure, devID ); } break; @@ -190,9 +188,13 @@ relaycon_receive( void* closure, int socket ) ntohl(gameToken) ); break; } - case XWPDEV_ALERT: - (*storage->procs.msgErrorMsg)( storage->procsClosure, (XP_UCHAR*)ptr ); + case XWPDEV_ALERT: { + XP_U16 len = getNetShort( &ptr ); + XP_UCHAR buf[len+1]; + getNetString( &ptr, len, buf ); + (*storage->procs.msgErrorMsg)( storage->procsClosure, buf ); break; + } default: XP_LOGF( "%s: Unexpected cmd %d", __func__, cmd ); XP_ASSERT( 0 ); @@ -260,3 +262,20 @@ addStrWithLength( XP_U8* buf, XP_U8* end, const XP_UCHAR* str ) } return len + sizeof(len); } + +static XP_U16 +getNetShort( XP_U8** ptr ) +{ + XP_U16 result; + memcpy( &result, *ptr, sizeof(result) ); + *ptr += sizeof(result); + return ntohs( result ); +} + +static void +getNetString( XP_U8** ptr, XP_U16 len, XP_UCHAR* buf ) +{ + memcpy( buf, *ptr, len ); + *ptr += len; + buf[len] = '\0'; +} From fd2f4309f484d04007551b021a1e0ab5c3c0a858 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 20 Jan 2013 15:29:51 -0800 Subject: [PATCH 041/116] beginning of android changes to talk udp to the relay: sufficient to get back (and log, not display) a message when the relay is in maintenance mode. --- .../eehouse/android/xw4/PrefsActivity.java | 4 + .../org/eehouse/android/xw4/RelayService.java | 264 +++++++++++++++++- .../org/eehouse/android/xw4/SMSService.java | 2 +- .../src/org/eehouse/android/xw4/XWApp.java | 4 +- .../src/org/eehouse/android/xw4/XWPrefs.java | 5 + .../org/eehouse/android/xw4/jni/UtilCtxt.java | 1 + 6 files changed, 269 insertions(+), 11 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/PrefsActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/PrefsActivity.java index 1398c5d28..ad7804597 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/PrefsActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/PrefsActivity.java @@ -41,6 +41,7 @@ public class PrefsActivity extends PreferenceActivity private String m_keyLogging; private String m_smsToasting; private String m_smsEnable; + private String m_udpEnabled; private String m_downloadPath; @Override @@ -122,6 +123,7 @@ public class PrefsActivity extends PreferenceActivity m_keyLogging = getString( R.string.key_logging_on ); m_smsToasting = getString( R.string.key_show_sms ); m_smsEnable = getString( R.string.key_enable_sms ); + m_udpEnabled = getString( R.string.key_udp_relay ); m_downloadPath = getString( R.string.key_download_path ); Button button = (Button)findViewById( R.id.revert_colors ); @@ -161,6 +163,8 @@ public class PrefsActivity extends PreferenceActivity DbgUtils.logEnable( sp.getBoolean( key, false ) ); } else if ( key.equals( m_smsToasting ) ) { SMSService.smsToastEnable( sp.getBoolean( key, false ) ); + } else if ( key.equals( m_udpEnabled ) ) { + RelayService.udpChanged( this ); } else if ( key.equals( m_smsEnable ) ) { if ( sp.getBoolean( key, true ) ) { SMSService.checkForInvites( this ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java index bc4d2d179..b5ed85fcc 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java @@ -24,31 +24,66 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; import java.io.DataOutputStream; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import java.util.concurrent.LinkedBlockingQueue; + +import junit.framework.Assert; import org.eehouse.android.xw4.jni.GameSummary; +import org.eehouse.android.xw4.jni.UtilCtxt; public class RelayService extends Service { private static final int MAX_SEND = 1024; private static final int MAX_BUF = MAX_SEND - 2; + private static final String CMD_STR = "CMD"; + private static final int UDP_CHANGED = 1; + + private Thread m_fetchThread = null; + private Thread m_UDPReadThread = null; + private Thread m_UDPWriteThread = null; + private DatagramSocket m_UDPSocket; + private LinkedBlockingQueue m_queue = null; + + // These must match the enum XWRelayReg in xwrelay.h + private static final int XWPDEV_PROTO_VERSION = 0; + // private static final int XWPDEV_NONE = 0; + private static final int XWPDEV_ALERT = 1; + private static final int XWPDEV_REG = 2; + + public static void startService( Context context ) + { + Intent intent = getIntentTo( context, UDP_CHANGED ); + context.startService( intent ); + } + + public static void udpChanged( Context context ) + { + startService( context ); + } + + private static Intent getIntentTo( Context context, int cmd ) + { + Intent intent = new Intent( context, RelayService.class ); + intent.putExtra( CMD_STR, cmd ); + return intent; + } + @Override public void onCreate() { super.onCreate(); - - Thread thread = new Thread( null, new Runnable() { - public void run() { - fetchAndProcess(); - RelayService.this.stopSelf(); - } - }, getClass().getName() ); - thread.start(); + startFetchThreadIf(); } @Override @@ -57,6 +92,36 @@ public class RelayService extends Service { return null; } + @Override + public int onStartCommand( Intent intent, int flags, int startId ) + { + DbgUtils.logf( "RelayService::onStartCommand" ); + int result; + if ( null != intent ) { + int cmd = intent.getIntExtra( CMD_STR, -1 ); + switch( cmd ) { + case UDP_CHANGED: + DbgUtils.logf( "RelayService::onStartCommand::UDP_CHANGED" ); + if ( XWPrefs.getUDPEnabled( this ) ) { + stopFetchThreadIf(); + startUDPThreads(); + registerWithRelay(); + } else { + stopUDPThreadsIf(); + startFetchThreadIf(); + } + break; + default: + Assert.fail(); + } + + result = Service.START_STICKY; + } else { + result = Service.START_STICKY_COMPATIBILITY; + } + return result; + } + private void setupNotification( String[] relayIDs ) { for ( String relayID : relayIDs ) { @@ -75,6 +140,189 @@ public class RelayService extends Service { } } + private void startFetchThreadIf() + { + DbgUtils.logf( "startFetchThreadIf()" ); + if ( !XWPrefs.getUDPEnabled( this ) && null == m_fetchThread ) { + m_fetchThread = new Thread( null, new Runnable() { + public void run() { + fetchAndProcess(); + m_fetchThread = null; + RelayService.this.stopSelf(); + } + }, getClass().getName() ); + m_fetchThread.start(); + } + } + + private void stopFetchThreadIf() + { + if ( null != m_fetchThread ) { + DbgUtils.logf( "2: m_fetchThread NOT NULL; WHAT TO DO???" ); + } + } + + private void startUDPThreads() + { + DbgUtils.logf( "startUDPThreads" ); + Assert.assertNull( m_UDPWriteThread ); + Assert.assertNull( m_UDPReadThread ); + Assert.assertTrue( XWPrefs.getUDPEnabled( this ) ); + + int port = XWPrefs.getDefaultRelayPort( RelayService.this ); + String host = XWPrefs.getDefaultRelayHost( RelayService.this ); + try { + m_UDPSocket = new DatagramSocket(); + InetAddress addr = InetAddress.getByName( host ); + m_UDPSocket.connect( addr, port ); + } catch( java.net.SocketException se ) { + DbgUtils.loge( se ); + Assert.fail(); + } catch( java.net.UnknownHostException uhe ) { + DbgUtils.loge( uhe ); + } + + m_UDPReadThread = new Thread( null, new Runnable() { + public void run() { + byte[] buf = new byte[1024]; + for ( ; ; ) { + DatagramPacket packet = + new DatagramPacket( buf, buf.length ); + try { + DbgUtils.logf( "UPD read thread blocking on receive" ); + m_UDPSocket.receive( packet ); + DbgUtils.logf( "UPD read thread: receive returned" ); + } catch( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + break; // ??? + } + DbgUtils.logf( "received %d bytes", packet.getLength() ); + gotPacket( packet ); + } + } + }, getClass().getName() ); + m_UDPReadThread.start(); + + m_queue = new LinkedBlockingQueue(); + m_UDPWriteThread = new Thread( null, new Runnable() { + public void run() { + for ( ; ; ) { + DatagramPacket outPacket; + try { + outPacket = m_queue.take(); + } catch ( InterruptedException ie ) { + DbgUtils.logf( "RelayService; write thread killed" ); + break; + } + if ( null == outPacket || 0 == outPacket.getLength() ) { + DbgUtils.logf( "stopping write thread" ); + break; + } + DbgUtils.logf( "Sending packet of length %d", + outPacket.getLength() ); + try { + m_UDPSocket.send( outPacket ); + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + } + } + }, getClass().getName() ); + m_UDPWriteThread.start(); + } + + private void stopUDPThreadsIf() + { + DbgUtils.logf( "stopUDPThreadsIf" ); + if ( null != m_queue && null != m_UDPWriteThread ) { + // can't add null + m_queue.add( new DatagramPacket( new byte[0], 0 ) ); + try { + DbgUtils.logf( "joining m_UDPWriteThread" ); + m_UDPWriteThread.join(); + DbgUtils.logf( "SUCCESSFULLY joined m_UDPWriteThread" ); + } catch( java.lang.InterruptedException ie ) { + DbgUtils.loge( ie ); + } + m_UDPWriteThread = null; + m_queue = null; + } + if ( null != m_UDPSocket && null != m_UDPReadThread ) { + m_UDPSocket.close(); + DbgUtils.logf( "waiting for read thread to exit" ); + try { + m_UDPReadThread.join(); + } catch( java.lang.InterruptedException ie ) { + DbgUtils.loge( ie ); + } + DbgUtils.logf( "read thread exited" ); + m_UDPReadThread = null; + m_UDPSocket = null; + } + DbgUtils.logf( "stopUDPThreadsIf DONE" ); + } + + private void gotPacket( DatagramPacket packet ) + { + DbgUtils.logf( "gotPacket" ); + ByteArrayInputStream bis = new ByteArrayInputStream( packet.getData() ); + DataInputStream dis = new DataInputStream( bis ); + try { + byte proto = dis.readByte(); + if ( XWPDEV_PROTO_VERSION == proto ) { + byte cmd = dis.readByte(); + switch ( cmd ) { + case XWPDEV_ALERT: + short len = dis.readShort(); + byte[] tmp = new byte[len]; + dis.read( tmp ); + String msg = new String( tmp ); + DbgUtils.logf( "got message: %s", msg ); + break; + default: + DbgUtils.logf( "RelayService: Unhandled cmd: %d", cmd ); + break; + } + } else { + DbgUtils.logf( "bad proto %d", proto ); + } + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + } + + private void registerWithRelay() + { + byte typ; + String devid = XWPrefs.getRelayDevID( this ); + if ( null != devid && 0 < devid.length() ) { + typ = UtilCtxt.ID_TYPE_RELAY; + } else { + devid = XWPrefs.getGCMDevID( this ); + if ( null != devid && 0 < devid.length() ) { + typ = UtilCtxt.ID_TYPE_ANDROID_GCM; + } else { + devid = "DO NOT SHIP WITH ME"; + typ = UtilCtxt.ID_TYPE_ANDROID_OTHER; + } + } + + ByteArrayOutputStream bas = new ByteArrayOutputStream(); + DataOutputStream outBuf = new DataOutputStream( bas ); + try { + outBuf.writeByte( XWPDEV_PROTO_VERSION ); + outBuf.writeByte( XWPDEV_REG ); + outBuf.writeByte( typ ); + outBuf.writeShort( devid.length() ); + outBuf.writeBytes( devid ); + + byte[] data = bas.toByteArray(); + m_queue.add( new DatagramPacket( data, data.length ) ); + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + } + private void fetchAndProcess() { long[][] rowIDss = new long[1][]; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java index be0a95ef2..1a78e9f47 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java @@ -66,7 +66,7 @@ public class SMSService extends Service { private static final int INVITE = 2; private static final int SEND = 3; private static final int REMOVE = 4; - private static final int MESG_GAMEGONE = 5; + // private static final int MESG_GAMEGONE = 5; private static final int CHECK_MSGDB = 6; private static final int ADDED_MISSING = 7; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java index d3d02f534..e48854788 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java @@ -58,10 +58,10 @@ public class XWApp extends Application { RelayReceiver.RestartTimer( this ); UpdateCheckReceiver.restartTimer( this ); + BTService.startService( this ); - SMSService.checkForInvites( this ); - + RelayService.startService( this ); GCMIntentService.init( this ); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java index 1491c52ff..649059f3a 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java @@ -34,6 +34,11 @@ public class XWPrefs { return getPrefsBoolean( context, R.string.key_enable_sms, false ); } + public static boolean getUDPEnabled( Context context ) + { + return getPrefsBoolean( context, R.string.key_udp_relay, false ); + } + public static boolean getDebugEnabled( Context context ) { return getPrefsBoolean( context, R.string.key_enable_debug, false ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java index 5302f41f7..95c18bf01 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java @@ -60,6 +60,7 @@ public interface UtilCtxt { public static final int ID_TYPE_NONE = 0; public static final int ID_TYPE_RELAY = 1; public static final int ID_TYPE_ANDROID_GCM = 3; + public static final int ID_TYPE_ANDROID_OTHER = 4; String getDevID( /*out*/ byte[] typ ); void deviceRegistered( int devIDType, String idRelay ); From 3df0484e6a38e4db2586eccec595a7664c1f55b6 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 20 Jan 2013 16:07:38 -0800 Subject: [PATCH 042/116] get maintenance mode message into an alert -- and create a new common superclass for Service subclasses to reuse some code. --- .../org/eehouse/android/xw4/BTService.java | 26 +------- .../org/eehouse/android/xw4/DlgDelegate.java | 5 ++ .../org/eehouse/android/xw4/MultiService.java | 2 + .../org/eehouse/android/xw4/RelayService.java | 13 +--- .../org/eehouse/android/xw4/SMSService.java | 27 +-------- .../org/eehouse/android/xw4/XWActivity.java | 6 +- .../android/xw4/XWExpandableListActivity.java | 16 +++++ .../eehouse/android/xw4/XWListActivity.java | 6 +- .../org/eehouse/android/xw4/XWService.java | 60 +++++++++++++++++++ 9 files changed, 92 insertions(+), 69 deletions(-) create mode 100644 xwords4/android/XWords4/src/org/eehouse/android/xw4/XWService.java diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java index 4d6f8fd6d..f73959b5b 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java @@ -29,7 +29,6 @@ import android.bluetooth.BluetoothSocket; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.os.IBinder; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.OutputStream; @@ -49,7 +48,7 @@ import junit.framework.Assert; import org.eehouse.android.xw4.MultiService.MultiEvent; import org.eehouse.android.xw4.jni.CommsAddrRec; -public class BTService extends Service { +public class BTService extends XWService { private static final long RESEND_TIMEOUT = 5; // seconds private static final int MAX_SEND_FAIL = 3; @@ -77,8 +76,6 @@ public class BTService extends Service { private static final String NTO_STR = "TOT"; private static final String NHE_STR = "HER"; - private static MultiService s_srcMgr = null; - private enum BTCmd { PING, PONG, @@ -147,16 +144,6 @@ public class BTService extends Service { } } - public static void setListener( MultiService.MultiEventListener li ) - { - if ( XWApp.BTSUPPORTED ) { - if ( null == s_srcMgr ) { - s_srcMgr = new MultiService(); - } - s_srcMgr.setListener( li ); - } - } - public static void radioChanged( Context context, boolean cameOn ) { Intent intent = getIntentTo( context, RADIO ); @@ -316,12 +303,6 @@ public class BTService extends Service { return result; } - @Override - public IBinder onBind( Intent intent ) - { - return null; - } - private class BTListenerThread extends Thread { private BluetoothServerSocket m_serverSocket; @@ -852,11 +833,6 @@ public class BTService extends Service { sendResult( MultiEvent.SCAN_DONE, (Object)(names()) ); } - private void sendResult( MultiEvent event, Object ... args ) - { - s_srcMgr.sendResult( event, args ); - } - private void listLocalBTGames( boolean force ) { if ( null == s_devGames ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java index 3c3dfa467..a7152518f 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java @@ -272,6 +272,11 @@ public class DlgDelegate { (String)args[0] ); asToast = false; break; + case RELAY_ALERT: + msg = (String)args[0]; + asToast = false; + break; + default: DbgUtils.logf( "eventOccurred: unhandled event %s", event.toString() ); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java index 5d2a776eb..17c123d73 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java @@ -62,6 +62,8 @@ public class MultiService { , SMS_SEND_OK , SMS_SEND_FAILED , SMS_SEND_FAILED_NORADIO + + , RELAY_ALERT }; public interface MultiEventListener { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java index b5ed85fcc..2e92d7c9d 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java @@ -23,7 +23,6 @@ package org.eehouse.android.xw4; import android.app.Service; import android.content.Context; import android.content.Intent; -import android.os.IBinder; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; @@ -41,8 +40,9 @@ import junit.framework.Assert; import org.eehouse.android.xw4.jni.GameSummary; import org.eehouse.android.xw4.jni.UtilCtxt; +import org.eehouse.android.xw4.MultiService.MultiEvent; -public class RelayService extends Service { +public class RelayService extends XWService { private static final int MAX_SEND = 1024; private static final int MAX_BUF = MAX_SEND - 2; @@ -86,12 +86,6 @@ public class RelayService extends Service { startFetchThreadIf(); } - @Override - public IBinder onBind( Intent intent ) - { - return null; - } - @Override public int onStartCommand( Intent intent, int flags, int startId ) { @@ -276,8 +270,7 @@ public class RelayService extends Service { short len = dis.readShort(); byte[] tmp = new byte[len]; dis.read( tmp ); - String msg = new String( tmp ); - DbgUtils.logf( "got message: %s", msg ); + sendResult( MultiEvent.RELAY_ALERT, new String( tmp ) ); break; default: DbgUtils.logf( "RelayService: Unhandled cmd: %d", cmd ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java index 1a78e9f47..1c8671843 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java @@ -32,7 +32,6 @@ import android.content.SharedPreferences; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; -import android.os.IBinder; import android.preference.PreferenceManager; import android.telephony.SmsManager; import android.telephony.SmsMessage; @@ -52,7 +51,7 @@ import org.eehouse.android.xw4.jni.CommsAddrRec; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; import org.eehouse.android.xw4.jni.XwJNI; -public class SMSService extends Service { +public class SMSService extends XWService { private static final String INSTALL_URL = "http://eehouse.org/_/a.py/a "; private static final int MAX_SMS_LEN = 140; // ??? differs by network @@ -76,7 +75,6 @@ public class SMSService extends Service { private static final String PHONE = "PHONE"; private static Boolean s_showToasts = null; - private static MultiService s_srcMgr = null; // All messages are base64-encoded byte arrays. The first byte is // always one of these. What follows depends. @@ -189,16 +187,6 @@ public class SMSService extends Service { return result; } - public static void setListener( MultiService.MultiEventListener li ) - { - if ( XWApp.SMSSUPPORTED ) { - if ( null == s_srcMgr ) { - s_srcMgr = new MultiService(); - } - s_srcMgr.setListener( li ); - } - } - private static Intent getIntentTo( Context context, int cmd ) { if ( null == s_showToasts ) { @@ -289,12 +277,6 @@ public class SMSService extends Service { return result; } // onStartCommand - @Override - public IBinder onBind( Intent intent ) - { - return null; - } - private void inviteRemote( String phone, int gameID, String gameName, int lang, String dict, int nPlayersT, int nPlayersH ) @@ -647,13 +629,6 @@ public class SMSService extends Service { } } - private void sendResult( MultiEvent event, Object ... args ) - { - if ( null != s_srcMgr ) { - s_srcMgr.sendResult( event, args ); - } - } - private void registerReceivers() { registerReceiver( new BroadcastReceiver() { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java index 5b0141838..e6da5a830 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java @@ -54,8 +54,7 @@ public class XWActivity extends Activity protected void onResume() { DbgUtils.logf( "%s.onResume(this=%H)", getClass().getName(), this ); - BTService.setListener( this ); - SMSService.setListener( this ); + XWService.setListener( this ); super.onResume(); } @@ -63,8 +62,7 @@ public class XWActivity extends Activity protected void onPause() { DbgUtils.logf( "%s.onPause(this=%H)", getClass().getName(), this ); - BTService.setListener( null ); - SMSService.setListener( null ); + XWService.setListener( null ); super.onPause(); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWExpandableListActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWExpandableListActivity.java index f33ce61cc..f047589bd 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWExpandableListActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWExpandableListActivity.java @@ -41,6 +41,22 @@ public class XWExpandableListActivity extends ExpandableListActivity m_delegate = new DlgDelegate( this, this, savedInstanceState ); } + @Override + protected void onResume() + { + DbgUtils.logf( "%s.onResume(this=%H)", getClass().getName(), this ); + XWService.setListener( this ); + super.onResume(); + } + + @Override + protected void onPause() + { + DbgUtils.logf( "%s.onPause(this=%H)", getClass().getName(), this ); + XWService.setListener( null ); + super.onPause(); + } + @Override protected void onSaveInstanceState( Bundle outState ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java index 3a042fe68..fcd96af06 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java @@ -51,8 +51,7 @@ public class XWListActivity extends ListActivity protected void onResume() { DbgUtils.logf( "%s.onResume(this=%H)", getClass().getName(), this ); - BTService.setListener( this ); - SMSService.setListener( this ); + XWService.setListener( this ); super.onResume(); } @@ -60,8 +59,7 @@ public class XWListActivity extends ListActivity protected void onPause() { DbgUtils.logf( "%s.onPause(this=%H)", getClass().getName(), this ); - BTService.setListener( null ); - SMSService.setListener( null ); + XWService.setListener( null ); super.onPause(); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWService.java new file mode 100644 index 000000000..2f93a94e7 --- /dev/null +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWService.java @@ -0,0 +1,60 @@ +/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */ +/* + * Copyright 2010 - 2012 by Eric House (xwords@eehouse.org). All + * rights reserved. + * + * This program 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 program 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. + */ + +package org.eehouse.android.xw4; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +import org.eehouse.android.xw4.MultiService.MultiEvent; + + +public class XWService extends Service { + + protected static MultiService s_srcMgr = null; + + @Override + public IBinder onBind( Intent intent ) + { + return null; + } + + public final static void setListener( MultiService.MultiEventListener li ) + { + if ( null == s_srcMgr ) { + DbgUtils.logf( "setListener: registering %s", li.getClass().getName() ); + s_srcMgr = new MultiService(); + } + s_srcMgr.setListener( li ); + } + + protected void sendResult( MultiEvent event, Object ... args ) + { + if ( null != s_srcMgr ) { + s_srcMgr.sendResult( event, args ); + } else { + DbgUtils.logf( "sendResult: dropping event" ); + } + } + + + +} From 991cc8f04c8c542042e9b048b861477228c2e364 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 20 Jan 2013 21:03:43 -0800 Subject: [PATCH 043/116] fail in main mode if udp socket not set --- xwords4/relay/xwrelay.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index 9bd037abf..1e2e5eeb7 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -1419,6 +1419,7 @@ enable_keepalive( int sock ) static void maint_str_loop( int udpsock, const char* str ) { + logf( XW_LOGINFO, "%s()", __func__ ); assert( -1 != udpsock ); short len = strlen(str); unsigned char outbuf[2 + sizeof(len) + len]; @@ -1446,9 +1447,12 @@ maint_str_loop( int udpsock, const char* str ) ssize_t nRead = recvfrom( udpsock, buf, sizeof(buf), 0 /*flags*/, &saddr.addr, &fromlen ); + logf( XW_LOGINFO, "%s(); got %d bytes", __func__, nRead); - if ( 2 <= nRead && buf[0] == XWPDEV_PROTO_VERSION ) { + if ( 1 <= nRead && buf[0] == XWPDEV_PROTO_VERSION ) { send_via_udp( udpsock, &saddr.addr, outbuf, sizeof(outbuf) ); + } else { + logf( XW_LOGERROR, "unexpected data" ); } } } // for @@ -1644,7 +1648,7 @@ main( int argc, char** argv ) } #endif - if ( 0 != udpport ) { + if ( -1 != udpport ) { struct sockaddr_in saddr; g_udpsock = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); saddr.sin_family = PF_INET; From 132a495c4f6a7de8ce8e2c694ceef12001e35da3 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 20 Jan 2013 21:32:19 -0800 Subject: [PATCH 044/116] add explanatory comment --- .../XWords4/src/org/eehouse/android/xw4/MultiMsgSink.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiMsgSink.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiMsgSink.java index 9041220bc..aa880a0a4 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiMsgSink.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiMsgSink.java @@ -36,7 +36,7 @@ public class MultiMsgSink implements TransportProcs { public int transportSend( byte[] buf, final CommsAddrRec addr, int gameID ) { - Assert.fail(); + Assert.fail(); // implement if this is getting called!!! return -1; } @@ -53,9 +53,9 @@ public class MultiMsgSink implements TransportProcs { { } - public boolean relayNoConnProc( byte[] buf, String relayID ) + public boolean relayNoConnProc( byte[] buf, String relayID ) { - Assert.fail(); + Assert.fail(); // implement if this is getting called!!! return false; } } From de7c29dcc7e3fc610c08b253ead9cfaf430109a9 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 20 Jan 2013 21:33:35 -0800 Subject: [PATCH 045/116] add comms_stop() to jni and use it --- xwords4/android/XWords4/jni/xwjni.c | 17 +++++++++++++++-- .../org/eehouse/android/xw4/jni/JNIThread.java | 3 +++ .../src/org/eehouse/android/xw4/jni/XwJNI.java | 1 + 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/xwords4/android/XWords4/jni/xwjni.c b/xwords4/android/XWords4/jni/xwjni.c index 535405ece..eb76f49ff 100644 --- a/xwords4/android/XWords4/jni/xwjni.c +++ b/xwords4/android/XWords4/jni/xwjni.c @@ -995,8 +995,21 @@ Java_org_eehouse_android_xw4_jni_XwJNI_comms_1start ( JNIEnv* env, jclass C, jint gamePtr ) { XWJNI_START(); - if ( !!state->game.comms ) { - comms_start( state->game.comms ); + CommsCtxt* comms = state->game.comms; + if ( !!comms ) { + comms_start( comms ); + } + XWJNI_END(); +} + +JNIEXPORT void JNICALL +Java_org_eehouse_android_xw4_jni_XwJNI_comms_1stop +( JNIEnv* env, jclass C, jint gamePtr ) +{ + XWJNI_START(); + CommsCtxt* comms = state->game.comms; + if ( !!comms ) { + comms_stop( comms ); } XWJNI_END(); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java index 29c3ebdb0..752b8ea7d 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java @@ -286,6 +286,9 @@ public class JNIThread extends Thread { // state. In some cases it'll otherwise drop the move. XwJNI.server_do( m_jniGamePtr ); + // And let it tell the relay (if any) it's leaving + XwJNI.comms_stop( m_jniGamePtr ); + XwJNI.game_getGi( m_jniGamePtr, m_gi ); if ( null != m_newDict ) { m_gi.dictName = m_newDict; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java index 0d869ab06..e25e1ad35 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java @@ -234,6 +234,7 @@ public class XwJNI { // Comms public static native void comms_start( int gamePtr ); + public static native void comms_stop( int gamePtr ); public static native void comms_resetSame( int gamePtr ); public static native void comms_getAddr( int gamePtr, CommsAddrRec addr ); public static native CommsAddrRec[] comms_getAddrs( int gamePtr ); From a6b3cb8cbee157d58ea45a5bf6c9708600dc1836 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 20 Jan 2013 21:34:28 -0800 Subject: [PATCH 046/116] add new param JNIThread requires --- .../eehouse/android/xw4/BoardActivity.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index 698f2144c..7a203dc5d 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -189,6 +189,23 @@ public class BoardActivity extends XWActivity return delivered; } + public static boolean feedMessage( long rowid, byte[] msg ) + { + boolean delivered = false; + synchronized( s_thisLocker ) { + if ( null != s_this ) { + Assert.assertNotNull( s_this.m_gi ); + Assert.assertNotNull( s_this.m_gameLock ); + Assert.assertNotNull( s_this.m_jniThread ); + if ( rowid == s_this.m_rowid ) { + s_this.m_jniThread.handle( JNICmd.CMD_RECEIVE, msg, null ); + delivered = true; + } + } + } + return delivered; + } + public static boolean feedMessages( long rowid, byte[][] msgs ) { boolean delivered = false; @@ -1686,7 +1703,7 @@ public class BoardActivity extends XWActivity if ( m_gi.serverRole != DeviceRole.SERVER_STANDALONE ) { m_xport = new CommsTransport( m_jniGamePtr, this, this, - m_gi.serverRole ); + m_rowid, m_gi.serverRole ); } CommonPrefs cp = CommonPrefs.get( this ); From ae9ec3186341f5835c841ac12c39eef8b20d3284 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 21 Jan 2013 18:57:48 -0800 Subject: [PATCH 047/116] don't call g_main_loop_quit() from inside signal handler: it doesn't work on Ubuntu LTS at least. Instead write to a "quit pipe" and exit the loop from the pipe's reader back outside exception context. --- xwords4/linux/cursesmain.c | 15 ++++++++++++++- xwords4/linux/cursesmain.h | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index 24eece97d..fb7862604 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -969,7 +969,7 @@ SIGWINCH_handler( int signal ) static void SIGINTTERM_handler( int XP_UNUSED(signal) ) { - (void)handleQuit( &g_globals ); + write( g_globals.quitPipe[1], "0", 1 ); } static void @@ -1671,6 +1671,15 @@ relay_error_curses( void* XP_UNUSED(closure), XWREASON XP_UNUSED_DBG(relayErr) ) } #ifdef USE_GLIBLOOP +static gboolean +read_quit( GIOChannel* XP_UNUSED(source), GIOCondition XP_UNUSED(condition), + gpointer data ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)data; + handleQuit( globals ); + return TRUE; +} + static gboolean handle_stdin( GIOChannel* XP_UNUSED_DBG(source), GIOCondition condition, gpointer data ) @@ -1758,6 +1767,10 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) #ifdef USE_GLIBLOOP cursesListenOnSocket( &g_globals, 0, handle_stdin ); setOneSecondTimer( &g_globals.cGlobals ); + + int result = pipe( g_globals.quitPipe ); + assert( 0 == result ); + cursesListenOnSocket( &g_globals, g_globals.quitPipe[0], read_quit ); #else cursesListenOnSocket( &g_globals, 0 ); /* stdin */ diff --git a/xwords4/linux/cursesmain.h b/xwords4/linux/cursesmain.h index 29e63e1e3..f520c9eeb 100644 --- a/xwords4/linux/cursesmain.h +++ b/xwords4/linux/cursesmain.h @@ -86,6 +86,7 @@ struct CursesAppGlobals { #ifdef USE_GLIBLOOP GMainLoop* loop; GList* sources; + int quitPipe[2]; #else XP_Bool timeToExit; short fdCount; From d374d0f3d800ced764ea8bbb263b0b9e96110f46 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 21 Jan 2013 19:39:57 -0800 Subject: [PATCH 048/116] optionally (compile-time change for now) store message data as b64-encoded TEXT instead of BYTEA, which on my laptop is getting corrupted sometimes. --- xwords4/relay/dbmgr.cpp | 92 +++++++++++++++++++++++++--------------- xwords4/relay/dbmgr.h | 1 + xwords4/relay/xwrelay.sh | 1 + 3 files changed, 60 insertions(+), 34 deletions(-) diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index 8f82b5514..84528a1c0 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -27,6 +27,8 @@ #include #include +#include + #include "dbmgr.h" #include "mlock.h" #include "configs.h" @@ -60,6 +62,7 @@ DBMgr::Get() DBMgr::DBMgr() { logf( XW_LOGINFO, "%s called", __func__ ); + m_useB64 = false; pthread_key_create( &m_conn_key, destr_function ); @@ -785,30 +788,67 @@ DBMgr::StoreMessage( const char* const connName, int hid, size_t newLen; const char* fmt = "INSERT INTO " MSGS_TABLE - " (connname, hid, devid, token, msg, msglen)" + " (connname, hid, devid, token, %s, msglen)" " VALUES( '%s', %d, %d, " "(SELECT tokens[%d] from " GAMES_TABLE " where connname='%s'), " - "E'%s', %d)"; - - unsigned char* bytes = PQescapeByteaConn( getThreadConn(), buf, - len, &newLen ); - assert( NULL != bytes ); + "%s'%s', %d)"; string query; - string_printf( query, fmt, connName, hid, devID, hid, connName, - bytes, len ); - - PQfreemem( bytes ); + if ( m_useB64 ) { + gchar* b64 = g_base64_encode( buf, len ); + string_printf( query, fmt, "msg64", connName, hid, devID, hid, connName, + "", b64, len ); + g_free( b64 ); + } else { + unsigned char* bytes = PQescapeByteaConn( getThreadConn(), buf, + len, &newLen ); + assert( NULL != bytes ); + + string_printf( query, fmt, "msg", connName, hid, devID, hid, connName, + "E", bytes, len ); + PQfreemem( bytes ); + } logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); execSql( query ); } +void +decodeMessage( PGresult* result, bool useB64, int b64indx, int byteaIndex, + unsigned char* buf, size_t* buflen ) +{ + const char* from = NULL; + if ( useB64 ) { + from = PQgetvalue( result, 0, b64indx ); + } + if ( NULL == from ) { + useB64 = false; + from = PQgetvalue( result, 0, byteaIndex ); + } + + size_t to_length; + if ( useB64 ) { + gsize out_len; + guchar* txt = g_base64_decode( (const gchar*)from, &out_len ); + to_length = out_len; + assert( to_length <= *buflen ); + memcpy( buf, txt, to_length ); + g_free( txt ); + } else { + unsigned char* bytes = PQunescapeBytea( (const unsigned char*)from, + &to_length ); + assert( to_length <= *buflen ); + memcpy( buf, bytes, to_length ); + PQfreemem( bytes ); + } + *buflen = to_length; +} + bool DBMgr::GetNthStoredMessage( const char* const connName, int hid, int nn, unsigned char* buf, size_t* buflen, int* msgID ) { - const char* fmt = "SELECT id, msg, msglen FROM " MSGS_TABLE + const char* fmt = "SELECT id, msg, msg64, msglen FROM " MSGS_TABLE " WHERE connName = '%s' AND hid = %d " #ifdef HAVE_STIME "AND stime IS NULL " @@ -827,18 +867,9 @@ DBMgr::GetNthStoredMessage( const char* const connName, int hid, int nn, if ( NULL != msgID ) { *msgID = atoi( PQgetvalue( result, 0, 0 ) ); } - size_t msglen = atoi( PQgetvalue( result, 0, 2 ) ); - - /* int len = PQgetlength( result, 0, 1 ); */ - const unsigned char* from = - (const unsigned char* )PQgetvalue( result, 0, 1 ); - size_t to_length; - unsigned char* bytes = PQunescapeBytea( from, &to_length ); - assert( to_length <= *buflen ); - memcpy( buf, bytes, to_length ); - PQfreemem( bytes ); - *buflen = to_length; - assert( 0 == msglen || to_length == msglen ); + size_t msglen = atoi( PQgetvalue( result, 0, 3 ) ); + decodeMessage( result, m_useB64, 2, 1, buf, buflen ); + assert( 0 == msglen || msglen == *buflen ); } PQclear( result ); return found; @@ -855,7 +886,7 @@ bool DBMgr::GetStoredMessage( int msgID, unsigned char* buf, size_t* buflen, AddrInfo::ClientToken* token ) { - const char* fmt = "SELECT token, msg, msglen FROM " MSGS_TABLE + const char* fmt = "SELECT token, msg, msg64, msglen FROM " MSGS_TABLE " WHERE id = %d " #ifdef HAVE_STIME "AND stime IS NULL " @@ -872,16 +903,9 @@ DBMgr::GetStoredMessage( int msgID, unsigned char* buf, size_t* buflen, bool found = nTuples == 1; if ( found ) { *token = atoi( PQgetvalue( result, 0, 0 ) ); - size_t msglen = atoi( PQgetvalue( result, 0, 2 ) ); - const unsigned char* from = - (const unsigned char* )PQgetvalue( result, 0, 1 ); - size_t to_length; - unsigned char* bytes = PQunescapeBytea( from, &to_length ); - assert( to_length <= *buflen ); - memcpy( buf, bytes, to_length ); - PQfreemem( bytes ); - *buflen = to_length; - assert( 0 == msglen || to_length == msglen ); + size_t msglen = atoi( PQgetvalue( result, 0, 3 ) ); + decodeMessage( result, m_useB64, 2, 1, buf, buflen ); + assert( 0 == msglen || *buflen == msglen ); } PQclear( result ); return found; diff --git a/xwords4/relay/dbmgr.h b/xwords4/relay/dbmgr.h index ce3a0abc1..6218e1aa2 100644 --- a/xwords4/relay/dbmgr.h +++ b/xwords4/relay/dbmgr.h @@ -128,6 +128,7 @@ class DBMgr { void conn_key_alloc(); pthread_key_t m_conn_key; + bool m_useB64; }; /* DBMgr */ diff --git a/xwords4/relay/xwrelay.sh b/xwords4/relay/xwrelay.sh index 52da03349..7e60ad072 100755 --- a/xwords4/relay/xwrelay.sh +++ b/xwords4/relay/xwrelay.sh @@ -72,6 +72,7 @@ id SERIAL ,stime TIMESTAMP DEFAULT NULL ,devid INTEGER ,msg BYTEA +,msg64 TEXT ,msglen INTEGER ,UNIQUE ( connName, hid, msg ) ); From 2744e8698c6d24bdfbb0e2297b94bd94f418432c Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 21 Jan 2013 19:40:20 -0800 Subject: [PATCH 049/116] optionally (compile-time change for now) store message data as b64-encoded TEXT instead of BYTEA, which on my laptop is getting corrupted sometimes. --- xwords4/relay/Makefile | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/xwords4/relay/Makefile b/xwords4/relay/Makefile index 2421f746c..04a3555bb 100644 --- a/xwords4/relay/Makefile +++ b/xwords4/relay/Makefile @@ -41,12 +41,14 @@ HASH=$(shell git describe) OBJ = $(patsubst %.cpp,%.o,$(SRC)) #LDFLAGS += -pthread -g -lmcheck $(STATIC) -LDFLAGS += -pthread -g $(STATIC) \ - -L$(shell pg_config --libdir) +LDFLAGS += -pthread -g $(STATIC) +LDFLAGS += -L$(shell pg_config --libdir) +LDFLAGS += $(shell pkg-config --libs glib-2.0) -CPPFLAGS += -DSPAWN_SELF -g -Wall \ - -I $(shell pg_config --includedir) \ - -DSVN_REV=\"$(shell cat $(GITINFO) 2>/dev/null || echo -n $(HASH) )\" +CPPFLAGS += -DSPAWN_SELF -g -Wall +CPPFLAGS += -I $(shell pg_config --includedir) +CPPFLAGS += -DSVN_REV=\"$(shell cat $(GITINFO) 2>/dev/null || echo -n $(HASH) )\" +CPPFLAGS += $(shell pkg-config --cflags glib-2.0) # CPPFLAGS += -DDO_HTTP # CPPFLAGS += -DHAVE_STIME @@ -58,7 +60,7 @@ memdebug all: xwrelay rq # Manual config in order to place -lpq after the .obj files as # required by something Ubuntu did upgrading natty to oneiric xwrelay: $(OBJ) - $(CXX) $(CPPFLAGS) $(LDFLAGS) -o $@ $^ -lpq + $(CXX) $(CPPFLAGS) -o $@ $^ -lpq $(LDFLAGS) rq: rq.c From 90b8f966f6fbcadbca5cfb00d99bf32f8b4a3142 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 21 Jan 2013 19:40:56 -0800 Subject: [PATCH 050/116] comment out unnecessary assert --- xwords4/relay/xwrelay.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index 1e2e5eeb7..298a47461 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -533,9 +533,9 @@ processReconnect( const unsigned char* bufp, int bufLen, const AddrInfo* addr ) wantsPublic, makePublic ); success = scr.Reconnect( srcID, nPlayersH, nPlayersT, gameSeed, &err ); - if ( !success ) { - assert( err != XWRELAY_ERROR_NONE ); - } + // if ( !success ) { + // assert( err != XWRELAY_ERROR_NONE ); + // } } else { err = XWRELAY_ERROR_BADPROTO; } @@ -1218,8 +1218,9 @@ msgToStr( XWRelayReg msg ) CASE_STR(XWPDEV_MSGNOCONN); CASE_STR(XWPDEV_MSGRSP); CASE_STR(XWPDEV_BADREG); + CASE_STR(XWPDEV_ALERT); // should not receive this.... default: - assert(0); + str = ""; break; } # undef CASE_STR From 29acab1ef28f85fef349a6a1742de6357ec30f64 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 21 Jan 2013 19:44:08 -0800 Subject: [PATCH 051/116] tweak display --- xwords4/relay/scripts/showinplay.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xwords4/relay/scripts/showinplay.sh b/xwords4/relay/scripts/showinplay.sh index 3d2c25651..dd9e940dd 100755 --- a/xwords4/relay/scripts/showinplay.sh +++ b/xwords4/relay/scripts/showinplay.sh @@ -23,10 +23,11 @@ done QUERY="WHERE NOT -NTOTAL = sum_array(nperdevice)" -echo "Device (pid) count: $(pidof xwords | wc | awk '{print $2}')" +echo -n "Device (pid) count: $(pidof xwords | wc | awk '{print $2}')" +echo "; relay pid[s]: $(pidof xwrelay)" echo "Row count:" $(psql -t xwgames -c "select count(*) FROM games $QUERY;") -echo "SELECT dead,connname,cid,room,lang,clntVers,ntotal,nperdevice,seeds,addrs,tokens,ack,nsent "\ +echo "SELECT dead,connname,cid,room,lang,clntVers as cv ,ntotal,nperdevice,seeds,addrs,tokens,devids,ack,nsent as snt "\ "FROM games $QUERY ORDER BY NOT dead, connname LIMIT $LIMIT;" \ | psql xwgames From c839db4a25683c551bbb0e44aa25d945a4a3e063 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 22 Jan 2013 06:12:55 -0800 Subject: [PATCH 052/116] disconnect from relay no matter what the state --- xwords4/common/comms.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c index 245fcefa2..5b8c9ffce 100644 --- a/xwords4/common/comms.c +++ b/xwords4/common/comms.c @@ -666,8 +666,7 @@ comms_start( CommsCtxt* comms ) void comms_stop( CommsCtxt* comms ) { - if ( COMMS_CONN_RELAY == comms->addr.conType - && COMMS_RELAYSTATE_DENIED != comms->r.relayState ) { + if ( COMMS_CONN_RELAY == comms->addr.conType ) { relayDisconnect( comms ); } } From 2d7977e4b5da8d72416cc69b6f0c6c183ba2714f Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 22 Jan 2013 06:52:50 -0800 Subject: [PATCH 053/116] fix test: unset TEXT column returns as "", not NULL. --- xwords4/relay/dbmgr.cpp | 6 +++--- xwords4/relay/dbmgr.h | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index 84528a1c0..6d015378c 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -814,14 +814,14 @@ DBMgr::StoreMessage( const char* const connName, int hid, } void -decodeMessage( PGresult* result, bool useB64, int b64indx, int byteaIndex, - unsigned char* buf, size_t* buflen ) +DBMgr::decodeMessage( PGresult* result, bool useB64, int b64indx, int byteaIndex, + unsigned char* buf, size_t* buflen ) { const char* from = NULL; if ( useB64 ) { from = PQgetvalue( result, 0, b64indx ); } - if ( NULL == from ) { + if ( NULL == from || '\0' == from[0] ) { useB64 = false; from = PQgetvalue( result, 0, byteaIndex ); } diff --git a/xwords4/relay/dbmgr.h b/xwords4/relay/dbmgr.h index 6218e1aa2..ec716422b 100644 --- a/xwords4/relay/dbmgr.h +++ b/xwords4/relay/dbmgr.h @@ -123,6 +123,8 @@ class DBMgr { DevIDRelay getDevID( const DevID* devID ); int getCountWhere( const char* table, string& test ); void RemoveStoredMessages( string& msgIDs ); + void decodeMessage( PGresult* result, bool useB64, int b64indx, + int byteaIndex, unsigned char* buf, size_t* buflen ); PGconn* getThreadConn( void ); From a3e4e312056052f08924a795e732c0cece02b5c1 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 23 Jan 2013 07:28:25 -0800 Subject: [PATCH 054/116] remove logging --- .../XWords4/src/org/eehouse/android/xw4/ExpiringTextView.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/ExpiringTextView.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/ExpiringTextView.java index 8bc674dc8..a122ee817 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/ExpiringTextView.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/ExpiringTextView.java @@ -49,8 +49,6 @@ class ExpiringTextView extends TextView { { if ( null != m_delegate ) { m_delegate.configure( haveTurn, haveTurnLocal, startSecs ); - } else { - DbgUtils.logf( "m_delegate null; skipping" ); } } From ff70e3fbc25974bc5571e3c50696b22aa8000312 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 23 Jan 2013 07:35:20 -0800 Subject: [PATCH 055/116] fix use of map.insert: check for existing key and overwrite value --- xwords4/relay/devmgr.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/xwords4/relay/devmgr.cpp b/xwords4/relay/devmgr.cpp index 2069ede63..724222f65 100644 --- a/xwords4/relay/devmgr.cpp +++ b/xwords4/relay/devmgr.cpp @@ -38,8 +38,17 @@ DevMgr::Remember( DevIDRelay devid, const AddrInfo::AddrUnion* saddr ) logf( XW_LOGINFO, "%s(devid=%d)", __func__, devid ); time_t now = time( NULL ); UDPAddrRec rec( saddr, now ); + MutexLock ml( &m_mapLock ); - m_devAddrMap.insert( pair( devid, rec ) ); + + // C++'s insert doesn't replace, but the result tells whether the key was + // already there and provides an iterator via which it can be updated + pair::iterator, bool> result = + m_devAddrMap.insert( pair( devid, rec ) ); + if ( !result.second ) { + result.first->second = rec; + } + logf( XW_LOGINFO, "dev->addr map now contains %d entries", m_devAddrMap.size() ); } From ce7cdab3f0ca0a65ebf9a79894148bf52176e272 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 23 Jan 2013 07:40:52 -0800 Subject: [PATCH 056/116] add msgid and acking to protocol -- maybe for debugging only. --- xwords4/relay/Makefile | 1 + xwords4/relay/udpack.cpp | 23 ++++++++++ xwords4/relay/udpack.h | 36 +++++++++++++++ xwords4/relay/xwrelay.cpp | 97 ++++++++++++++++++++++++++------------- xwords4/relay/xwrelay.h | 3 ++ 5 files changed, 129 insertions(+), 31 deletions(-) create mode 100644 xwords4/relay/udpack.cpp create mode 100644 xwords4/relay/udpack.h diff --git a/xwords4/relay/Makefile b/xwords4/relay/Makefile index 04a3555bb..d465f3b65 100644 --- a/xwords4/relay/Makefile +++ b/xwords4/relay/Makefile @@ -33,6 +33,7 @@ SRC = \ addrinfo.cpp \ devmgr.cpp \ udpqueue.cpp \ + udpack.cpp \ xwrelay.cpp \ # STATIC ?= -static diff --git a/xwords4/relay/udpack.cpp b/xwords4/relay/udpack.cpp new file mode 100644 index 000000000..0b01aef5f --- /dev/null +++ b/xwords4/relay/udpack.cpp @@ -0,0 +1,23 @@ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2013 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program 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 program 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. + */ + +#include "udpack.h" + +uint32_t UDPAckTrack::s_nextID = 0; + diff --git a/xwords4/relay/udpack.h b/xwords4/relay/udpack.h new file mode 100644 index 000000000..b161050ea --- /dev/null +++ b/xwords4/relay/udpack.h @@ -0,0 +1,36 @@ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2013 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program 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 program 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. + */ + +#ifndef _UDPACK_H_ +#define _UDPACK_H_ + +#include "xwrelay_priv.h" + +class UDPAckTrack { + public: + static uint32_t nextPacketID() { return ++s_nextID; } + static void recordAck( uint32_t packetID ) { + logf( XW_LOGINFO, "received ack for %d", packetID ); + } + + private: + static uint32_t s_nextID; +}; + +#endif diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index 298a47461..e2f1e6703 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -78,6 +78,7 @@ #include "addrinfo.h" #include "devmgr.h" #include "udpqueue.h" +#include "udpack.h" static int s_nSpawns = 0; static int g_maxsocks = -1; @@ -353,10 +354,44 @@ denyConnection( const AddrInfo* addr, XWREASON err ) static ssize_t send_via_udp( int socket, const struct sockaddr *dest_addr, - const unsigned char* buf, int buflen ) + XWRelayReg cmd, ... ) { - ssize_t nSent = sendto( socket, buf, buflen, - 0, /* flags */ dest_addr, sizeof(*dest_addr) ); + uint32_t packetNum = UDPAckTrack::nextPacketID(); + struct iovec vec[10]; + int iocount = 0; + + unsigned char header[1 + 1 + sizeof(packetNum)]; + header[0] = XWPDEV_PROTO_VERSION; + packetNum = htonl( packetNum ); + memcpy( &header[1], &packetNum, sizeof(packetNum) ); + header[5] = cmd; + vec[iocount].iov_base = header; + vec[iocount].iov_len = sizeof(header); + ++iocount; + + va_list ap; + va_start( ap, cmd ); + for ( ; ; ) { + unsigned char* ptr = va_arg(ap, unsigned char*); + if ( !ptr ) { + break; + } + vec[iocount].iov_base = ptr; + vec[iocount].iov_len = va_arg(ap, int); + ++iocount; + } + va_end( ap ); + + struct msghdr mhdr = {0}; + mhdr.msg_iov = vec; + mhdr.msg_iovlen = iocount; + mhdr.msg_name = (void*)dest_addr; + mhdr.msg_namelen = sizeof(*dest_addr); + + ssize_t nSent = sendmsg( socket, &mhdr, 0 /* flags */); + if ( 0 > nSent ) { + logf( XW_LOGERROR, "sendmsg->errno %d (%s)", errno, strerror(errno) ); + } logf( XW_LOGINFO, "%s()=>%d", __func__, nSent ); return nSent; } @@ -384,18 +419,14 @@ send_with_length_unsafe( const AddrInfo* addr, const unsigned char* buf, } else { AddrInfo::ClientToken clientToken = addr->clientToken(); assert( 0 != clientToken ); - unsigned char tmpbuf[1 + 1 + sizeof(clientToken) + bufLen]; - tmpbuf[0] = XWPDEV_PROTO_VERSION; - tmpbuf[1] = XWPDEV_MSG; clientToken = htonl(clientToken); - memcpy( &tmpbuf[2], &clientToken, sizeof(clientToken) ); - memcpy( &tmpbuf[2 + sizeof(clientToken)], buf, bufLen ); const struct sockaddr* saddr = addr->sockaddr(); assert( g_udpsock == socket || socket == -1 ); if ( -1 == socket ) { socket = g_udpsock; } - send_via_udp( socket, saddr, tmpbuf, sizeof(tmpbuf) ); + send_via_udp( socket, saddr, XWPDEV_MSG, &clientToken, + sizeof(clientToken), buf, bufLen, NULL ); logf( XW_LOGINFO, "sent %d bytes on UDP socket %d", bufLen, socket ); ok = true; } @@ -417,12 +448,9 @@ send_havemsgs( const AddrInfo* addr ) } AddrInfo::ClientToken clientToken = htonl(addr->clientToken()); - unsigned char tmpbuf[1 + 1 + sizeof(clientToken)]; - tmpbuf[0] = XWPDEV_PROTO_VERSION; - tmpbuf[1] = XWPDEV_HAVEMSGS; - memcpy( &tmpbuf[2], &clientToken, sizeof(clientToken) ); - - send_via_udp( socket, addr->sockaddr(), tmpbuf, sizeof(tmpbuf) ); + send_via_udp( socket, addr->sockaddr(), XWPDEV_HAVEMSGS, + &clientToken, sizeof(clientToken), + NULL ); } /* A CONNECT message from a device gives us the hostID and socket we'll @@ -1146,6 +1174,7 @@ registerDevice( const DevID* devID, const AddrInfo::AddrUnion* saddr ) DBMgr* dbMgr = DBMgr::Get(); short indx = 0; unsigned char buf[32]; + if ( ID_TYPE_RELAY == devID->m_devIDType ) { // known to us; just update the time relayID = devID->asRelayID(); if ( dbMgr->updateDevice( relayID, true ) ) { @@ -1155,21 +1184,19 @@ registerDevice( const DevID* devID, const AddrInfo::AddrUnion* saddr ) send_havemsgs( &addr ); } } else { - relayID = DBMgr::DEVID_NONE; - - buf[indx++] = XWPDEV_PROTO_VERSION; - buf[indx++] = XWPDEV_BADREG; indx += addRegID( &buf[indx], relayID ); - send_via_udp( g_udpsock, &saddr->addr, buf, indx ); + send_via_udp( g_udpsock, &saddr->addr, XWPDEV_BADREG, buf, indx, + NULL ); + + relayID = DBMgr::DEVID_NONE; } } else { relayID = dbMgr->RegisterDevice( devID ); if ( DBMgr::DEVID_NONE != relayID ) { // send it back to the device - buf[indx++] = XWPDEV_PROTO_VERSION; - buf[indx++] = XWPDEV_REGRSP; indx += addRegID( &buf[indx], relayID ); - send_via_udp( g_udpsock, &saddr->addr, buf, sizeof(buf) ); + send_via_udp( g_udpsock, &saddr->addr, XWPDEV_REGRSP, buf, + indx, NULL ); } } @@ -1219,6 +1246,7 @@ msgToStr( XWRelayReg msg ) CASE_STR(XWPDEV_MSGRSP); CASE_STR(XWPDEV_BADREG); CASE_STR(XWPDEV_ALERT); // should not receive this.... + CASE_STR(XWPDEV_ACK); default: str = ""; break; @@ -1238,6 +1266,7 @@ udp_thread_proc( UdpThreadClosure* utc ) if ( XWPDEV_PROTO_VERSION != 0 ) { logf( XW_LOGERROR, "unexpected proto %d", __func__, (int) proto ); } else { + ptr += 4; // skip msgid XWRelayReg msg = (XWRelayReg)*ptr++; logf( XW_LOGINFO, "%s(msg=%s)", __func__, msgToStr( msg ) ); switch( msg ) { @@ -1286,7 +1315,8 @@ udp_thread_proc( UdpThreadClosure* utc ) handlePutMessage( scr, hid, &addr, end - ptr, &ptr, end ); assert( ptr == end ); // DON'T CHECK THIS IN!!! } else { - logf( XW_LOGERROR, "%s: invalid scr for %s", __func__, connName ); + logf( XW_LOGERROR, "%s: invalid scr for %s", __func__, + connName ); } } else { logf( XW_LOGERROR, "no clientToken found!!!" ); @@ -1309,7 +1339,13 @@ udp_thread_proc( UdpThreadClosure* utc ) retrieveMessages( devID, utc->saddr() ); break; } - + case XWPDEV_ACK: { + uint32_t packetID; + if ( getNetLong( &ptr, end, &packetID ) ) { + UDPAckTrack::recordAck( packetID ); + } + break; + } default: logf( XW_LOGERROR, "%s: unexpected msg %d", __func__, msg ); } @@ -1423,12 +1459,10 @@ maint_str_loop( int udpsock, const char* str ) logf( XW_LOGINFO, "%s()", __func__ ); assert( -1 != udpsock ); short len = strlen(str); - unsigned char outbuf[2 + sizeof(len) + len]; - outbuf[0] = XWPDEV_PROTO_VERSION; - outbuf[1] = XWPDEV_ALERT; + unsigned char outbuf[sizeof(len) + len]; short lenNS = htons( len ); - memcpy( &outbuf[2], &lenNS, sizeof(lenNS) ); - memcpy( &outbuf[2+sizeof(len)], str, len ); + memcpy( &outbuf[0], &lenNS, sizeof(lenNS) ); + memcpy( &outbuf[0+sizeof(len)], str, len ); fd_set rfds; for ( ; ; ) { @@ -1451,7 +1485,8 @@ maint_str_loop( int udpsock, const char* str ) logf( XW_LOGINFO, "%s(); got %d bytes", __func__, nRead); if ( 1 <= nRead && buf[0] == XWPDEV_PROTO_VERSION ) { - send_via_udp( udpsock, &saddr.addr, outbuf, sizeof(outbuf) ); + send_via_udp( udpsock, &saddr.addr, XWPDEV_ALERT, + outbuf, sizeof(outbuf), NULL ); } else { logf( XW_LOGERROR, "unexpected data" ); } diff --git a/xwords4/relay/xwrelay.h b/xwords4/relay/xwrelay.h index a7f27a230..0365a9b2a 100644 --- a/xwords4/relay/xwrelay.h +++ b/xwords4/relay/xwrelay.h @@ -75,6 +75,8 @@ enum { XWPDEV_NONE /* 0 is an illegal value */ ,XWPDEV_BADREG /* relay->dev. You sent me a relayID via XWPDEV_REG but I've never heard of it */ + ,XWPDEV_ACK + } #ifndef CANT_DO_TYPEDEF XWRelayReg @@ -164,6 +166,7 @@ typedef enum { ,ID_TYPE_RELAY /* assigned by relay as replacement for one of the below */ ,ID_TYPE_LINUX ,ID_TYPE_ANDROID_GCM + ,ID_TYPE_ANDROID_OTHER ,ID_TYPE_NTYPES } DevIDType; From 12f70154a09df17f107850ea5dab933e4bafa014 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 23 Jan 2013 07:43:58 -0800 Subject: [PATCH 057/116] changes for includes-messageID-please-ack protocol addition --- xwords4/linux/gtkboard.c | 5 +- xwords4/linux/gtkboard.h | 2 +- xwords4/linux/gtkmain.c | 2 +- xwords4/linux/linuxmain.c | 2 +- xwords4/linux/linuxmain.h | 2 +- xwords4/linux/relaycon.c | 156 +++++++++++++++++++++++++------------- xwords4/linux/relaycon.h | 2 +- 7 files changed, 112 insertions(+), 59 deletions(-) diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 1b7457a38..935f49839 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -539,6 +539,7 @@ createOrLoadObjects( GtkGameGlobals* globals ) #ifndef XWFEATURE_STANDALONE_ONLY /* This may trigger network activity */ if ( !!cGlobals->game.comms ) { + XP_ASSERT( COMMS_CONN_RELAY == addr.conType ); comms_setAddr( cGlobals->game.comms, &addr ); } #endif @@ -2622,13 +2623,15 @@ makeNewGame( GtkGameGlobals* globals ) cGlobals->dict = linux_dictionary_make( MEMPOOL cGlobals->params, gi->dictName, XP_TRUE ); + gi->dictLang = dict_getLangCode( cGlobals->dict ); } LOG_RETURNF( "%d", success ); return success; } void -gameGotBuf( GtkGameGlobals* globals, XP_Bool hasDraw, XP_U8* buf, XP_U16 len ) +gameGotBuf( GtkGameGlobals* globals, XP_Bool hasDraw, const XP_U8* buf, + XP_U16 len ) { XP_LOGF( "%s(hasDraw=%d)", __func__, hasDraw ); XP_Bool redraw = XP_FALSE; diff --git a/xwords4/linux/gtkboard.h b/xwords4/linux/gtkboard.h index 4861f912e..4253b5cb6 100644 --- a/xwords4/linux/gtkboard.h +++ b/xwords4/linux/gtkboard.h @@ -170,7 +170,7 @@ void initGlobals( GtkGameGlobals* globals, LaunchParams* params ); void freeGlobals( GtkGameGlobals* globals ); XP_Bool makeNewGame( GtkGameGlobals* globals ); void gameGotBuf( GtkGameGlobals* globals, XP_Bool haveDraw, - XP_U8* buf, XP_U16 len ); + const XP_U8* buf, XP_U16 len ); XP_Bool loadGameNoDraw( GtkGameGlobals* globals, LaunchParams* params, sqlite3* pDb, sqlite3_int64 rowid ); void destroy_board_window( GtkWidget* widget, GtkGameGlobals* globals ); diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index c620f2552..cc57d1c89 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -385,7 +385,7 @@ socketChanged( void* closure, int newSock, int XP_UNUSED(oldSock), } static void -gtkGotBuf( void* closure, XP_U8* buf, XP_U16 len ) +gtkGotBuf( void* closure, const XP_U8* buf, XP_U16 len ) { LOG_FUNC(); GtkAppGlobals* apg = (GtkAppGlobals*)closure; diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index e5510a2c8..c25f5b7f9 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -1065,7 +1065,7 @@ linux_relay_receive( CommonGlobals* cGlobals, unsigned char* buf, int bufSize ) information specific to our platform's comms layer (return address, say) */ XWStreamCtxt* -stream_from_msgbuf( CommonGlobals* globals, unsigned char* bufPtr, +stream_from_msgbuf( CommonGlobals* globals, const unsigned char* bufPtr, XP_U16 nBytes ) { XWStreamCtxt* result; diff --git a/xwords4/linux/linuxmain.h b/xwords4/linux/linuxmain.h index 1519a5182..c52f0c356 100644 --- a/xwords4/linux/linuxmain.h +++ b/xwords4/linux/linuxmain.h @@ -55,7 +55,7 @@ XP_Bool linuxFireTimer( CommonGlobals* cGlobals, XWTimerReason why ); XWStreamCtxt* stream_from_msgbuf( CommonGlobals* cGlobals, - unsigned char* bufPtr, XP_U16 nBytes ); + const unsigned char* bufPtr, XP_U16 nBytes ); XP_UCHAR* strFromStream( XWStreamCtxt* stream ); void catGameHistory( CommonGlobals* cGlobals ); diff --git a/xwords4/linux/relaycon.c b/xwords4/linux/relaycon.c index 7025b5cc2..0b14ded54 100644 --- a/xwords4/linux/relaycon.c +++ b/xwords4/linux/relaycon.c @@ -19,6 +19,7 @@ #include #include +#include #include "relaycon.h" #include "comtypes.h" @@ -30,13 +31,20 @@ typedef struct _RelayConStorage { struct sockaddr_in saddr; } RelayConStorage; +typedef struct _MsgHeader { + XWRelayReg cmd; + uint32_t packetID; +} MsgHeader; + static RelayConStorage* getStorage( LaunchParams* params ); static XP_U32 hostNameToIP( const XP_UCHAR* name ); static void relaycon_receive( void* closure, int socket ); static ssize_t sendIt( RelayConStorage* storage, const XP_U8* msgbuf, XP_U16 len ); static size_t addStrWithLength( XP_U8* buf, XP_U8* end, const XP_UCHAR* str ); -static void getNetString( XP_U8** ptr, XP_U16 len, XP_UCHAR* buf ); -static XP_U16 getNetShort( XP_U8** ptr ); +static void getNetString( const XP_U8** ptr, XP_U16 len, XP_UCHAR* buf ); +static XP_U16 getNetShort( const XP_U8** ptr ); +static int writeHeader( XP_U8* dest, XWRelayReg cmd ); +static bool readHeader( const XP_U8** buf, MsgHeader* header ); void relaycon_init( LaunchParams* params, const RelayConnProcs* procs, @@ -68,8 +76,7 @@ relaycon_reg( LaunchParams* params, const XP_UCHAR* devID, DevIDType typ ) XP_ASSERT( !!devID ); XP_U16 idLen = XP_STRLEN( devID ); XP_U16 lenNBO = XP_HTONS( idLen ); - tmpbuf[indx++] = XWPDEV_PROTO_VERSION; - tmpbuf[indx++] = XWPDEV_REG; + indx += writeHeader( tmpbuf, XWPDEV_REG ); tmpbuf[indx++] = typ; XP_MEMCPY( &tmpbuf[indx], &lenNBO, sizeof(lenNBO) ); indx += sizeof(lenNBO); @@ -86,13 +93,15 @@ relaycon_send( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, ssize_t nSent = -1; RelayConStorage* storage = getStorage( params ); - XP_U8 tmpbuf[1 + 1 + sizeof(gameToken) + buflen]; - tmpbuf[0] = XWPDEV_PROTO_VERSION; - tmpbuf[1] = XWPDEV_MSG; + XP_U8 tmpbuf[1 + 4 + 1 + sizeof(gameToken) + buflen]; + int indx = 0; + indx += writeHeader( tmpbuf, XWPDEV_MSG ); XP_U32 inNBO = htonl(gameToken); - XP_MEMCPY( &tmpbuf[2], &inNBO, sizeof(inNBO) ); - XP_MEMCPY( &tmpbuf[1 + 1 + sizeof(gameToken)], buf, buflen ); - nSent = sendIt( storage, tmpbuf, sizeof(tmpbuf) ); + XP_MEMCPY( &tmpbuf[indx], &inNBO, sizeof(inNBO) ); + indx += sizeof(inNBO); + XP_MEMCPY( &tmpbuf[indx], buf, buflen ); + indx += buflen; + nSent = sendIt( storage, tmpbuf, indx ); if ( nSent > buflen ) { nSent = buflen; } @@ -110,11 +119,10 @@ relaycon_sendnoconn( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, RelayConStorage* storage = getStorage( params ); XP_U16 idLen = XP_STRLEN( relayID ); - XP_U8 tmpbuf[1 + 1 + + XP_U8 tmpbuf[1 + 4 + 1 + 1 + idLen + sizeof(gameToken) + buflen]; - tmpbuf[indx++] = XWPDEV_PROTO_VERSION; - tmpbuf[indx++] = XWPDEV_MSGNOCONN; + indx += writeHeader( tmpbuf, XWPDEV_MSGNOCONN ); gameToken = htonl( gameToken ); XP_MEMCPY( &tmpbuf[indx], &gameToken, sizeof(gameToken) ); indx += sizeof(gameToken); @@ -138,13 +146,25 @@ relaycon_requestMsgs( LaunchParams* params, const XP_UCHAR* devID ) XP_U8 tmpbuf[128]; int indx = 0; - tmpbuf[indx++] = XWPDEV_PROTO_VERSION; - tmpbuf[indx++] = XWPDEV_RQSTMSGS; + indx += writeHeader( tmpbuf, XWPDEV_RQSTMSGS ); indx += addStrWithLength( &tmpbuf[indx], tmpbuf + sizeof(tmpbuf), devID ); sendIt( storage, tmpbuf, indx ); } +static void +sendAckIf( RelayConStorage* storage, const MsgHeader* header ) +{ + if ( header->cmd != XWPDEV_ACK ) { + XP_U8 tmpbuf[16]; + int indx = writeHeader( tmpbuf, XWPDEV_ACK ); + uint32_t msgID = htonl( header->packetID ); + memcpy( &tmpbuf[indx], &msgID, sizeof(msgID) ); + indx += sizeof(msgID); + sendIt( storage, tmpbuf, indx ); + } +} + static void relaycon_receive( void* closure, int socket ) { @@ -161,43 +181,45 @@ relaycon_receive( void* closure, int socket ) (struct sockaddr*)&from, &fromlen ); XP_LOGF( "%s: read %d bytes", __func__, nRead ); if ( 0 <= nRead ) { - XP_U8* ptr = buf; + const XP_U8* ptr = buf; const XP_U8* end = buf + nRead; - XP_ASSERT( XWPDEV_PROTO_VERSION == *ptr++ ); - XWRelayReg cmd = *ptr++; - switch( cmd ) { - case XWPDEV_REGRSP: { - XP_U16 len = getNetShort( &ptr ); - XP_UCHAR devID[len+1]; - getNetString( &ptr, len, devID ); - (*storage->procs.devIDChanged)( storage->procsClosure, devID ); - } - break; - case XWPDEV_MSG: - (*storage->procs.msgReceived)( storage->procsClosure, - ptr, end - ptr ); - break; - case XWPDEV_BADREG: - (*storage->procs.devIDChanged)( storage->procsClosure, NULL ); - break; - case XWPDEV_HAVEMSGS: { - XP_U32 gameToken; - XP_MEMCPY( &gameToken, ptr, sizeof(gameToken) ); - ptr += sizeof( gameToken ); - (*storage->procs.msgNoticeReceived)( storage->procsClosure, - ntohl(gameToken) ); - break; - } - case XWPDEV_ALERT: { - XP_U16 len = getNetShort( &ptr ); - XP_UCHAR buf[len+1]; - getNetString( &ptr, len, buf ); - (*storage->procs.msgErrorMsg)( storage->procsClosure, buf ); - break; - } - default: - XP_LOGF( "%s: Unexpected cmd %d", __func__, cmd ); - XP_ASSERT( 0 ); + MsgHeader header; + if ( readHeader( &ptr, &header ) ) { + sendAckIf( storage, &header ); + switch( header.cmd ) { + case XWPDEV_REGRSP: { + XP_U16 len = getNetShort( &ptr ); + XP_UCHAR devID[len+1]; + getNetString( &ptr, len, devID ); + (*storage->procs.devIDChanged)( storage->procsClosure, devID ); + } + break; + case XWPDEV_MSG: + (*storage->procs.msgReceived)( storage->procsClosure, + ptr, end - ptr ); + break; + case XWPDEV_BADREG: + (*storage->procs.devIDChanged)( storage->procsClosure, NULL ); + break; + case XWPDEV_HAVEMSGS: { + XP_U32 gameToken; + XP_MEMCPY( &gameToken, ptr, sizeof(gameToken) ); + ptr += sizeof( gameToken ); + (*storage->procs.msgNoticeReceived)( storage->procsClosure, + ntohl(gameToken) ); + break; + } + case XWPDEV_ALERT: { + XP_U16 len = getNetShort( &ptr ); + XP_UCHAR buf[len+1]; + getNetString( &ptr, len, buf ); + (*storage->procs.msgErrorMsg)( storage->procsClosure, buf ); + break; + } + default: + XP_LOGF( "%s: Unexpected cmd %d", __func__, header.cmd ); + XP_ASSERT( 0 ); + } } } else { XP_LOGF( "%s: error reading udp socket: %d (%s)", __func__, @@ -264,7 +286,7 @@ addStrWithLength( XP_U8* buf, XP_U8* end, const XP_UCHAR* str ) } static XP_U16 -getNetShort( XP_U8** ptr ) +getNetShort( const XP_U8** ptr ) { XP_U16 result; memcpy( &result, *ptr, sizeof(result) ); @@ -273,9 +295,37 @@ getNetShort( XP_U8** ptr ) } static void -getNetString( XP_U8** ptr, XP_U16 len, XP_UCHAR* buf ) +getNetString( const XP_U8** ptr, XP_U16 len, XP_UCHAR* buf ) { memcpy( buf, *ptr, len ); *ptr += len; buf[len] = '\0'; } + +static int +writeHeader( XP_U8* dest, XWRelayReg cmd ) +{ + int indx = 0; + dest[indx++] = XWPDEV_PROTO_VERSION; + uint32_t packetNum = htonl(0); + memcpy( &dest[indx], &packetNum, sizeof(packetNum) ); + indx += sizeof(packetNum); + dest[indx++] = cmd; + return indx; +} + +static bool +readHeader( const XP_U8** buf, MsgHeader* header ) +{ + const XP_U8* ptr = *buf; + bool ok = XWPDEV_PROTO_VERSION == *ptr++; + assert( ok ); + uint32_t packetID; + memcpy( &packetID, ptr, sizeof(packetID) ); + ptr += sizeof(packetID); + header->packetID = ntohl( packetID ); + XP_LOGF( "%s: got packet %d", __func__, header->packetID ); + header->cmd = *ptr++; + *buf = ptr; + return ok; +} diff --git a/xwords4/linux/relaycon.h b/xwords4/linux/relaycon.h index f1e67f725..1e1de55f7 100644 --- a/xwords4/linux/relaycon.h +++ b/xwords4/linux/relaycon.h @@ -23,7 +23,7 @@ #include "main.h" typedef struct _Procs { - void (*msgReceived)( void* closure, XP_U8* buf, XP_U16 len ); + void (*msgReceived)( void* closure, const XP_U8* buf, XP_U16 len ); void (*msgNoticeReceived)( void* closure, XP_U32 gameToken ); void (*devIDChanged)( void* closure, const XP_UCHAR* devID ); void (*msgErrorMsg)( void* closure, const XP_UCHAR* msg ); From da4d8412202b0b6fb4f61236d47f2e253e4c9733 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 23 Jan 2013 07:46:13 -0800 Subject: [PATCH 058/116] use per-device UDP rather than per-board TCP to communicate with relay, including latest UDP protocol and acking changes. Basically works (in emulator at least) but there are problems especially with initial game creation. --- .../eehouse/android/xw4/CommsTransport.java | 20 +- .../org/eehouse/android/xw4/GamesList.java | 33 +- .../org/eehouse/android/xw4/RelayService.java | 461 ++++++++++++++---- 3 files changed, 401 insertions(+), 113 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java index ba89de474..22c9e0bce 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java @@ -57,6 +57,7 @@ public class CommsTransport implements TransportProcs, private ByteBuffer m_bytesIn; private Context m_context; + private long m_rowid; // assembling inbound packet private byte[] m_packetIn; @@ -64,11 +65,12 @@ public class CommsTransport implements TransportProcs, public CommsTransport( int jniGamePtr, Context context, TransportProcs.TPMsgHandler handler, - DeviceRole role ) + long rowid, DeviceRole role ) { m_jniGamePtr = jniGamePtr; m_context = context; m_tpHandler = handler; + m_rowid = rowid; m_buffersOut = new Vector(); m_bytesIn = ByteBuffer.allocate( 2048 ); @@ -377,13 +379,17 @@ public class CommsTransport implements TransportProcs, switch ( addr.conType ) { case COMMS_CONN_RELAY: - if ( NetStateCache.netAvail( m_context ) ) { - putOut( buf ); // add to queue - if ( null == m_thread ) { - m_thread = new CommsThread(); - m_thread.start(); + if ( XWPrefs.getUDPEnabled( m_context ) ) { + nSent = RelayService.sendPacket( m_context, m_rowid, buf ); + } else { + if ( NetStateCache.netAvail( m_context ) ) { + putOut( buf ); // add to queue + if ( null == m_thread ) { + m_thread = new CommsThread(); + m_thread.start(); + } + nSent = buf.length; } - nSent = buf.length; } break; case COMMS_CONN_SMS: diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index ff1677320..e5d9c0cc0 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -74,6 +74,7 @@ public class GamesList extends XWExpandableListActivity private static final String SAVE_DICTNAMES = "SAVE_DICTNAMES"; private static final String RELAYIDS_EXTRA = "relayids"; + private static final String ROWID_EXTRA = "rowid"; private static final String GAMEID_EXTRA = "gameid"; private static final String REMATCH_ROWID_EXTRA = "rowid"; @@ -386,6 +387,7 @@ public class GamesList extends XWExpandableListActivity super.onNewIntent( intent ); Assert.assertNotNull( intent ); invalRelayIDs( intent.getStringArrayExtra( RELAYIDS_EXTRA ) ); + invalRowID( intent.getLongExtra( ROWID_EXTRA, -1 ) ); startFirstHasDict( intent ); startNewNetGame( intent ); startHasGameID( intent ); @@ -916,10 +918,18 @@ public class GamesList extends XWExpandableListActivity } } + private void invalRowID( long rowid ) + { + if ( -1 != rowid ) { + m_adapter.inval( rowid ); + } + } + // Launch the first of these for which there's a dictionary // present. - private void startFirstHasDict( String[] relayIDs ) + private boolean startFirstHasDict( String[] relayIDs ) { + boolean launched = false; if ( null != relayIDs ) { outer: for ( String relayID : relayIDs ) { @@ -928,19 +938,33 @@ public class GamesList extends XWExpandableListActivity for ( long rowid : rowids ) { if ( GameUtils.gameDictsHere( this, rowid ) ) { launchGame( rowid ); + launched = true; break outer; } } } } } + return launched; + } + + private void startFirstHasDict( long rowid ) + { + if ( -1 != rowid ) { + if ( GameUtils.gameDictsHere( this, rowid ) ) { + launchGame( rowid ); + } + } } private void startFirstHasDict( Intent intent ) { if ( null != intent ) { String[] relayIDs = intent.getStringArrayExtra( RELAYIDS_EXTRA ); - startFirstHasDict( relayIDs ); + if ( !startFirstHasDict( relayIDs ) ) { + long rowid = intent.getLongExtra( ROWID_EXTRA, -1 ); + startFirstHasDict( rowid ); + } } } @@ -1093,11 +1117,10 @@ public class GamesList extends XWExpandableListActivity return intent; } - public static Intent makeRelayIdsIntent( Context context, - String[] relayIDs ) + public static Intent makeRowidIntent( Context context, long rowid ) { Intent intent = makeSelfIntent( context ); - intent.putExtra( RELAYIDS_EXTRA, relayIDs ); + intent.putExtra( ROWID_EXTRA, rowid ); return intent; } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java index 2e92d7c9d..963697c44 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java @@ -48,6 +48,11 @@ public class RelayService extends XWService { private static final String CMD_STR = "CMD"; private static final int UDP_CHANGED = 1; + private static final int SEND = 2; + private static final int RECEIVE = 3; + + private static final String ROWID = "ROWID"; + private static final String BINBUFFER = "BINBUFFER"; private Thread m_fetchThread = null; private Thread m_UDPReadThread = null; @@ -58,8 +63,32 @@ public class RelayService extends XWService { // These must match the enum XWRelayReg in xwrelay.h private static final int XWPDEV_PROTO_VERSION = 0; // private static final int XWPDEV_NONE = 0; - private static final int XWPDEV_ALERT = 1; - private static final int XWPDEV_REG = 2; + + private enum XWRelayReg { + XWPDEV_NONE + ,XWPDEV_ALERT + ,XWPDEV_REG + ,XWPDEV_REGRSP + ,XWPDEV_PING + ,XWPDEV_HAVEMSGS + ,XWPDEV_RQSTMSGS + ,XWPDEV_MSG + ,XWPDEV_MSGNOCONN + ,XWPDEV_MSGRSP + ,XWPDEV_BADREG + ,XWPDEV_ACK + }; + + // private static final int XWPDEV_ALERT = 1; + // private static final int XWPDEV_REG = 2; + // private static final int XWPDEV_REGRSP = 3; + // private static final int XWPDEV_PING = 4; + // private static final int XWPDEV_HAVEMSGS = 5; + // private static final int XWPDEV_RQSTMSGS = 6; + // private static final int XWPDEV_MSG = 7; + // private static final int XWPDEV_MSGNOCONN = 8; + // private static final int XWPDEV_MSGRSP = 9; + // private static final int XWPDEV_BADREG = 10; public static void startService( Context context ) { @@ -67,6 +96,26 @@ public class RelayService extends XWService { context.startService( intent ); } + public static int sendPacket( Context context, long rowid, byte[] buf ) + { + Intent intent = getIntentTo( context, SEND ); + intent.putExtra( ROWID, rowid ); + intent.putExtra( BINBUFFER, buf ); + context.startService( intent ); + return buf.length; + } + + // Exists to get incoming data onto the main thread + private static void postData( Context context, long rowid, byte[] msg ) + { + DbgUtils.logf( "RelayService::postData: packet of length %d for token %d", + msg.length, rowid ); + Intent intent = getIntentTo( context, RECEIVE ); + intent.putExtra( ROWID, rowid ); + intent.putExtra( BINBUFFER, msg ); + context.startService( intent ); + } + public static void udpChanged( Context context ) { startService( context ); @@ -94,6 +143,8 @@ public class RelayService extends XWService { if ( null != intent ) { int cmd = intent.getIntExtra( CMD_STR, -1 ); switch( cmd ) { + case -1: + break; case UDP_CHANGED: DbgUtils.logf( "RelayService::onStartCommand::UDP_CHANGED" ); if ( XWPrefs.getUDPEnabled( this ) ) { @@ -105,6 +156,16 @@ public class RelayService extends XWService { startFetchThreadIf(); } break; + case SEND: + case RECEIVE: + long rowid = intent.getLongExtra( ROWID, -1 ); + byte[] msg = intent.getByteArrayExtra( BINBUFFER ); + if ( SEND == cmd ) { + sendMessage( rowid, msg ); + } else { + feedMessage( rowid, msg ); + } + break; default: Assert.fail(); } @@ -122,17 +183,20 @@ public class RelayService extends XWService { long[] rowids = DBUtils.getRowIDsFor( this, relayID ); if ( null != rowids ) { for ( long rowid : rowids ) { - Intent intent = - GamesList.makeRelayIdsIntent( this, - new String[] {relayID} ); - String msg = Utils.format( this, R.string.notify_bodyf, - GameUtils.getName( this, rowid ) ); - Utils.postNotification( this, intent, R.string.notify_title, - msg, (int)rowid ); + setupNotification( rowid ); } } } } + + private void setupNotification( long rowid ) + { + Intent intent = GamesList.makeRowidIntent( this, rowid ); + String msg = Utils.format( this, R.string.notify_bodyf, + GameUtils.getName( this, rowid ) ); + Utils.postNotification( this, intent, R.string.notify_title, + msg, (int)rowid ); + } private void startFetchThreadIf() { @@ -159,70 +223,85 @@ public class RelayService extends XWService { private void startUDPThreads() { DbgUtils.logf( "startUDPThreads" ); - Assert.assertNull( m_UDPWriteThread ); - Assert.assertNull( m_UDPReadThread ); Assert.assertTrue( XWPrefs.getUDPEnabled( this ) ); - int port = XWPrefs.getDefaultRelayPort( RelayService.this ); - String host = XWPrefs.getDefaultRelayHost( RelayService.this ); - try { - m_UDPSocket = new DatagramSocket(); - InetAddress addr = InetAddress.getByName( host ); - m_UDPSocket.connect( addr, port ); - } catch( java.net.SocketException se ) { - DbgUtils.loge( se ); - Assert.fail(); - } catch( java.net.UnknownHostException uhe ) { - DbgUtils.loge( uhe ); + if ( null == m_UDPSocket ) { + int port = XWPrefs.getDefaultRelayPort( RelayService.this ); + String host = XWPrefs.getDefaultRelayHost( RelayService.this ); + try { + m_UDPSocket = new DatagramSocket(); + InetAddress addr = InetAddress.getByName( host ); + m_UDPSocket.connect( addr, port ); // meaning: remember this address + } catch( java.net.SocketException se ) { + DbgUtils.loge( se ); + Assert.fail(); + } catch( java.net.UnknownHostException uhe ) { + DbgUtils.loge( uhe ); + } + } else { + Assert.assertTrue( m_UDPSocket.isConnected() ); + DbgUtils.logf( "m_UDPSocket not null" ); } - m_UDPReadThread = new Thread( null, new Runnable() { - public void run() { - byte[] buf = new byte[1024]; - for ( ; ; ) { - DatagramPacket packet = - new DatagramPacket( buf, buf.length ); - try { - DbgUtils.logf( "UPD read thread blocking on receive" ); - m_UDPSocket.receive( packet ); - DbgUtils.logf( "UPD read thread: receive returned" ); - } catch( java.io.IOException ioe ) { - DbgUtils.loge( ioe ); - break; // ??? + if ( null == m_UDPReadThread ) { + m_UDPReadThread = new Thread( null, new Runnable() { + public void run() { + DbgUtils.logf( "read thread running" ); + byte[] buf = new byte[1024]; + for ( ; ; ) { + DatagramPacket packet = + new DatagramPacket( buf, buf.length ); + try { + DbgUtils.logf( "UPD read thread blocking on receive" ); + m_UDPSocket.receive( packet ); + DbgUtils.logf( "UPD read thread: receive returned" ); + } catch( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + break; // ??? + } + DbgUtils.logf( "received %d bytes", packet.getLength() ); + gotPacket( packet ); } - DbgUtils.logf( "received %d bytes", packet.getLength() ); - gotPacket( packet ); + DbgUtils.logf( "read thread exiting" ); } - } - }, getClass().getName() ); - m_UDPReadThread.start(); + }, getClass().getName() ); + m_UDPReadThread.start(); + } else { + DbgUtils.logf( "m_UDPReadThread not null and assumed to be running" ); + } - m_queue = new LinkedBlockingQueue(); - m_UDPWriteThread = new Thread( null, new Runnable() { - public void run() { - for ( ; ; ) { - DatagramPacket outPacket; - try { - outPacket = m_queue.take(); - } catch ( InterruptedException ie ) { - DbgUtils.logf( "RelayService; write thread killed" ); - break; - } - if ( null == outPacket || 0 == outPacket.getLength() ) { - DbgUtils.logf( "stopping write thread" ); - break; - } - DbgUtils.logf( "Sending packet of length %d", - outPacket.getLength() ); - try { - m_UDPSocket.send( outPacket ); - } catch ( java.io.IOException ioe ) { - DbgUtils.loge( ioe ); + if ( null == m_UDPWriteThread ) { + m_queue = new LinkedBlockingQueue(); + m_UDPWriteThread = new Thread( null, new Runnable() { + public void run() { + DbgUtils.logf( "write thread running" ); + for ( ; ; ) { + DatagramPacket outPacket; + try { + outPacket = m_queue.take(); + } catch ( InterruptedException ie ) { + DbgUtils.logf( "RelayService; write thread killed" ); + break; + } + if ( null == outPacket || 0 == outPacket.getLength() ) { + DbgUtils.logf( "stopping write thread" ); + break; + } + DbgUtils.logf( "Sending udp packet of length %d", + outPacket.getLength() ); + try { + m_UDPSocket.send( outPacket ); + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } } + DbgUtils.logf( "write thread exiting" ); } - } - }, getClass().getName() ); - m_UDPWriteThread.start(); + }, getClass().getName() ); + m_UDPWriteThread.start(); + } else { + DbgUtils.logf( "m_UDPWriteThread not null and assumed to be running" ); + } } private void stopUDPThreadsIf() @@ -256,35 +335,185 @@ public class RelayService extends XWService { DbgUtils.logf( "stopUDPThreadsIf DONE" ); } + // Running on reader thread private void gotPacket( DatagramPacket packet ) { - DbgUtils.logf( "gotPacket" ); - ByteArrayInputStream bis = new ByteArrayInputStream( packet.getData() ); + int packetLen = packet.getLength(); + byte[] data = new byte[packetLen]; + System.arraycopy( packet.getData(), 0, data, 0, packetLen ); + DbgUtils.logf( "RelayService::gotPacket: %d bytes of data", packetLen ); + ByteArrayInputStream bis = new ByteArrayInputStream( data ); DataInputStream dis = new DataInputStream( bis ); try { - byte proto = dis.readByte(); - if ( XWPDEV_PROTO_VERSION == proto ) { - byte cmd = dis.readByte(); - switch ( cmd ) { + PacketHeader header = readHeader( dis ); + if ( null != header ) { + sendAckIf( header ); + switch ( header.m_cmd ) { case XWPDEV_ALERT: - short len = dis.readShort(); - byte[] tmp = new byte[len]; - dis.read( tmp ); - sendResult( MultiEvent.RELAY_ALERT, new String( tmp ) ); + String str = getStringWithLength( dis ); + sendResult( MultiEvent.RELAY_ALERT, str ); + break; + case XWPDEV_BADREG: + str = getStringWithLength( dis ); + DbgUtils.logf( "bad relayID \"%s\" reported", str ); + XWPrefs.clearRelayDevID( this ); + registerWithRelay(); + break; + case XWPDEV_REGRSP: + DbgUtils.logf( "got XWPDEV_REGRSP" ); + str = getStringWithLength( dis ); + DbgUtils.logf( "got relayid %s", str ); + XWPrefs.setRelayDevID( this, str ); + break; + case XWPDEV_HAVEMSGS: + requestMessages(); + break; + case XWPDEV_MSG: + DbgUtils.logf( "got XWPDEV_MSG" ); + int token = dis.readInt(); + byte[] msg = new byte[dis.available()]; + Assert.assertTrue( packet.getLength() >= msg.length ); + Assert.assertTrue( packetLen >= msg.length ); + dis.read( msg ); + postData( RelayService.this, token, msg ); break; default: - DbgUtils.logf( "RelayService: Unhandled cmd: %d", cmd ); + DbgUtils.logf( "RelayService: Unhandled cmd: %d", + header.m_cmd ); break; } - } else { - DbgUtils.logf( "bad proto %d", proto ); } } catch ( java.io.IOException ioe ) { DbgUtils.loge( ioe ); } + } // gotPacket + + private void registerWithRelay() + { + DbgUtils.logf( "registerWithRelay" ); + byte[] typ = new byte[1]; + String devid = getDevID(typ); + + ByteArrayOutputStream bas = new ByteArrayOutputStream(); + try { + DataOutputStream out = addProtoAndCmd( bas, XWRelayReg.XWPDEV_REG ); + out.writeByte( typ[0] ); + out.writeShort( devid.length() ); + out.writeBytes( devid ); + postPacket( bas ); + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } } - private void registerWithRelay() + private void requestMessages() + { + DbgUtils.logf( "requestMessages" ); + ByteArrayOutputStream bas = new ByteArrayOutputStream(); + try { + DataOutputStream out = + addProtoAndCmd( bas, XWRelayReg.XWPDEV_RQSTMSGS ); + String devid = getDevID( null ); + out.writeShort( devid.length() ); + out.writeBytes( devid ); + postPacket( bas ); + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + } + + private void sendMessage( long rowid, byte[] msg ) + { + ByteArrayOutputStream bas = new ByteArrayOutputStream(); + try { + DataOutputStream out = addProtoAndCmd( bas, XWRelayReg.XWPDEV_MSG ); + Assert.assertTrue( rowid < Integer.MAX_VALUE ); + out.writeInt( (int)rowid ); + out.write( msg, 0, msg.length ); + postPacket( bas ); + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + } + + private void sendNoConnMessage( long rowid, String relayID, byte[] msg ) + { + ByteArrayOutputStream bas = new ByteArrayOutputStream(); + try { + DataOutputStream out = + addProtoAndCmd( bas, XWRelayReg.XWPDEV_MSGNOCONN ); + Assert.assertTrue( rowid < Integer.MAX_VALUE ); + out.writeInt( (int)rowid ); + out.writeBytes( relayID ); + out.write( '\n' ); + out.write( msg, 0, msg.length ); + postPacket( bas ); + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + } + + private void sendAckIf( PacketHeader header ) + { + DbgUtils.logf( "sendAckIf" ); + if ( XWRelayReg.XWPDEV_ACK != header.m_cmd ) { + ByteArrayOutputStream bas = new ByteArrayOutputStream(); + try { + DataOutputStream out = + addProtoAndCmd( bas, XWRelayReg.XWPDEV_ACK ); + out.writeInt( header.m_packetID ); + postPacket( bas ); + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + } + } + + private PacketHeader readHeader( DataInputStream dis ) + throws java.io.IOException + { + PacketHeader result = null; + byte proto = dis.readByte(); + if ( XWPDEV_PROTO_VERSION == proto ) { + int packetID = dis.readInt(); + DbgUtils.logf( "readHeader: got packetID %d", packetID ); + byte ordinal = dis.readByte(); + XWRelayReg cmd = XWRelayReg.values()[ordinal]; + result = new PacketHeader( cmd, packetID ); + } else { + DbgUtils.logf( "bad proto: %d", proto ); + } + DbgUtils.logf( "readHeader => %H", result ); + return result; + } + + private String getStringWithLength( DataInputStream dis ) + throws java.io.IOException + { + short len = dis.readShort(); + byte[] tmp = new byte[len]; + dis.read( tmp ); + return new String( tmp ); + } + + private DataOutputStream addProtoAndCmd( ByteArrayOutputStream bas, + XWRelayReg cmd ) + throws java.io.IOException + { + DataOutputStream out = new DataOutputStream( bas ); + out.writeByte( XWPDEV_PROTO_VERSION ); + out.writeInt( 0 ); // packetID + out.writeByte( cmd.ordinal() ); + return out; + } + + private void postPacket( ByteArrayOutputStream bas ) + { + byte[] data = bas.toByteArray(); + m_queue.add( new DatagramPacket( data, data.length ) ); + } + + private String getDevID( byte[] typp ) { byte typ; String devid = XWPrefs.getRelayDevID( this ); @@ -299,20 +528,30 @@ public class RelayService extends XWService { typ = UtilCtxt.ID_TYPE_ANDROID_OTHER; } } + if ( null != typp ) { + typp[0] = typ; + } else { + Assert.assertTrue( typ == UtilCtxt.ID_TYPE_RELAY ); + } + return devid; + } - ByteArrayOutputStream bas = new ByteArrayOutputStream(); - DataOutputStream outBuf = new DataOutputStream( bas ); - try { - outBuf.writeByte( XWPDEV_PROTO_VERSION ); - outBuf.writeByte( XWPDEV_REG ); - outBuf.writeByte( typ ); - outBuf.writeShort( devid.length() ); - outBuf.writeBytes( devid ); - - byte[] data = bas.toByteArray(); - m_queue.add( new DatagramPacket( data, data.length ) ); - } catch ( java.io.IOException ioe ) { - DbgUtils.loge( ioe ); + private void feedMessage( long rowid, byte[] msg ) + { + DbgUtils.logf( "RelayService::feedMessage: %d bytes for rowid %d", + msg.length, rowid ); + if ( BoardActivity.feedMessage( rowid, msg ) ) { + DbgUtils.logf( "feedMessage: board ate it" ); + // do nothing + } else { + RelayMsgSink sink = new RelayMsgSink(); + sink.setRowID( rowid ); + if ( GameUtils.feedMessage( this, rowid, msg, null, + sink ) ) { + setupNotification( rowid ); + } else { + DbgUtils.logf( "feedMessage: background dropped it" ); + } } } @@ -428,29 +667,49 @@ public class RelayService extends XWService { private class RelayMsgSink extends MultiMsgSink { private HashMap> m_msgLists = null; + private long m_rowid = -1; + + public void setRowID( long rowid ) { m_rowid = rowid; } public void send( Context context ) { - sendToRelay( context, m_msgLists ); + if ( -1 == m_rowid ) { + sendToRelay( context, m_msgLists ); + } else { + Assert.assertNull( m_msgLists ); + } } /***** TransportProcs interface *****/ public boolean relayNoConnProc( byte[] buf, String relayID ) { - if ( null == m_msgLists ) { - m_msgLists = new HashMap>(); - } + if ( -1 != m_rowid ) { + sendNoConnMessage( m_rowid, relayID, buf ); + } else { + if ( null == m_msgLists ) { + m_msgLists = new HashMap>(); + } - ArrayList list = m_msgLists.get( relayID ); - if ( list == null ) { - list = new ArrayList(); - m_msgLists.put( relayID, list ); + ArrayList list = m_msgLists.get( relayID ); + if ( list == null ) { + list = new ArrayList(); + m_msgLists.put( relayID, list ); + } + list.add( buf ); } - list.add( buf ); - return true; } } + private class PacketHeader { + public int m_packetID; + public XWRelayReg m_cmd; + public PacketHeader( XWRelayReg cmd, int packetID ) { + DbgUtils.logf( "in PacketHeader contructor" ); + m_packetID = packetID; + m_cmd = cmd; + } + } + } From c465a0bb0c0078c4b5bd80b49d57a5f8ecda345f Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 24 Jan 2013 07:43:24 -0800 Subject: [PATCH 059/116] add loop to print when packets haven't been ack'd --- xwords4/relay/udpack.cpp | 109 +++++++++++++++++++++++++++++++++++++- xwords4/relay/udpack.h | 25 +++++++-- xwords4/relay/xwrelay.cpp | 43 +++++++++++---- 3 files changed, 160 insertions(+), 17 deletions(-) diff --git a/xwords4/relay/udpack.cpp b/xwords4/relay/udpack.cpp index 0b01aef5f..2c8d61169 100644 --- a/xwords4/relay/udpack.cpp +++ b/xwords4/relay/udpack.cpp @@ -18,6 +18,113 @@ */ #include "udpack.h" +#include "mlock.h" -uint32_t UDPAckTrack::s_nextID = 0; +UDPAckTrack* UDPAckTrack::s_self = NULL; + +/* static*/ uint32_t +UDPAckTrack::nextPacketID( XWRelayReg cmd ) +{ + return get()->nextPacketIDImpl( cmd ); +} + +/* static*/ void +UDPAckTrack::recordAck( uint32_t packetID ) +{ + get()->recordAckImpl( packetID ); +} + +/* static */ UDPAckTrack* +UDPAckTrack::get() +{ + if ( NULL == s_self ) { + s_self = new UDPAckTrack(); + } + return s_self; +} + +UDPAckTrack::UDPAckTrack() +{ + m_nextID = 0; + pthread_mutex_init( &m_mutex, NULL ); + + pthread_t thread; + pthread_create( &thread, NULL, thread_main, (void*)this ); + pthread_detach( thread ); +} + +uint32_t +UDPAckTrack::nextPacketIDImpl( XWRelayReg cmd ) +{ + uint32_t result; + if ( XWPDEV_ACK == cmd || XWPDEV_ALERT == cmd ) { + result = 0; + } else { + MutexLock ml( &m_mutex ); + AckRecord record; + result = ++m_nextID; + m_pendings.insert( pair(result, record) ); + } + return result; +} + +void +UDPAckTrack::recordAckImpl( uint32_t packetID ) +{ + map::iterator iter; + MutexLock ml( &m_mutex ); + iter = m_pendings.find( packetID ); + if ( m_pendings.end() == iter ) { + logf( XW_LOGERROR, "%s: packet ID %d not found", __func__, packetID ); + } else { + time_t took = time( NULL ) - iter->second.m_createTime; + if ( 5 < took ) { + logf( XW_LOGERROR, "%s: packet ID %d took %d seconds to get acked", __func__, packetID ); + } + m_pendings.erase( iter ); + } +} + +void* +UDPAckTrack::threadProc() +{ + for ( ; ; ) { + sleep( 30 ); + time_t now = time( NULL ); + vector older; + { + MutexLock ml( &m_mutex ); + map::iterator iter; + for ( iter = m_pendings.begin(); iter != m_pendings.end(); ++iter ) { + time_t took = now - iter->second.m_createTime; + if ( 60 < took ) { + older.push_back( iter->first ); + m_pendings.erase( iter ); + } + } + } + if ( 0 < older.size() ) { + string leaked; + vector::const_iterator iter = older.begin(); + for ( ; ; ) { + string_printf( leaked, "%d", *iter ); + if ( ++iter == older.end() ) { + break; + } + string_printf( leaked, ", " ); + } + logf( XW_LOGERROR, "these packets leaked: %s", leaked.c_str() ); + } else { + logf( XW_LOGINFO, "no packets leaked" ); + } + } + return NULL; +} + +/* static */ void* +UDPAckTrack::thread_main( void* arg ) +{ + UDPAckTrack* self = (UDPAckTrack*)arg; + return self->threadProc(); +} diff --git a/xwords4/relay/udpack.h b/xwords4/relay/udpack.h index b161050ea..1e0fa3a80 100644 --- a/xwords4/relay/udpack.h +++ b/xwords4/relay/udpack.h @@ -21,16 +21,31 @@ #define _UDPACK_H_ #include "xwrelay_priv.h" +#include "xwrelay.h" + +class AckRecord { + public: + AckRecord() { m_createTime = time( NULL ); } + time_t m_createTime; +}; class UDPAckTrack { public: - static uint32_t nextPacketID() { return ++s_nextID; } - static void recordAck( uint32_t packetID ) { - logf( XW_LOGINFO, "received ack for %d", packetID ); - } + static uint32_t nextPacketID( XWRelayReg cmd ); + static void recordAck( uint32_t packetID ); private: - static uint32_t s_nextID; + static UDPAckTrack* get(); + static void* thread_main( void* arg ); + UDPAckTrack(); + uint32_t nextPacketIDImpl( XWRelayReg cmd ); + void recordAckImpl( uint32_t packetID ); + void* threadProc(); + + static UDPAckTrack* s_self; + uint32_t m_nextID; + pthread_mutex_t m_mutex; + map m_pendings; }; #endif diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index e2f1e6703..a1ca3da04 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -80,6 +80,12 @@ #include "udpqueue.h" #include "udpack.h" +typedef struct _UDPHeader { + uint32_t packetID; + unsigned char proto; + XWRelayReg cmd; +} UDPHeader; + static int s_nSpawns = 0; static int g_maxsocks = -1; static int g_udpsock = -1; @@ -262,6 +268,23 @@ getNetString( const unsigned char** bufpp, const unsigned char* end, string& out return success; } +static bool +getHeader( const unsigned char** bufpp, const unsigned char* end, + UDPHeader* header ) +{ + unsigned char byt; + bool success = getNetByte( bufpp, end, &header->proto ) + && getNetLong( bufpp, end, &header->packetID ) + && getNetByte( bufpp, end, &byt ) + && XWPDEV_PROTO_VERSION == header->proto; + if ( success ) { + header->cmd = (XWRelayReg)byt; + } else { + logf( XW_LOGERROR, "%s: bad packet header", __func__ ); + } + return success; +} + static void getDevID( const unsigned char** bufpp, const unsigned char* end, unsigned short flags, DevID* devID ) @@ -356,7 +379,7 @@ static ssize_t send_via_udp( int socket, const struct sockaddr *dest_addr, XWRelayReg cmd, ... ) { - uint32_t packetNum = UDPAckTrack::nextPacketID(); + uint32_t packetNum = UDPAckTrack::nextPacketID( cmd ); struct iovec vec[10]; int iocount = 0; @@ -1262,14 +1285,10 @@ udp_thread_proc( UdpThreadClosure* utc ) const unsigned char* ptr = utc->buf(); const unsigned char* end = ptr + utc->len(); - unsigned char proto = *ptr++; - if ( XWPDEV_PROTO_VERSION != 0 ) { - logf( XW_LOGERROR, "unexpected proto %d", __func__, (int) proto ); - } else { - ptr += 4; // skip msgid - XWRelayReg msg = (XWRelayReg)*ptr++; - logf( XW_LOGINFO, "%s(msg=%s)", __func__, msgToStr( msg ) ); - switch( msg ) { + UDPHeader header; + if ( getHeader( &ptr, end, &header ) ) { + logf( XW_LOGINFO, "%s(msg=%s)", __func__, msgToStr( header.cmd ) ); + switch( header.cmd ) { case XWPDEV_REG: { DevIDType typ = (DevIDType)*ptr++; unsigned short idLen; @@ -1347,7 +1366,7 @@ udp_thread_proc( UdpThreadClosure* utc ) break; } default: - logf( XW_LOGERROR, "%s: unexpected msg %d", __func__, msg ); + logf( XW_LOGERROR, "%s: unexpected msg %d", __func__, header.cmd ); } } } @@ -1484,7 +1503,9 @@ maint_str_loop( int udpsock, const char* str ) &saddr.addr, &fromlen ); logf( XW_LOGINFO, "%s(); got %d bytes", __func__, nRead); - if ( 1 <= nRead && buf[0] == XWPDEV_PROTO_VERSION ) { + UDPHeader header; + const unsigned char* ptr = buf; + if ( getHeader( &ptr, ptr + nRead, &header ) ) { send_via_udp( udpsock, &saddr.addr, XWPDEV_ALERT, outbuf, sizeof(outbuf), NULL ); } else { From 9f3250cb3fa6bff5fa35508e39489181f743af84 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 24 Jan 2013 07:49:49 -0800 Subject: [PATCH 060/116] make curses version use udp socket when --db param passed. There's no UI for the DB and the assumption is only one game will be stored but this should let the test script be modified to test the new protocol. --- xwords4/linux/cursesask.c | 4 +- xwords4/linux/cursesask.h | 4 +- xwords4/linux/cursesdlgutil.c | 2 +- xwords4/linux/cursesdlgutil.h | 2 +- xwords4/linux/cursesletterask.c | 4 +- xwords4/linux/cursesmain.c | 154 +++++++++++++++++++++++++++++++- xwords4/linux/cursesmain.h | 1 + xwords4/linux/gamesdb.c | 12 +-- xwords4/linux/gamesdb.h | 4 +- xwords4/linux/gtkboard.c | 34 ------- xwords4/linux/gtkboard.h | 2 - xwords4/linux/gtkmain.c | 46 +++------- xwords4/linux/linuxmain.c | 68 ++++++++++++++ xwords4/linux/linuxmain.h | 6 ++ xwords4/linux/main.h | 2 - xwords4/linux/relaycon.c | 4 +- xwords4/linux/relaycon.h | 3 + 17 files changed, 259 insertions(+), 93 deletions(-) diff --git a/xwords4/linux/cursesask.c b/xwords4/linux/cursesask.c index 463c622dc..ffca1a9a7 100644 --- a/xwords4/linux/cursesask.c +++ b/xwords4/linux/cursesask.c @@ -32,8 +32,8 @@ /* Figure out how many lines there are and how wide the widest is. */ short -cursesask( CursesAppGlobals* globals, char* question, short numButtons, - char* button1, ... ) +cursesask( CursesAppGlobals* globals, const char* question, short numButtons, + const char* button1, ... ) { WINDOW* confWin; int x, y, rows, row, nLines; diff --git a/xwords4/linux/cursesask.h b/xwords4/linux/cursesask.h index f4930a682..36f20fb35 100644 --- a/xwords4/linux/cursesask.h +++ b/xwords4/linux/cursesask.h @@ -22,8 +22,8 @@ #include "cursesmain.h" -short cursesask( CursesAppGlobals* globals, char* question, - short numButtons, char* button1, ... ); +short cursesask( CursesAppGlobals* globals, const char* question, + short numButtons, const char* button1, ... ); #endif diff --git a/xwords4/linux/cursesdlgutil.c b/xwords4/linux/cursesdlgutil.c index b7484691d..740bf27ec 100644 --- a/xwords4/linux/cursesdlgutil.c +++ b/xwords4/linux/cursesdlgutil.c @@ -70,7 +70,7 @@ measureAskText( const XP_UCHAR* question, int width, FormatInfo* fip ) void drawButtons( WINDOW* win, XP_U16 line, short spacePerButton, - short numButtons, short curSelButton, char** button1 ) + short numButtons, short curSelButton, const char** button1 ) { short i; for ( i = 0; i < numButtons; ++i ) { diff --git a/xwords4/linux/cursesdlgutil.h b/xwords4/linux/cursesdlgutil.h index 1f9199c7e..dd9a2403c 100644 --- a/xwords4/linux/cursesdlgutil.h +++ b/xwords4/linux/cursesdlgutil.h @@ -44,6 +44,6 @@ typedef struct FormatInfo { void measureAskText( const XP_UCHAR* question, int maxWidth, FormatInfo* fip ); void drawButtons( WINDOW* confWin, XP_U16 line, short spacePerButton, - short numButtons, short curSelButton, char** button1 ); + short numButtons, short curSelButton, const char** button1 ); #endif diff --git a/xwords4/linux/cursesletterask.c b/xwords4/linux/cursesletterask.c index 332d722eb..1dc5d0e19 100644 --- a/xwords4/linux/cursesletterask.c +++ b/xwords4/linux/cursesletterask.c @@ -57,7 +57,7 @@ curses_askLetter( CursesAppGlobals* globals, XP_UCHAR* query, short curSelButton = 1; /* force draw by being different */ short maxWidth; short numCtlButtons; - char* ctlButtons[] = { "Ok", "Cancel" }; + const char* ctlButtons[] = { "Ok", "Cancel" }; XP_Bool dismissed = XP_FALSE; FormatInfo fi; int len; @@ -138,7 +138,7 @@ curses_askLetter( CursesAppGlobals* globals, XP_UCHAR* query, MAX_TILE_BUTTON_WIDTH-1, nInRow, newSelButton - textsOffsets[i], - (char**)&textPtrs[textsOffsets[i]] ); + (const char**)&textPtrs[textsOffsets[i]] ); } diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index fb7862604..d3c1a87c0 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -60,6 +60,8 @@ #include "dbgutil.h" #include "linuxsms.h" #include "linuxudp.h" +#include "gamesdb.h" +#include "relaycon.h" #ifdef CURSES_SMALL_SCREEN # define MENU_WINDOW_HEIGHT 1 @@ -1121,6 +1123,15 @@ curses_socket_changed( void* closure, int oldSock, int newSock, #endif } /* curses_socket_changed */ +static void +curses_onGameSaved( void* closure, sqlite3_int64 rowid, + XP_Bool XP_UNUSED(firstTime) ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)closure; + /* May not be recorded */ + globals->cGlobals.selRow = rowid; +} + #ifdef USE_GLIBLOOP static gboolean fire_acceptor( GIOChannel* source, GIOCondition condition, gpointer data ) @@ -1717,6 +1728,107 @@ curses_getFlags( void* XP_UNUSED(closure) ) } #endif +static void +cursesGotBuf( void* closure, const XP_U8* buf, XP_U16 len ) +{ + LOG_FUNC(); + CursesAppGlobals* globals = (CursesAppGlobals*)closure; + XP_U32 gameToken; + XP_ASSERT( sizeof(gameToken) < len ); + gameToken = ntohl(*(XP_U32*)&buf[0]); + buf += sizeof(gameToken); + len -= sizeof(gameToken); + + gameGotBuf( &globals->cGlobals, XP_TRUE, buf, len ); +} + +static gint +curses_requestMsgs( gpointer data ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)data; + XP_UCHAR devIDBuf[64] = {0}; + db_fetch( globals->cGlobals.pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); + if ( '\0' != devIDBuf[0] ) { + relaycon_requestMsgs( globals->cGlobals.params, devIDBuf ); + } else { + XP_LOGF( "%s: not requesting messages as don't have relay id", __func__ ); + } + return 0; /* don't run again */ +} + + +static void +cursesNoticeRcvd( void* closure, XP_U32 XP_UNUSED(gameToken) ) +{ + LOG_FUNC(); + CursesAppGlobals* globals = (CursesAppGlobals*)closure; + (void)g_idle_add( curses_requestMsgs, globals ); +} + +static void +cursesDevIDChanged( void* closure, const XP_UCHAR* devID ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)closure; + sqlite3* pDb = globals->cGlobals.pDb; + if ( !!devID ) { + XP_LOGF( "%s(devID=%s)", __func__, devID ); + db_store( pDb, KEY_RDEVID, devID ); + } else { + XP_LOGF( "%s: bad relayid", __func__ ); + db_remove( pDb, KEY_RDEVID ); + sendRelayReg( globals->cGlobals.params, pDb ); + } +} + +static void +cursesErrorMsgRcvd( void* closure, const XP_UCHAR* msg ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)closure; + if ( !!globals->lastErr && 0 == strcmp( globals->lastErr, msg ) ) { + XP_LOGF( "skipping error message from relay" ); + } else { + g_free( globals->lastErr ); + globals->lastErr = g_strdup( msg ); + (void)cursesask( globals, msg, 1, "Ok" ); + } +} + + +static gboolean +curses_app_socket_proc( GIOChannel* source, GIOCondition condition, + gpointer data ) +{ + if ( 0 != (G_IO_IN & condition) ) { + CursesAppGlobals* globals = (CursesAppGlobals*)data; + int socket = g_io_channel_unix_get_fd( source ); + GList* iter; + for ( iter = globals->sources; !!iter; iter = iter->next ) { + SourceData* sd = (SourceData*)iter->data; + if ( sd->channel == source ) { + (*sd->proc)( sd->procClosure, socket ); + break; + } + } + XP_ASSERT( !!iter ); /* didn't fail to find it */ + } + return TRUE; +} + +static void +cursesUDPSocketChanged( void* closure, int newSock, int XP_UNUSED(oldSock), + SockReceiver proc, void* procClosure ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)closure; + + SourceData* sd = g_malloc( sizeof(*sd) ); + sd->channel = g_io_channel_unix_new( newSock ); + sd->watch = g_io_add_watch( sd->channel, G_IO_IN | G_IO_ERR, + curses_app_socket_proc, globals ); + sd->proc = proc; + sd->procClosure = procClosure; + globals->sources = g_list_append( globals->sources, sd ); +} + void cursesmain( XP_Bool isServer, LaunchParams* params ) { @@ -1737,6 +1849,9 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) g_globals.cGlobals.socketChanged = curses_socket_changed; g_globals.cGlobals.socketChangedClosure = &g_globals; + g_globals.cGlobals.onSave = curses_onGameSaved; + g_globals.cGlobals.onSaveClosure = &g_globals; + g_globals.cGlobals.addAcceptor = curses_socket_acceptor; g_globals.cGlobals.cp.showBoardArrow = XP_TRUE; @@ -1816,7 +1931,38 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) cursesDrawCtxtMake( g_globals.boardWin ); XWStreamCtxt* stream = NULL; - if ( !!params->fileName && file_exists( params->fileName ) ) { + if ( !!params->dbName ) { + g_globals.cGlobals.pDb = openGamesDB( params->dbName ); + + RelayConnProcs procs = { + .msgReceived = cursesGotBuf, + .msgNoticeReceived = cursesNoticeRcvd, + .devIDChanged = cursesDevIDChanged, + .msgErrorMsg = cursesErrorMsgRcvd, + .socketChanged = cursesUDPSocketChanged, + }; + + relaycon_init( params, &procs, &g_globals, + params->connInfo.relay.relayName, + params->connInfo.relay.defaultSendPort ); + sendRelayReg( params, g_globals.cGlobals.pDb ); + + GSList* games = listGames( g_globals.cGlobals.pDb ); + if ( !!games ) { + stream = mem_stream_make( MEMPOOL params->vtMgr, + &g_globals.cGlobals, CHANNEL_NONE, + NULL ); + sqlite3_int64 selRow = *(sqlite3_int64*)games->data; + if ( loadGame( stream, g_globals.cGlobals.pDb, selRow ) ) { + g_globals.cGlobals.selRow = selRow; + } else { + stream_destroy( stream ); + stream = NULL; + } + g_slist_free( games ); + } + + } else if ( !!params->fileName && file_exists( params->fileName ) ) { stream = streamFromFile( &g_globals.cGlobals, params->fileName, &g_globals ); #ifdef USE_SQLITE @@ -1830,8 +1976,8 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) cGlobals->dict = makeDictForStream( cGlobals, stream ); } (void)game_makeFromStream( MEMPOOL stream, &cGlobals->game, - &cGlobals->gi, cGlobals->dict, &cGlobals->dicts, - cGlobals->util, + &cGlobals->gi, cGlobals->dict, + &cGlobals->dicts, cGlobals->util, (DrawCtx*)g_globals.draw, &g_globals.cGlobals.cp, &procs ); @@ -1845,6 +1991,8 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) game_makeNewGame( MEMPOOL &cGlobals->game, &cGlobals->gi, cGlobals->util, (DrawCtx*)g_globals.draw, &g_globals.cGlobals.cp, &procs, params->gameSeed ); + g_globals.cGlobals.selRow = -1; + saveGame( &g_globals.cGlobals ); } #ifndef XWFEATURE_STANDALONE_ONLY diff --git a/xwords4/linux/cursesmain.h b/xwords4/linux/cursesmain.h index f520c9eeb..3d21ae3ce 100644 --- a/xwords4/linux/cursesmain.h +++ b/xwords4/linux/cursesmain.h @@ -68,6 +68,7 @@ struct CursesAppGlobals { XP_Bool doDraw; const struct MenuList* menuList; XP_U16 nLinesMenu; + gchar* lastErr; union { struct { diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c index c0f217e10..d6255d48d 100644 --- a/xwords4/linux/gamesdb.c +++ b/xwords4/linux/gamesdb.c @@ -141,14 +141,14 @@ summarize( CommonGlobals* cGlobals ) } GSList* -listGames( GtkAppGlobals* apg ) +listGames( sqlite3* pDb ) { GSList* list = NULL; sqlite3_stmt *ppStmt; - int result = sqlite3_prepare_v2( apg->pDb, - "SELECT rowid FROM games ORDER BY rowid", -1, - &ppStmt, NULL ); + int result = sqlite3_prepare_v2( pDb, + "SELECT rowid FROM games ORDER BY rowid", + -1, &ppStmt, NULL ); XP_ASSERT( SQLITE_OK == result ); while ( NULL != ppStmt ) { switch( sqlite3_step( ppStmt ) ) { @@ -173,7 +173,7 @@ listGames( GtkAppGlobals* apg ) } XP_Bool -getGameInfo( GtkAppGlobals* apg, sqlite3_int64 rowid, GameInfo* gib ) +getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib ) { XP_Bool success = XP_FALSE; const char* fmt = "SELECT room, ended, turn, nmoves, nmissing " @@ -182,7 +182,7 @@ getGameInfo( GtkAppGlobals* apg, sqlite3_int64 rowid, GameInfo* gib ) snprintf( query, sizeof(query), fmt, rowid ); sqlite3_stmt* ppStmt; - int result = sqlite3_prepare_v2( apg->pDb, query, -1, &ppStmt, NULL ); + int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL ); XP_ASSERT( SQLITE_OK == result ); result = sqlite3_step( ppStmt ); if ( SQLITE_ROW == result ) { diff --git a/xwords4/linux/gamesdb.h b/xwords4/linux/gamesdb.h index 97fccee8f..3272c3c20 100644 --- a/xwords4/linux/gamesdb.h +++ b/xwords4/linux/gamesdb.h @@ -44,8 +44,8 @@ void writeToDB( XWStreamCtxt* stream, void* closure ); void summarize( CommonGlobals* cGlobals ); /* Return GSList whose data is (ptrs to) rowids */ -GSList* listGames( GtkAppGlobals* apg ); -XP_Bool getGameInfo( GtkAppGlobals* apg, sqlite3_int64 rowid, GameInfo* gib ); +GSList* listGames( sqlite3* dbp ); +XP_Bool getGameInfo( sqlite3* dbp, sqlite3_int64 rowid, GameInfo* gib ); XP_Bool loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ); void deleteGame( sqlite3* pDb, sqlite3_int64 rowid ); diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 935f49839..b0ee2c6f6 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -2629,38 +2629,4 @@ makeNewGame( GtkGameGlobals* globals ) return success; } -void -gameGotBuf( GtkGameGlobals* globals, XP_Bool hasDraw, const XP_U8* buf, - XP_U16 len ) -{ - XP_LOGF( "%s(hasDraw=%d)", __func__, hasDraw ); - XP_Bool redraw = XP_FALSE; - XWGame* game = &globals->cGlobals.game; - XWStreamCtxt* stream = stream_from_msgbuf( &globals->cGlobals, buf, len ); - if ( !!stream ) { - if ( comms_checkIncomingStream( game->comms, stream, NULL ) ) { - redraw = server_receiveMessage( game->server, stream ); - if ( redraw ) { - saveGame( &globals->cGlobals ); - } - } - stream_destroy( stream ); - - /* if there's something to draw resulting from the message, we - need to give the main loop time to reflect that on the screen - before giving the server another shot. So just call the idle - proc. */ - if ( hasDraw && redraw ) { - gtk_util_requestTime( globals->cGlobals.util ); - } else { - for ( int ii = 0; ii < 4; ++ii ) { - redraw = server_do( game->server ) || redraw; - } - } - if ( hasDraw && redraw ) { - board_draw( game->board ); - } - } -} - #endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkboard.h b/xwords4/linux/gtkboard.h index 4253b5cb6..29436c4f5 100644 --- a/xwords4/linux/gtkboard.h +++ b/xwords4/linux/gtkboard.h @@ -169,8 +169,6 @@ typedef struct GtkGameGlobals { void initGlobals( GtkGameGlobals* globals, LaunchParams* params ); void freeGlobals( GtkGameGlobals* globals ); XP_Bool makeNewGame( GtkGameGlobals* globals ); -void gameGotBuf( GtkGameGlobals* globals, XP_Bool haveDraw, - const XP_U8* buf, XP_U16 len ); XP_Bool loadGameNoDraw( GtkGameGlobals* globals, LaunchParams* params, sqlite3* pDb, sqlite3_int64 rowid ); void destroy_board_window( GtkWidget* widget, GtkGameGlobals* globals ); diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index cc57d1c89..d98211f94 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -31,7 +31,6 @@ static void onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid, XP_Bool isNew ); static void updateButtons( GtkAppGlobals* apg ); -static void sendRelayReg( GtkAppGlobals* apg ); static void recordOpened( GtkAppGlobals* apg, GtkGameGlobals* globals ) @@ -302,7 +301,7 @@ makeGamesWindow( GtkAppGlobals* apg ) gtk_widget_show( list ); - GSList* games = listGames( apg ); + GSList* games = listGames( apg->pDb ); for ( GSList* iter = games; !!iter; iter = iter->next ) { sqlite3_int64* rowid = (sqlite3_int64*)iter->data; onNewData( apg, *rowid, XP_TRUE ); @@ -347,13 +346,13 @@ static void onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid, XP_Bool isNew ) { GameInfo gib; - if ( getGameInfo( apg, rowid, &gib ) ) { + if ( getGameInfo( apg->pDb, rowid, &gib ) ) { add_to_list( apg->listWidget, rowid, isNew, &gib ); } } static gboolean -app_socket_proc( GIOChannel* source, GIOCondition condition, gpointer data ) +gtk_app_socket_proc( GIOChannel* source, GIOCondition condition, gpointer data ) { if ( 0 != (G_IO_IN & condition) ) { GtkAppGlobals* apg = (GtkAppGlobals*)data; @@ -372,13 +371,14 @@ app_socket_proc( GIOChannel* source, GIOCondition condition, gpointer data ) } static void -socketChanged( void* closure, int newSock, int XP_UNUSED(oldSock), - SockReceiver proc, void* procClosure ) +gtkSocketChanged( void* closure, int newSock, int XP_UNUSED(oldSock), + SockReceiver proc, void* procClosure ) { GtkAppGlobals* apg = (GtkAppGlobals*)closure; SourceData* sd = g_malloc( sizeof(*sd) ); sd->channel = g_io_channel_unix_new( newSock ); - sd->watch = g_io_add_watch( sd->channel, G_IO_IN | G_IO_ERR, app_socket_proc, apg ); + sd->watch = g_io_add_watch( sd->channel, G_IO_IN | G_IO_ERR, + gtk_app_socket_proc, apg ); sd->proc = proc; sd->procClosure = procClosure; apg->sources = g_list_append( apg->sources, sd ); @@ -397,11 +397,11 @@ gtkGotBuf( void* closure, const XP_U8* buf, XP_U16 len ) GtkGameGlobals* globals = findGame( apg, gameToken ); if ( !!globals ) { - gameGotBuf( globals, XP_TRUE, buf, len ); + gameGotBuf( &globals->cGlobals, XP_TRUE, buf, len ); } else { GtkGameGlobals tmpGlobals; if ( loadGameNoDraw( &tmpGlobals, apg->params, apg->pDb, gameToken ) ) { - gameGotBuf( &tmpGlobals, XP_FALSE, buf, len ); + gameGotBuf( &tmpGlobals.cGlobals, XP_FALSE, buf, len ); saveGame( &tmpGlobals.cGlobals ); } freeGlobals( &tmpGlobals ); @@ -440,7 +440,7 @@ gtkDevIDChanged( void* closure, const XP_UCHAR* devID ) } else { XP_LOGF( "%s: bad relayid", __func__ ); db_remove( apg->pDb, KEY_RDEVID ); - sendRelayReg( apg ); + sendRelayReg( apg->params, apg->pDb ); } } @@ -482,27 +482,6 @@ handle_sigintterm( int XP_UNUSED(sig) ) handle_destroy( NULL, g_globals_for_signal ); } -static void -sendRelayReg( GtkAppGlobals* apg ) -{ - LaunchParams* params = apg->params; - XP_UCHAR devIDBuf[64] = {0}; - XP_UCHAR* devID; - DevIDType typ = ID_TYPE_RELAY; - if ( !!params->rDevID ) { - devID = params->rDevID; - } else { - db_fetch( apg->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); - if ( '\0' != devIDBuf[0] ) { - devID = devIDBuf; - } else { - devID = params->devID; - typ = ID_TYPE_LINUX; - } - } - relaycon_reg( params, devID, typ ); -} - int gtkmain( LaunchParams* params ) { @@ -516,20 +495,19 @@ gtkmain( LaunchParams* params ) apg.selRows = g_array_new( FALSE, FALSE, sizeof( sqlite3_int64 ) ); apg.params = params; apg.pDb = openGamesDB( params->dbName ); - params->socketChanged = socketChanged; - params->socketChangedClosure = &apg; RelayConnProcs procs = { .msgReceived = gtkGotBuf, .msgNoticeReceived = gtkNoticeRcvd, .devIDChanged = gtkDevIDChanged, .msgErrorMsg = gtkErrorMsgRcvd, + .socketChanged = gtkSocketChanged, }; relaycon_init( params, &procs, &apg, params->connInfo.relay.relayName, params->connInfo.relay.defaultSendPort ); - sendRelayReg( &apg ); + sendRelayReg( params, apg.pDb ); apg.window = makeGamesWindow( &apg ); gtk_main(); diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index c25f5b7f9..41bb9816d 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -172,6 +172,54 @@ makeDictForStream( CommonGlobals* cGlobals, XWStreamCtxt* stream ) return dict; } +void +gameGotBuf( CommonGlobals* cGlobals, XP_Bool hasDraw, const XP_U8* buf, + XP_U16 len ) +{ + XP_LOGF( "%s(hasDraw=%d)", __func__, hasDraw ); + XP_Bool redraw = XP_FALSE; + XWGame* game = &cGlobals->game; + XWStreamCtxt* stream = stream_from_msgbuf( cGlobals, buf, len ); + if ( !!stream ) { + if ( comms_checkIncomingStream( game->comms, stream, NULL ) ) { + redraw = server_receiveMessage( game->server, stream ); + if ( redraw ) { + saveGame( cGlobals ); + } + } + stream_destroy( stream ); + + /* if there's something to draw resulting from the message, we + need to give the main loop time to reflect that on the screen + before giving the server another shot. So just call the idle + proc. */ + if ( hasDraw && redraw ) { + util_requestTime( cGlobals->util ); + } else { + for ( int ii = 0; ii < 4; ++ii ) { + redraw = server_do( game->server ) || redraw; + } + } + if ( hasDraw && redraw ) { + board_draw( game->board ); + } + } +} + +gint +requestMsgsIdle( gpointer data ) +{ + CommonGlobals* cGlobals = (CommonGlobals*)data; + XP_UCHAR devIDBuf[64] = {0}; + db_fetch( cGlobals->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); + if ( '\0' != devIDBuf[0] ) { + relaycon_requestMsgs( cGlobals->params, devIDBuf ); + } else { + XP_LOGF( "%s: not requesting messages as don't have relay id", __func__ ); + } + return 0; /* don't run again */ +} + void writeToFile( XWStreamCtxt* stream, void* closure ) { @@ -801,6 +849,26 @@ linShiftFocus( CommonGlobals* cGlobals, XP_Key key, const BoardObjectType* order } /* linShiftFocus */ #endif +void +sendRelayReg( LaunchParams* params, sqlite3* pDb ) +{ + XP_UCHAR devIDBuf[64] = {0}; + XP_UCHAR* devID; + DevIDType typ = ID_TYPE_RELAY; + if ( !!params->rDevID ) { + devID = params->rDevID; + } else { + db_fetch( pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); + if ( '\0' != devIDBuf[0] ) { + devID = devIDBuf; + } else { + devID = params->devID; + typ = ID_TYPE_LINUX; + } + } + relaycon_reg( params, devID, typ ); +} + #ifdef XWFEATURE_RELAY static int linux_init_relay_socket( CommonGlobals* cGlobals, const CommsAddrRec* addrRec ) diff --git a/xwords4/linux/linuxmain.h b/xwords4/linux/linuxmain.h index c52f0c356..86bdedd72 100644 --- a/xwords4/linux/linuxmain.h +++ b/xwords4/linux/linuxmain.h @@ -103,6 +103,12 @@ DictionaryCtxt* makeDictForStream( CommonGlobals* cGlobals, void linuxSetIsServer( CommonGlobals* cGlobals, XP_Bool isServer ); void linuxChangeRoles( CommonGlobals* cGlobals ); +void sendRelayReg( LaunchParams* params, sqlite3* pDb ); +void gameGotBuf( CommonGlobals* globals, XP_Bool haveDraw, + const XP_U8* buf, XP_U16 len ); +gboolean app_socket_proc( GIOChannel* source, GIOCondition condition, + gpointer data ); + /* void initParams( LaunchParams* params ); */ /* void freeParams( LaunchParams* params ); */ diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index d0cd1c0fa..d3b22c4e1 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -55,8 +55,6 @@ typedef struct LaunchParams { GSList* dictDirs; char* fileName; char* dbName; - NewSocketProc socketChanged; - void* socketChangedClosure; XP_U16 saveFailPct; const XP_UCHAR* playerDictNames[MAX_NUM_PLAYERS]; #ifdef USE_SQLITE diff --git a/xwords4/linux/relaycon.c b/xwords4/linux/relaycon.c index 0b14ded54..c97e82dfe 100644 --- a/xwords4/linux/relaycon.c +++ b/xwords4/linux/relaycon.c @@ -56,8 +56,8 @@ relaycon_init( LaunchParams* params, const RelayConnProcs* procs, storage->procsClosure = procsClosure; storage->socket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); - (*params->socketChanged)( params->socketChangedClosure, storage->socket, -1, - relaycon_receive, params ); + (*procs->socketChanged)( procsClosure, storage->socket, -1, + relaycon_receive, params ); XP_MEMSET( &storage->saddr, 0, sizeof(storage->saddr) ); storage->saddr.sin_family = PF_INET; diff --git a/xwords4/linux/relaycon.h b/xwords4/linux/relaycon.h index 1e1de55f7..19324ed7d 100644 --- a/xwords4/linux/relaycon.h +++ b/xwords4/linux/relaycon.h @@ -27,6 +27,9 @@ typedef struct _Procs { void (*msgNoticeReceived)( void* closure, XP_U32 gameToken ); void (*devIDChanged)( void* closure, const XP_UCHAR* devID ); void (*msgErrorMsg)( void* closure, const XP_UCHAR* msg ); + void (*socketChanged)( void* closure, int newSock, int oldSock, + SockReceiver proc, void* procClosure ); + } RelayConnProcs; void relaycon_init( LaunchParams* params, const RelayConnProcs* procs, From cbb82eb5e8a29b852fe5f53e80835839eb8a2fe2 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 24 Jan 2013 08:00:53 -0800 Subject: [PATCH 061/116] add --via-udp option -- which causes app to crash because the memory leak detector is working --- xwords4/linux/scripts/discon_ok2.sh | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/xwords4/linux/scripts/discon_ok2.sh b/xwords4/linux/scripts/discon_ok2.sh index 7abda93ab..fd6c8872a 100755 --- a/xwords4/linux/scripts/discon_ok2.sh +++ b/xwords4/linux/scripts/discon_ok2.sh @@ -4,6 +4,7 @@ set -u -e APP_NEW="" APP_NEW_PARAMS="" NGAMES="" +USE_UDP="" UPGRADE_ODDS="" NROOMS="" HOST="" @@ -166,7 +167,11 @@ build_cmds() { DEV=0 for NLOCALS in ${LOCALS[@]}; do DEV=$((DEV + 1)) - FILE="${LOGDIR}/GAME_${GAME}_${DEV}.xwg" + if [ -n "$USE_UDP" ]; then + FILE="${LOGDIR}/GAME_${GAME}_${DEV}.sql3" + else + FILE="${LOGDIR}/GAME_${GAME}_${DEV}.xwg" + fi LOG=${LOGDIR}/${GAME}_${DEV}_LOG.txt > $LOG # clear the log @@ -187,7 +192,12 @@ build_cmds() { PARAMS="$PARAMS $BOARD_SIZE --room $ROOM --trade-pct 20 --sort-tiles " [ $UNDO_PCT -gt 0 ] && PARAMS="$PARAMS --undo-pct $UNDO_PCT " PARAMS="$PARAMS --game-dict $DICT --port $PORT --host $HOST " - PARAMS="$PARAMS --file $FILE --slow-robot 1:3 --skip-confirm" + PARAMS="$PARAMS --slow-robot 1:3 --skip-confirm" + if [ -n "$USE_UDP" ]; then + PARAMS="$PARAMS --db $FILE" + else + PARAMS="$PARAMS --file $FILE" + fi PARAMS="$PARAMS --drop-nth-packet $DROP_N $PLAT_PARMS" # PARAMS="$PARAMS --savefail-pct 10" [ -n "$SEED" ] && PARAMS="$PARAMS --seed $RANDOM" @@ -514,6 +524,7 @@ function getArg() { function usage() { [ $# -gt 0 ] && echo "Error: $1" >&2 echo "Usage: $(basename $0) \\" >&2 + echo " [--via-udp] \\" >&2 echo " [--game-dict ]* \\" >&2 echo " [--old-app &2 echo " [--new-app &2 @@ -538,6 +549,9 @@ function usage() { while [ "$#" -gt 0 ]; do case $1 in + --via-udp) + USE_UDP=true + ;; --num-games) NGAMES=$(getArg $*) shift From 481f344ff49dd448027e3e79f2cbea0295001c7f Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 24 Jan 2013 08:08:53 -0800 Subject: [PATCH 062/116] fix failure to clean up on exit --- xwords4/linux/cursesmain.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index d3c1a87c0..c5a409fde 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -2107,6 +2107,11 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) endwin(); + if ( !!params->dbName ) { + closeGamesDB( g_globals.cGlobals.pDb ); + } + relaycon_cleanup( params ); + linux_util_vt_destroy( g_globals.cGlobals.util ); } /* cursesmain */ #endif /* PLATFORM_NCURSES */ From 0fad19e38a31e3972d6829031dccdda190b2f4dd Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 24 Jan 2013 18:56:10 -0800 Subject: [PATCH 063/116] make it possible to mix udp and non-udp games (which currently crashes the relay very rarely) --- xwords4/linux/scripts/discon_ok2.sh | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/xwords4/linux/scripts/discon_ok2.sh b/xwords4/linux/scripts/discon_ok2.sh index fd6c8872a..0c6403fa0 100755 --- a/xwords4/linux/scripts/discon_ok2.sh +++ b/xwords4/linux/scripts/discon_ok2.sh @@ -4,7 +4,7 @@ set -u -e APP_NEW="" APP_NEW_PARAMS="" NGAMES="" -USE_UDP="" +UDP_PCT=0 UPGRADE_ODDS="" NROOMS="" HOST="" @@ -167,10 +167,12 @@ build_cmds() { DEV=0 for NLOCALS in ${LOCALS[@]}; do DEV=$((DEV + 1)) - if [ -n "$USE_UDP" ]; then - FILE="${LOGDIR}/GAME_${GAME}_${DEV}.sql3" - else + if [ $((RANDOM % 100)) -gt $UDP_PCT ]; then FILE="${LOGDIR}/GAME_${GAME}_${DEV}.xwg" + USE_UDP="" + else + FILE="${LOGDIR}/GAME_${GAME}_${DEV}.sql3" + USE_UDP=1 fi LOG=${LOGDIR}/${GAME}_${DEV}_LOG.txt > $LOG # clear the log @@ -524,7 +526,7 @@ function getArg() { function usage() { [ $# -gt 0 ] && echo "Error: $1" >&2 echo "Usage: $(basename $0) \\" >&2 - echo " [--via-udp] \\" >&2 + echo " [--via-udp ] \\" >&2 echo " [--game-dict ]* \\" >&2 echo " [--old-app &2 echo " [--new-app &2 @@ -550,7 +552,8 @@ function usage() { while [ "$#" -gt 0 ]; do case $1 in --via-udp) - USE_UDP=true + UDP_PCT=$(getArg $*) + shift ;; --num-games) NGAMES=$(getArg $*) From 498498a2bf33d393e29cd2615dda45621603714e Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 24 Jan 2013 19:20:35 -0800 Subject: [PATCH 064/116] remove gameToken param from XWPDEV_HAVEMSGS message since it's for the whole device rather than one game on it. --- xwords4/linux/cursesmain.c | 2 +- xwords4/linux/gtkmain.c | 2 +- xwords4/linux/relaycon.c | 8 +++----- xwords4/linux/relaycon.h | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index c5a409fde..efd80fb00 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -1758,7 +1758,7 @@ curses_requestMsgs( gpointer data ) static void -cursesNoticeRcvd( void* closure, XP_U32 XP_UNUSED(gameToken) ) +cursesNoticeRcvd( void* closure ) { LOG_FUNC(); CursesAppGlobals* globals = (CursesAppGlobals*)closure; diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index d98211f94..1f381f394 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -423,7 +423,7 @@ requestMsgs( gpointer data ) } static void -gtkNoticeRcvd( void* closure, XP_U32 XP_UNUSED(gameToken) ) +gtkNoticeRcvd( void* closure ) { LOG_FUNC(); GtkAppGlobals* apg = (GtkAppGlobals*)closure; diff --git a/xwords4/linux/relaycon.c b/xwords4/linux/relaycon.c index c97e82dfe..cc672aa69 100644 --- a/xwords4/linux/relaycon.c +++ b/xwords4/linux/relaycon.c @@ -90,6 +90,7 @@ XP_S16 relaycon_send( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, XP_U32 gameToken, const CommsAddrRec* XP_UNUSED(addrRec) ) { + XP_ASSERT( 0 != gameToken ); ssize_t nSent = -1; RelayConStorage* storage = getStorage( params ); @@ -114,6 +115,7 @@ relaycon_sendnoconn( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, const XP_UCHAR* relayID, XP_U32 gameToken ) { XP_LOGF( "%s(relayID=%s)", __func__, relayID ); + XP_ASSERT( 0 != gameToken ); XP_U16 indx = 0; ssize_t nSent = -1; RelayConStorage* storage = getStorage( params ); @@ -202,11 +204,7 @@ relaycon_receive( void* closure, int socket ) (*storage->procs.devIDChanged)( storage->procsClosure, NULL ); break; case XWPDEV_HAVEMSGS: { - XP_U32 gameToken; - XP_MEMCPY( &gameToken, ptr, sizeof(gameToken) ); - ptr += sizeof( gameToken ); - (*storage->procs.msgNoticeReceived)( storage->procsClosure, - ntohl(gameToken) ); + (*storage->procs.msgNoticeReceived)( storage->procsClosure ); break; } case XWPDEV_ALERT: { diff --git a/xwords4/linux/relaycon.h b/xwords4/linux/relaycon.h index 19324ed7d..f2498c519 100644 --- a/xwords4/linux/relaycon.h +++ b/xwords4/linux/relaycon.h @@ -24,7 +24,7 @@ typedef struct _Procs { void (*msgReceived)( void* closure, const XP_U8* buf, XP_U16 len ); - void (*msgNoticeReceived)( void* closure, XP_U32 gameToken ); + void (*msgNoticeReceived)( void* closure ); void (*devIDChanged)( void* closure, const XP_UCHAR* devID ); void (*msgErrorMsg)( void* closure, const XP_UCHAR* msg ); void (*socketChanged)( void* closure, int newSock, int oldSock, From 481e50aaae2dc2dee42dbb4aa9ac4985d5d4e170 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 24 Jan 2013 19:23:05 -0800 Subject: [PATCH 065/116] don't bother pushing do-we-ack decision out of static method --- xwords4/relay/udpack.cpp | 23 ++++++++++++----------- xwords4/relay/udpack.h | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/xwords4/relay/udpack.cpp b/xwords4/relay/udpack.cpp index 2c8d61169..297032c7c 100644 --- a/xwords4/relay/udpack.cpp +++ b/xwords4/relay/udpack.cpp @@ -26,7 +26,13 @@ UDPAckTrack* UDPAckTrack::s_self = NULL; /* static*/ uint32_t UDPAckTrack::nextPacketID( XWRelayReg cmd ) { - return get()->nextPacketIDImpl( cmd ); + uint32_t result; + if ( XWPDEV_ACK == cmd || XWPDEV_ALERT == cmd ) { + result = 0; + } else { + result = get()->nextPacketIDImpl(); + } + return result; } /* static*/ void @@ -55,17 +61,12 @@ UDPAckTrack::UDPAckTrack() } uint32_t -UDPAckTrack::nextPacketIDImpl( XWRelayReg cmd ) +UDPAckTrack::nextPacketIDImpl() { - uint32_t result; - if ( XWPDEV_ACK == cmd || XWPDEV_ALERT == cmd ) { - result = 0; - } else { - MutexLock ml( &m_mutex ); - AckRecord record; - result = ++m_nextID; - m_pendings.insert( pair(result, record) ); - } + MutexLock ml( &m_mutex ); + AckRecord record; + uint32_t result = ++m_nextID; + m_pendings.insert( pair(result, record) ); return result; } diff --git a/xwords4/relay/udpack.h b/xwords4/relay/udpack.h index 1e0fa3a80..5df604154 100644 --- a/xwords4/relay/udpack.h +++ b/xwords4/relay/udpack.h @@ -38,7 +38,7 @@ class UDPAckTrack { static UDPAckTrack* get(); static void* thread_main( void* arg ); UDPAckTrack(); - uint32_t nextPacketIDImpl( XWRelayReg cmd ); + uint32_t nextPacketIDImpl(); void recordAckImpl( uint32_t packetID ); void* threadProc(); From 55c117c9fc0b6410f221c7aae45bc610e00d3473 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 24 Jan 2013 19:25:32 -0800 Subject: [PATCH 066/116] fix assert by nulling out variable --- xwords4/relay/udpqueue.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/xwords4/relay/udpqueue.cpp b/xwords4/relay/udpqueue.cpp index 8d768cc1d..0f3c03cb1 100644 --- a/xwords4/relay/udpqueue.cpp +++ b/xwords4/relay/udpqueue.cpp @@ -37,6 +37,7 @@ UdpThreadClosure::logStats() } UdpQueue::UdpQueue() + :m_cb(NULL) { pthread_mutex_init ( &m_queueMutex, NULL ); pthread_cond_init( &m_queueCondVar, NULL ); From 5f2d62885578fba8c08802fec1c7212652d5a223 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 24 Jan 2013 19:33:02 -0800 Subject: [PATCH 067/116] update protocol description --- xwords4/relay/xwrelay.h | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/xwords4/relay/xwrelay.h b/xwords4/relay/xwrelay.h index 0365a9b2a..de5fac534 100644 --- a/xwords4/relay/xwrelay.h +++ b/xwords4/relay/xwrelay.h @@ -33,41 +33,44 @@ typedef #endif enum { XWPDEV_NONE /* 0 is an illegal value */ + /* All messages have the following six-byte header + * proto: 1 byte + * msgID: 4 byte unsigned long, 0 an illegal value + * cmd: 1 byte, one of the values below. + */ + ,XWPDEV_ALERT /* relay->device: provides a string message to present to the user (with device allowed not to present the same string more than once) - format: proto: 1, this enum: 1, - null-terminnated string: varies */ + format: header, null-terminnated string: varies */ ,XWPDEV_REG /* dev->relay: device registers self and self-selected (e.g. gcm) or assigned devid - format: proto: 1; this enum: 1; idType: 1, + format: header, idType: 1, idLen: 2, id: */ ,XWPDEV_REGRSP /* relay->device: if non-relay-assigned devid type was given, this gives the relay-assigned one to be used from now on. - format: proto: 1, this enum: 1, idLen: 2, id: + format: header, idLen: 2, id: */ ,XWPDEV_PING /* device->relay: keep the UDP connection - open. format: proto: 1, this enum: 1. */ + open. header. */ ,XWPDEV_HAVEMSGS /* Relay->device: check messages for this - game. format: proto: 1, this enum: 1; clientToken: 4 */ + game. format: header */ ,XWPDEV_RQSTMSGS /* device->relay: got any messages for me? - format: proto: 1, this enum 1, devID: 4 [, clientToken: 4] + format: header, devID: 4 [, clientToken: 4] */ ,XWPDEV_MSG /* dev->relay and relay->dev: norm: a message from a game to - the relay format: proto: 1, this enum: 1, - clientToken: 4, message*/ + the relay format: header, clientToken: 4, message*/ ,XWPDEV_MSGNOCONN /* dev->relay in the proxy format that includes relayID (connname:hid) and seems to be reserved for relay FWD messages. - format: proto: 1, this enum: 1, - clientToken: 4; -terminated-connname: + format: header, clientToken: 4; -terminated-connname: varies, message: varies */ ,XWPDEV_MSGRSP /* relay->dev: conveys error on receipt of XWPDEV_MSG */ @@ -75,7 +78,11 @@ enum { XWPDEV_NONE /* 0 is an illegal value */ ,XWPDEV_BADREG /* relay->dev. You sent me a relayID via XWPDEV_REG but I've never heard of it */ - ,XWPDEV_ACK + ,XWPDEV_ACK /* tells recipient its message has been + received. This is for debugging, and maybe + later for timing keepAlives based on + firewall timeouts. format: header, msgID: 4 + */ } #ifndef CANT_DO_TYPEDEF From 036c908b72b0b575bad51af9edd0a4be82f9d51b Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 24 Jan 2013 19:33:33 -0800 Subject: [PATCH 068/116] remove token from XWPDEV_HAVEMSGS message --- xwords4/relay/xwrelay.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index a1ca3da04..e6a66b2c2 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -470,10 +470,7 @@ send_havemsgs( const AddrInfo* addr ) socket = g_udpsock; } - AddrInfo::ClientToken clientToken = htonl(addr->clientToken()); - send_via_udp( socket, addr->sockaddr(), XWPDEV_HAVEMSGS, - &clientToken, sizeof(clientToken), - NULL ); + send_via_udp( socket, addr->sockaddr(), XWPDEV_HAVEMSGS, NULL ); } /* A CONNECT message from a device gives us the hostID and socket we'll @@ -1203,7 +1200,7 @@ registerDevice( const DevID* devID, const AddrInfo::AddrUnion* saddr ) if ( dbMgr->updateDevice( relayID, true ) ) { int nMsgs = dbMgr->CountStoredMessages( relayID ); if ( 0 < nMsgs ) { - AddrInfo addr( -1, 0, saddr ); + AddrInfo addr( -1, -1, saddr ); send_havemsgs( &addr ); } } else { @@ -1320,7 +1317,7 @@ udp_thread_proc( UdpThreadClosure* utc ) } case XWPDEV_MSGNOCONN: { AddrInfo::ClientToken clientToken; - if ( getNetLong( &ptr, end, &clientToken ) ) { + if ( getNetLong( &ptr, end, &clientToken ) && 0 != clientToken ) { HostID hid; char connName[MAX_CONNNAME_LEN+1]; if ( !parseRelayID( &ptr, end, connName, From 7253cfa313f0bd31b2d74456cb1137e4c515c97a Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 24 Jan 2013 19:34:20 -0800 Subject: [PATCH 069/116] assert that map.insert() is actually doing something in those cases where I expect it to. --- xwords4/relay/configs.cpp | 4 +++- xwords4/relay/crefmgr.cpp | 4 +++- xwords4/relay/lstnrmgr.cpp | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/xwords4/relay/configs.cpp b/xwords4/relay/configs.cpp index 3152f98a4..90b222149 100644 --- a/xwords4/relay/configs.cpp +++ b/xwords4/relay/configs.cpp @@ -133,7 +133,9 @@ RelayConfigs::SetValueFor( const char* key, const char* value ) m_values.erase(iter); } - m_values.insert( pair(strdup(key),strdup(value) ) ); + pair::iterator,bool> result = + m_values.insert( pair(strdup(key),strdup(value) ) ); + assert( result.second ); } ino_t diff --git a/xwords4/relay/crefmgr.cpp b/xwords4/relay/crefmgr.cpp index 290475b9b..c3441f486 100644 --- a/xwords4/relay/crefmgr.cpp +++ b/xwords4/relay/crefmgr.cpp @@ -462,7 +462,9 @@ CRefMgr::AddNew( const char* cookie, const char* connName, CookieID cid, ref->assignConnName(); - m_cookieMap.insert( pair(ref->GetCid(), ref ) ); + pair result = + m_cookieMap.insert( pair(ref->GetCid(), ref ) ); + assert( result.second ); logf( XW_LOGINFO, "%s: paired cookie %s/connName %s with cid %d", __func__, (cookie?cookie:"NULL"), connName, ref->GetCid() ); diff --git a/xwords4/relay/lstnrmgr.cpp b/xwords4/relay/lstnrmgr.cpp index 098605930..d01118413 100644 --- a/xwords4/relay/lstnrmgr.cpp +++ b/xwords4/relay/lstnrmgr.cpp @@ -171,7 +171,9 @@ ListenerMgr::addOne( int port, bool perGame ) success = sock != -1; if ( success ) { pairentry(port, perGame); - m_socks_to_ports.insert( pair >(sock, entry ) ); + pair >::iterator, bool> result + = m_socks_to_ports.insert( pair >(sock, entry ) ); + assert( result.second ); } return success; } From 084643ca820ccb99b7a3c9d33a96dc9ef7c44f35 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 24 Jan 2013 20:27:36 -0800 Subject: [PATCH 070/116] don't include dead-game messages when fetching for devices --- xwords4/relay/dbmgr.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index 6d015378c..d7704007d 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -765,7 +765,8 @@ DBMgr::CountStoredMessages( DevIDRelay relayID ) void DBMgr::GetStoredMessageIDs( DevIDRelay relayID, vector& ids ) { - const char* fmt = "SELECT id FROM " MSGS_TABLE " WHERE devid=%d"; + const char* fmt = "SELECT id FROM " MSGS_TABLE " WHERE devid=%d " + "AND connname IN (SELECT connname FROM games WHERE NOT game.dead)"; string query; string_printf( query, fmt, relayID ); // logf( XW_LOGINFO, "%s: query=\"%s\"", __func__, query.c_str() ); From af58e7c4e1604f1621c5fec456eb3f00fdba8d56 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 24 Jan 2013 20:27:56 -0800 Subject: [PATCH 071/116] comment --- xwords4/relay/xwrelay.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/xwords4/relay/xwrelay.h b/xwords4/relay/xwrelay.h index de5fac534..05edf1003 100644 --- a/xwords4/relay/xwrelay.h +++ b/xwords4/relay/xwrelay.h @@ -78,10 +78,12 @@ enum { XWPDEV_NONE /* 0 is an illegal value */ ,XWPDEV_BADREG /* relay->dev. You sent me a relayID via XWPDEV_REG but I've never heard of it */ - ,XWPDEV_ACK /* tells recipient its message has been - received. This is for debugging, and maybe - later for timing keepAlives based on - firewall timeouts. format: header, msgID: 4 + ,XWPDEV_ACK /* relay->dev (maybe) and dev->relay + (definitely). Tells recipient its message + has been received. This is for debugging, + and maybe later for timing keepAlives based + on firewall timeouts. format: header, + msgID: 4 */ } From 2e0ff09cf9354b1e8d8d403111e55d9be87e9a71 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 24 Jan 2013 21:10:41 -0800 Subject: [PATCH 072/116] fix memory leak and uninstall idle proc on the way out --- xwords4/linux/gtkboard.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index b0ee2c6f6..f8d0188ae 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -750,6 +750,7 @@ static void cleanup( GtkGameGlobals* globals ) { saveGame( &globals->cGlobals ); + g_source_remove( globals->idleID ); #ifdef XWFEATURE_BLUETOOTH linux_bt_close( &globals->cGlobals ); @@ -767,6 +768,7 @@ cleanup( GtkGameGlobals* globals ) gi_disposePlayerInfo( MEMPOOL &globals->cGlobals.gi ); linux_util_vt_destroy( globals->cGlobals.util ); + free( globals->cGlobals.util ); } /* cleanup */ GtkWidget* @@ -1766,7 +1768,7 @@ idle_func( gpointer data ) /* remove before calling server_do. If server_do puts up a dialog that calls gtk_main, then this idle proc will also apply to that event loop and bad things can happen. So kill the idle proc asap. */ - gtk_idle_remove( globals->idleID ); + g_source_remove( globals->idleID ); ServerCtxt* server = globals->cGlobals.game.server; if ( !!server && server_do( server ) ) { @@ -1781,7 +1783,7 @@ static void gtk_util_requestTime( XW_UtilCtxt* uc ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; - globals->idleID = gtk_idle_add( idle_func, globals ); + globals->idleID = g_idle_add( idle_func, globals ); } /* gtk_util_requestTime */ static XP_Bool @@ -2441,7 +2443,6 @@ initGlobalsNoDraw( GtkGameGlobals* globals, LaunchParams* params ) setupUtil( &globals->cGlobals ); setupGtkUtilCallbacks( globals, globals->cGlobals.util ); - } void From 23b7e5a2eb01f2547b4a0ff1408026c405916b98 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 24 Jan 2013 21:11:15 -0800 Subject: [PATCH 073/116] fix syntax error in query --- xwords4/relay/dbmgr.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index d7704007d..7ed59e74b 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -766,7 +766,8 @@ void DBMgr::GetStoredMessageIDs( DevIDRelay relayID, vector& ids ) { const char* fmt = "SELECT id FROM " MSGS_TABLE " WHERE devid=%d " - "AND connname IN (SELECT connname FROM games WHERE NOT game.dead)"; + "AND connname IN (SELECT connname FROM " GAMES_TABLE + " WHERE NOT " GAMES_TABLE ".dead)"; string query; string_printf( query, fmt, relayID ); // logf( XW_LOGINFO, "%s: query=\"%s\"", __func__, query.c_str() ); From c8b3050c8d41bb0f7a28b0ecea69341ff883d7d0 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 25 Jan 2013 06:20:32 -0800 Subject: [PATCH 074/116] fix duplicate key that was getting lots of extra games created --- .../android/XWords4/src/org/eehouse/android/xw4/GamesList.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index e5d9c0cc0..17e9a8cd1 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -76,7 +76,7 @@ public class GamesList extends XWExpandableListActivity private static final String RELAYIDS_EXTRA = "relayids"; private static final String ROWID_EXTRA = "rowid"; private static final String GAMEID_EXTRA = "gameid"; - private static final String REMATCH_ROWID_EXTRA = "rowid"; + private static final String REMATCH_ROWID_EXTRA = "rowid_rm"; private static final int NEW_NET_GAME_ACTION = 1; private static final int RESET_GAME_ACTION = 2; From f91af774389fcc41d0ff0611dbc93bb4771abcf9 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 25 Jan 2013 06:22:37 -0800 Subject: [PATCH 075/116] always try starting UDP threads in case Service has been stopped; implement transportSend in RelayMsgSink since relay-connection packets need it. --- .../org/eehouse/android/xw4/RelayService.java | 159 +++++++++--------- 1 file changed, 84 insertions(+), 75 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java index 963697c44..1b5a0d7d0 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java @@ -38,9 +38,10 @@ import java.util.concurrent.LinkedBlockingQueue; import junit.framework.Assert; +import org.eehouse.android.xw4.MultiService.MultiEvent; +import org.eehouse.android.xw4.jni.CommsAddrRec; import org.eehouse.android.xw4.jni.GameSummary; import org.eehouse.android.xw4.jni.UtilCtxt; -import org.eehouse.android.xw4.MultiService.MultiEvent; public class RelayService extends XWService { private static final int MAX_SEND = 1024; @@ -149,7 +150,7 @@ public class RelayService extends XWService { DbgUtils.logf( "RelayService::onStartCommand::UDP_CHANGED" ); if ( XWPrefs.getUDPEnabled( this ) ) { stopFetchThreadIf(); - startUDPThreads(); + startUDPThreadsIfNot(); registerWithRelay(); } else { stopUDPThreadsIf(); @@ -158,6 +159,7 @@ public class RelayService extends XWService { break; case SEND: case RECEIVE: + startUDPThreadsIfNot(); long rowid = intent.getLongExtra( ROWID, -1 ); byte[] msg = intent.getByteArrayExtra( BINBUFFER ); if ( SEND == cmd ) { @@ -220,87 +222,86 @@ public class RelayService extends XWService { } } - private void startUDPThreads() + private void startUDPThreadsIfNot() { - DbgUtils.logf( "startUDPThreads" ); - Assert.assertTrue( XWPrefs.getUDPEnabled( this ) ); - - if ( null == m_UDPSocket ) { - int port = XWPrefs.getDefaultRelayPort( RelayService.this ); - String host = XWPrefs.getDefaultRelayHost( RelayService.this ); - try { - m_UDPSocket = new DatagramSocket(); - InetAddress addr = InetAddress.getByName( host ); - m_UDPSocket.connect( addr, port ); // meaning: remember this address - } catch( java.net.SocketException se ) { - DbgUtils.loge( se ); - Assert.fail(); - } catch( java.net.UnknownHostException uhe ) { - DbgUtils.loge( uhe ); + if ( XWPrefs.getUDPEnabled( this ) ) { + if ( null == m_UDPSocket ) { + int port = XWPrefs.getDefaultRelayPort( RelayService.this ); + String host = XWPrefs.getDefaultRelayHost( RelayService.this ); + try { + m_UDPSocket = new DatagramSocket(); + InetAddress addr = InetAddress.getByName( host ); + m_UDPSocket.connect( addr, port ); // remember this address + } catch( java.net.SocketException se ) { + DbgUtils.loge( se ); + Assert.fail(); + } catch( java.net.UnknownHostException uhe ) { + DbgUtils.loge( uhe ); + } + } else { + Assert.assertTrue( m_UDPSocket.isConnected() ); + DbgUtils.logf( "m_UDPSocket not null" ); } - } else { - Assert.assertTrue( m_UDPSocket.isConnected() ); - DbgUtils.logf( "m_UDPSocket not null" ); - } - if ( null == m_UDPReadThread ) { - m_UDPReadThread = new Thread( null, new Runnable() { - public void run() { - DbgUtils.logf( "read thread running" ); - byte[] buf = new byte[1024]; - for ( ; ; ) { - DatagramPacket packet = - new DatagramPacket( buf, buf.length ); - try { - DbgUtils.logf( "UPD read thread blocking on receive" ); - m_UDPSocket.receive( packet ); - DbgUtils.logf( "UPD read thread: receive returned" ); - } catch( java.io.IOException ioe ) { - DbgUtils.loge( ioe ); - break; // ??? + if ( null == m_UDPReadThread ) { + m_UDPReadThread = new Thread( null, new Runnable() { + public void run() { + DbgUtils.logf( "read thread running" ); + byte[] buf = new byte[1024]; + for ( ; ; ) { + DatagramPacket packet = + new DatagramPacket( buf, buf.length ); + try { + DbgUtils.logf( "UPD read thread blocking on receive" ); + m_UDPSocket.receive( packet ); + DbgUtils.logf( "UPD read thread: receive returned" ); + } catch( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + break; // ??? + } + DbgUtils.logf( "received %d bytes", packet.getLength() ); + gotPacket( packet ); } - DbgUtils.logf( "received %d bytes", packet.getLength() ); - gotPacket( packet ); + DbgUtils.logf( "read thread exiting" ); } - DbgUtils.logf( "read thread exiting" ); - } - }, getClass().getName() ); - m_UDPReadThread.start(); - } else { - DbgUtils.logf( "m_UDPReadThread not null and assumed to be running" ); - } + }, getClass().getName() ); + m_UDPReadThread.start(); + } else { + DbgUtils.logf( "m_UDPReadThread not null and assumed to be running" ); + } - if ( null == m_UDPWriteThread ) { - m_queue = new LinkedBlockingQueue(); - m_UDPWriteThread = new Thread( null, new Runnable() { - public void run() { - DbgUtils.logf( "write thread running" ); - for ( ; ; ) { - DatagramPacket outPacket; - try { - outPacket = m_queue.take(); - } catch ( InterruptedException ie ) { - DbgUtils.logf( "RelayService; write thread killed" ); - break; - } - if ( null == outPacket || 0 == outPacket.getLength() ) { - DbgUtils.logf( "stopping write thread" ); - break; - } - DbgUtils.logf( "Sending udp packet of length %d", - outPacket.getLength() ); - try { - m_UDPSocket.send( outPacket ); - } catch ( java.io.IOException ioe ) { - DbgUtils.loge( ioe ); + if ( null == m_UDPWriteThread ) { + m_queue = new LinkedBlockingQueue(); + m_UDPWriteThread = new Thread( null, new Runnable() { + public void run() { + DbgUtils.logf( "write thread running" ); + for ( ; ; ) { + DatagramPacket outPacket; + try { + outPacket = m_queue.take(); + } catch ( InterruptedException ie ) { + DbgUtils.logf( "RelayService; write thread killed" ); + break; + } + if ( null == outPacket || 0 == outPacket.getLength() ) { + DbgUtils.logf( "stopping write thread" ); + break; + } + DbgUtils.logf( "Sending udp packet of length %d", + outPacket.getLength() ); + try { + m_UDPSocket.send( outPacket ); + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } } + DbgUtils.logf( "write thread exiting" ); } - DbgUtils.logf( "write thread exiting" ); - } - }, getClass().getName() ); - m_UDPWriteThread.start(); - } else { - DbgUtils.logf( "m_UDPWriteThread not null and assumed to be running" ); + }, getClass().getName() ); + m_UDPWriteThread.start(); + } else { + DbgUtils.logf( "m_UDPWriteThread not null and assumed to be running" ); + } } } @@ -682,6 +683,14 @@ public class RelayService extends XWService { /***** TransportProcs interface *****/ + public int transportSend( byte[] buf, final CommsAddrRec addr, + int gameID ) + { + Assert.assertTrue( -1 != m_rowid ); + sendPacket( RelayService.this, m_rowid, buf ); + return buf.length; + } + public boolean relayNoConnProc( byte[] buf, String relayID ) { if ( -1 != m_rowid ) { From 83441c65efffced9bb1ddf7848588711137da84c Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 25 Jan 2013 07:16:38 -0800 Subject: [PATCH 076/116] debug pref to disable GCM so new stuff can be tested on-device --- .../XWords4/res/values/common_rsrc.xml | 2 ++ xwords4/android/XWords4/res/xml/xwprefs.xml | 4 +++ .../eehouse/android/xw4/GCMIntentService.java | 34 +++++++++++-------- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/xwords4/android/XWords4/res/values/common_rsrc.xml b/xwords4/android/XWords4/res/values/common_rsrc.xml index d73dcd305..261efccf5 100644 --- a/xwords4/android/XWords4/res/values/common_rsrc.xml +++ b/xwords4/android/XWords4/res/values/common_rsrc.xml @@ -39,6 +39,7 @@ key_dict_host3 key_logging_on key_udp_relay + key_drop_gcm key_show_sms key_init_hintsallowed key_init_nethintsallowed @@ -128,6 +129,7 @@ Write DB to SD card Load DB from SD card New/Experimental relay connection + Ignore GCM notices (for testing) diff --git a/xwords4/android/XWords4/res/xml/xwprefs.xml b/xwords4/android/XWords4/res/xml/xwprefs.xml index 5bf240ba9..2ca59eb67 100644 --- a/xwords4/android/XWords4/res/xml/xwprefs.xml +++ b/xwords4/android/XWords4/res/xml/xwprefs.xml @@ -299,6 +299,10 @@ android:title="@string/udp_relay" android:defaultValue="false" /> + Date: Fri, 25 Jan 2013 07:17:00 -0800 Subject: [PATCH 077/116] debug pref to disable GCM so new stuff can be tested on-device --- .../android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java index 649059f3a..3ca696182 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java @@ -39,6 +39,11 @@ public class XWPrefs { return getPrefsBoolean( context, R.string.key_udp_relay, false ); } + public static boolean getGCMIgnored( Context context ) + { + return getPrefsBoolean( context, R.string.key_drop_gcm, false ); + } + public static boolean getDebugEnabled( Context context ) { return getPrefsBoolean( context, R.string.key_enable_debug, false ); From 7ee402d2ae7589fe21f927cb1b8f2ffb3ef74662 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 25 Jan 2013 07:19:44 -0800 Subject: [PATCH 078/116] move callback into queueelem so same queue can service udp and tcp (eventually) --- xwords4/relay/udpqueue.cpp | 12 ++---------- xwords4/relay/udpqueue.h | 28 +++++++++++++++++----------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/xwords4/relay/udpqueue.cpp b/xwords4/relay/udpqueue.cpp index 0f3c03cb1..e22372add 100644 --- a/xwords4/relay/udpqueue.cpp +++ b/xwords4/relay/udpqueue.cpp @@ -37,7 +37,6 @@ UdpThreadClosure::logStats() } UdpQueue::UdpQueue() - :m_cb(NULL) { pthread_mutex_init ( &m_queueMutex, NULL ); pthread_cond_init( &m_queueCondVar, NULL ); @@ -68,9 +67,8 @@ void UdpQueue::handle( const AddrInfo::AddrUnion* saddr, unsigned char* buf, int len, QueueCallback cb ) { - UdpThreadClosure* utc = new UdpThreadClosure( saddr, buf, len ); + UdpThreadClosure* utc = new UdpThreadClosure( saddr, buf, len, cb ); MutexLock ml( &m_queueMutex ); - setCB( cb ); m_queue.push_back( utc ); pthread_cond_signal( &m_queueCondVar ); } @@ -88,7 +86,7 @@ UdpQueue::thread_main() pthread_mutex_unlock( &m_queueMutex ); utc->noteDequeued(); - (*m_cb)( utc ); + (*utc->cb())( utc ); utc->logStats(); delete utc; } @@ -104,9 +102,3 @@ UdpQueue::thread_main_static( void* closure ) return me->thread_main(); } -void -UdpQueue::setCB( QueueCallback cb ) -{ - assert( cb == m_cb || !m_cb ); - m_cb = cb; -} diff --git a/xwords4/relay/udpqueue.h b/xwords4/relay/udpqueue.h index 04f05f956..61752b5de 100644 --- a/xwords4/relay/udpqueue.h +++ b/xwords4/relay/udpqueue.h @@ -29,15 +29,23 @@ using namespace std; +class UdpThreadClosure; + +typedef void (*QueueCallback)( UdpThreadClosure* closure ); + class UdpThreadClosure { public: - UdpThreadClosure( const AddrInfo::AddrUnion* saddr, unsigned char* buf, int len ) { - m_saddr = *saddr; - m_buf = new unsigned char[len]; - memcpy( m_buf, buf, len ); - m_len = len; - m_created = time( NULL ); - } + UdpThreadClosure( const AddrInfo::AddrUnion* saddr, unsigned char* buf, + int len, QueueCallback cb ) + : m_buf(new unsigned char[len]) + , m_len(len) + , m_saddr(*saddr) + , m_cb(cb) + , m_created(time( NULL )) + { + memcpy( m_buf, buf, len ); + } + ~UdpThreadClosure() { delete m_buf; } const unsigned char* buf() const { return m_buf; } @@ -45,17 +53,17 @@ public: const AddrInfo::AddrUnion* saddr() const { return &m_saddr; } void noteDequeued() { m_dequed = time( NULL ); } void logStats(); + const QueueCallback cb() const { return m_cb; } private: unsigned char* m_buf; int m_len; AddrInfo::AddrUnion m_saddr; + QueueCallback m_cb; time_t m_created; time_t m_dequed; }; -typedef void (*QueueCallback)( UdpThreadClosure* closure ); - class UdpQueue { public: static UdpQueue* get(); @@ -67,13 +75,11 @@ class UdpQueue { private: static void* thread_main_static( void* closure ); void* thread_main(); - void setCB( QueueCallback cb ); pthread_mutex_t m_queueMutex; pthread_cond_t m_queueCondVar; deque m_queue; - QueueCallback m_cb; }; #endif From 9e1d09648df79de156c8ec9bed500b3ada65a1fd Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 26 Jan 2013 11:04:20 -0800 Subject: [PATCH 079/116] fix log statement --- xwords4/relay/cref.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/relay/cref.cpp b/xwords4/relay/cref.cpp index cbd7558c0..c956094aa 100644 --- a/xwords4/relay/cref.cpp +++ b/xwords4/relay/cref.cpp @@ -347,7 +347,7 @@ bool CookieRef::AlreadyHere( unsigned short seed, const AddrInfo* addr, HostID* prevHostID ) { - logf( XW_LOGINFO, "%s(seed=%x(%d),socket=%d)", __func__, seed, seed, socket ); + logf( XW_LOGINFO, "%s(seed=%x(%d))", __func__, seed, seed ); bool here = false; RWReadLock rrl( &m_socketsRWLock ); From 336e3cd28944aa17a5f4211d8f7947b368522cea Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 26 Jan 2013 11:54:48 -0800 Subject: [PATCH 080/116] handle all three types of message in a single thread, punting all synchronization issues to a future point where the load demands it. --- xwords4/relay/addrinfo.h | 4 ++-- xwords4/relay/tpool.cpp | 25 +++++++++++++------------ xwords4/relay/tpool.h | 11 +++++------ xwords4/relay/udpqueue.cpp | 4 ++-- xwords4/relay/udpqueue.h | 11 ++++++----- xwords4/relay/xwrelay.cpp | 33 +++++++++++++++++++++++++-------- xwords4/relay/xwrelay_priv.h | 2 -- 7 files changed, 53 insertions(+), 37 deletions(-) diff --git a/xwords4/relay/addrinfo.h b/xwords4/relay/addrinfo.h index 84145397e..1c8d04d87 100644 --- a/xwords4/relay/addrinfo.h +++ b/xwords4/relay/addrinfo.h @@ -41,8 +41,8 @@ class AddrInfo { m_isValid = false; } - AddrInfo( int socket, const AddrUnion* saddr ) { - construct( socket, saddr, true ); + AddrInfo( int socket, const AddrUnion* saddr, bool isTCP ) { + construct( socket, saddr, isTCP ); } AddrInfo( int socket, ClientToken clientToken, const AddrUnion* saddr ) { diff --git a/xwords4/relay/tpool.cpp b/xwords4/relay/tpool.cpp index 129889d40..36c5857f6 100644 --- a/xwords4/relay/tpool.cpp +++ b/xwords4/relay/tpool.cpp @@ -78,11 +78,10 @@ XWThreadPool::~XWThreadPool() } /* ~XWThreadPool */ void -XWThreadPool::Setup( int nThreads, packet_func pFunc, kill_func kFunc ) +XWThreadPool::Setup( int nThreads, kill_func kFunc ) { m_nThreads = nThreads; m_threadInfos = (ThreadInfo*)malloc( nThreads * sizeof(*m_threadInfos) ); - m_pFunc = pFunc; m_kFunc = kFunc; for ( int ii = 0; ii < nThreads; ++ii ) { @@ -116,12 +115,13 @@ XWThreadPool::Stop() } void -XWThreadPool::AddSocket( SockType stype, const AddrInfo* from ) +XWThreadPool::AddSocket( SockType stype, QueueCallback proc, const AddrInfo* from ) { { RWWriteLock ml( &m_activeSocketsRWLock ); SockInfo si; si.m_type = stype; + si.m_proc = proc; si.m_addr = *from; m_activeSockets.push_back( si ); logf( XW_LOGINFO, "%s: %d sockets active", __func__, @@ -197,7 +197,7 @@ XWThreadPool::EnqueueKill( const AddrInfo* addr, const char* const why ) } bool -XWThreadPool::get_process_packet( SockType stype, const AddrInfo* addr ) +XWThreadPool::get_process_packet( SockType stype, QueueCallback proc, const AddrInfo* addr ) { bool success = false; short packetSize; @@ -207,13 +207,14 @@ XWThreadPool::get_process_packet( SockType stype, const AddrInfo* addr ) int nRead = read_packet( addr->socket(), buf, sizeof(buf) ); if ( nRead < 0 ) { EnqueueKill( addr, "bad packet" ); - } else if ( STYPE_GAME == stype ) { - logf( XW_LOGINFO, "calling m_pFunc" ); - success = (*m_pFunc)( buf, nRead, addr ); - } else { + } else if ( STYPE_PROXY == stype && NULL != proc ) { buf[nRead] = '\0'; - handle_proxy_packet( buf, nRead, addr ); - CloseSocket( addr ); + UdpQueue::get()->handle( addr, buf, nRead+1, proc ); + } else if ( STYPE_GAME == stype && NULL != proc ) { + UdpQueue::get()->handle( addr, buf, nRead, proc ); + success = true; + } else { + assert(0); } return success; } /* get_process_packet */ @@ -260,8 +261,8 @@ XWThreadPool::real_tpool_main( ThreadInfo* tip ) switch ( pr.m_act ) { case Q_READ: assert( socket >= 0 ); - if ( get_process_packet( pr.m_info.m_type, &pr.m_info.m_addr ) ) { - AddSocket( pr.m_info.m_type, &pr.m_info.m_addr ); + if ( get_process_packet( pr.m_info.m_type, pr.m_info.m_proc, &pr.m_info.m_addr ) ) { + AddSocket( pr.m_info.m_type, pr.m_info.m_proc, &pr.m_info.m_addr ); } break; case Q_KILL: diff --git a/xwords4/relay/tpool.h b/xwords4/relay/tpool.h index 9aa38f164..f8fa997ed 100644 --- a/xwords4/relay/tpool.h +++ b/xwords4/relay/tpool.h @@ -32,6 +32,7 @@ #include #include "addrinfo.h" +#include "udpqueue.h" using namespace std; @@ -41,6 +42,7 @@ class XWThreadPool { typedef enum { STYPE_UNKNOWN, STYPE_GAME, STYPE_PROXY } SockType; typedef struct _SockInfo { SockType m_type; + QueueCallback m_proc; AddrInfo m_addr; } SockInfo; @@ -51,18 +53,16 @@ class XWThreadPool { } ThreadInfo; static XWThreadPool* GetTPool(); - typedef bool (*packet_func)( const unsigned char* buf, int bufLen, - const AddrInfo* from ); typedef void (*kill_func)( const AddrInfo* addr ); XWThreadPool(); ~XWThreadPool(); - void Setup( int nThreads, packet_func pFunc, kill_func kFunc ); + void Setup( int nThreads, kill_func kFunc ); void Stop(); /* Add to set being listened on */ - void AddSocket( SockType stype, const AddrInfo* from ); + void AddSocket( SockType stype, QueueCallback proc, const AddrInfo* from ); /* remove from tpool altogether, and close */ void CloseSocket( const AddrInfo* addr ); @@ -82,7 +82,7 @@ class XWThreadPool { void print_in_use( void ); void log_hung_threads( void ); - bool get_process_packet( SockType stype, const AddrInfo* from ); + bool get_process_packet( SockType stype, QueueCallback proc, const AddrInfo* from ); void interrupt_poll(); void* real_tpool_main( ThreadInfo* tsp ); @@ -107,7 +107,6 @@ class XWThreadPool { bool m_timeToDie; int m_nThreads; - packet_func m_pFunc; kill_func m_kFunc; ThreadInfo* m_threadInfos; diff --git a/xwords4/relay/udpqueue.cpp b/xwords4/relay/udpqueue.cpp index e22372add..724d73c16 100644 --- a/xwords4/relay/udpqueue.cpp +++ b/xwords4/relay/udpqueue.cpp @@ -64,10 +64,10 @@ UdpQueue::get() } void -UdpQueue::handle( const AddrInfo::AddrUnion* saddr, unsigned char* buf, int len, +UdpQueue::handle( const AddrInfo* addr, unsigned char* buf, int len, QueueCallback cb ) { - UdpThreadClosure* utc = new UdpThreadClosure( saddr, buf, len, cb ); + UdpThreadClosure* utc = new UdpThreadClosure( addr, buf, len, cb ); MutexLock ml( &m_queueMutex ); m_queue.push_back( utc ); pthread_cond_signal( &m_queueCondVar ); diff --git a/xwords4/relay/udpqueue.h b/xwords4/relay/udpqueue.h index 61752b5de..57cbda212 100644 --- a/xwords4/relay/udpqueue.h +++ b/xwords4/relay/udpqueue.h @@ -35,11 +35,11 @@ typedef void (*QueueCallback)( UdpThreadClosure* closure ); class UdpThreadClosure { public: - UdpThreadClosure( const AddrInfo::AddrUnion* saddr, unsigned char* buf, + UdpThreadClosure( const AddrInfo* addr, unsigned char* buf, int len, QueueCallback cb ) : m_buf(new unsigned char[len]) , m_len(len) - , m_saddr(*saddr) + , m_addr(*addr) , m_cb(cb) , m_created(time( NULL )) { @@ -50,7 +50,8 @@ public: const unsigned char* buf() const { return m_buf; } int len() const { return m_len; } - const AddrInfo::AddrUnion* saddr() const { return &m_saddr; } + const AddrInfo::AddrUnion* saddr() const { return m_addr.saddr(); } + const AddrInfo* addr() const { return &m_addr; } void noteDequeued() { m_dequed = time( NULL ); } void logStats(); const QueueCallback cb() const { return m_cb; } @@ -58,7 +59,7 @@ public: private: unsigned char* m_buf; int m_len; - AddrInfo::AddrUnion m_saddr; + AddrInfo m_addr; QueueCallback m_cb; time_t m_created; time_t m_dequed; @@ -69,7 +70,7 @@ class UdpQueue { static UdpQueue* get(); UdpQueue(); ~UdpQueue(); - void handle( const AddrInfo::AddrUnion* saddr, unsigned char* buf, int len, + void handle( const AddrInfo* addr, unsigned char* buf, int len, QueueCallback cb ); private: diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index e6a66b2c2..2d9881669 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -1096,9 +1096,22 @@ handleProxyMsgs( int sock, const AddrInfo* addr, const unsigned char* bufp, } } // handleProxyMsgs -void -handle_proxy_packet( unsigned char* buf, int len, const AddrInfo* addr ) +static void +game_thread_proc( UdpThreadClosure* utc ) { + if ( !processMessage( utc->buf(), utc->len(), utc->addr() ) ) { + XWThreadPool::GetTPool()->CloseSocket( utc->addr() ); + } +} + +static void +proxy_thread_proc( UdpThreadClosure* utc ) +{ + int len = utc->len(); + const AddrInfo* addr = utc->addr(); + const unsigned char* buf = utc->buf(); + + logf( XW_LOGINFO, "%s called", __func__ ); logf( XW_LOGVERBOSE0, "%s()", __func__ ); if ( len > 0 ) { assert( addr->isTCP() ); @@ -1171,7 +1184,8 @@ handle_proxy_packet( unsigned char* buf, int len, const AddrInfo* addr ) } } } -} /* handle_proxy_packet */ + XWThreadPool::GetTPool()->CloseSocket( addr ); +} static short addRegID( unsigned char* ptr, DevIDRelay relayID ) @@ -1373,7 +1387,7 @@ udp_thread_proc( UdpThreadClosure* utc ) static void handle_udp_packet( int udpsock ) { - unsigned char buf[512]; + unsigned char buf[MAX_MSG_LEN]; AddrInfo::AddrUnion saddr; memset( &saddr, 0, sizeof(saddr) ); socklen_t fromlen = sizeof(saddr.addr_in); @@ -1382,7 +1396,8 @@ handle_udp_packet( int udpsock ) &saddr.addr, &fromlen ); logf( XW_LOGINFO, "%s: recvfrom=>%d", __func__, nRead ); if ( 0 < nRead ) { - UdpQueue::get()->handle( &saddr, buf, nRead, udp_thread_proc ); + AddrInfo addr( udpsock, &saddr, false ); + UdpQueue::get()->handle( &addr, buf, nRead, udp_thread_proc ); } } @@ -1808,7 +1823,7 @@ main( int argc, char** argv ) (void)sigaction( SIGINT, &act, NULL ); XWThreadPool* tPool = XWThreadPool::GetTPool(); - tPool->Setup( nWorkerThreads, processMessage, killSocket ); + tPool->Setup( nWorkerThreads, killSocket ); /* set up select call */ fd_set rfds; @@ -1876,9 +1891,11 @@ main( int argc, char** argv ) "%s: accepting connection from %s on socket %d", __func__, inet_ntoa(saddr.addr_in.sin_addr), newSock ); - AddrInfo addr( newSock, &saddr ); - tPool->AddSocket( perGame ? XWThreadPool::STYPE_GAME + AddrInfo addr( newSock, &saddr, true ); + tPool->AddSocket( perGame ? XWThreadPool::STYPE_GAME : XWThreadPool::STYPE_PROXY, + perGame ? game_thread_proc + : proxy_thread_proc, &addr ); } --retval; diff --git a/xwords4/relay/xwrelay_priv.h b/xwords4/relay/xwrelay_priv.h index 84ae9d696..c8fe20909 100644 --- a/xwords4/relay/xwrelay_priv.h +++ b/xwords4/relay/xwrelay_priv.h @@ -59,8 +59,6 @@ int make_socket( unsigned long addr, unsigned short port ); void string_printf( std::string& str, const char* fmt, ... ); int read_packet( int sock, unsigned char* buf, int buflen ); -void handle_proxy_packet( unsigned char* buf, int bufLen, - const AddrInfo* addr ); const char* cmdToStr( XWRELAY_Cmd cmd ); From 2aba3c0ce102d2f59a208276a6ebb63e921c52ed Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 26 Jan 2013 11:56:20 -0800 Subject: [PATCH 081/116] load dict early enough to have lang available to comms when it first connects. --- xwords4/linux/cursesmain.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index efd80fb00..30225616f 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -1046,7 +1046,7 @@ data_socket_proc( GIOChannel* source, GIOCondition condition, gpointer data ) if ( 0 != (G_IO_IN & condition) ) { CursesAppGlobals* globals = (CursesAppGlobals*)data; int fd = g_io_channel_unix_get_fd( source ); - unsigned char buf[256]; + unsigned char buf[1024]; int nBytes; CommsAddrRec addrRec; CommsAddrRec* addrp = NULL; @@ -1292,7 +1292,7 @@ blocking_gotEvent( CursesAppGlobals* globals, int* ch ) globals ); } else { #ifndef XWFEATURE_STANDALONE_ONLY - unsigned char buf[256]; + unsigned char buf[1024]; int nBytes; CommsAddrRec addrRec; CommsAddrRec* addrp = NULL; @@ -1971,10 +1971,18 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) #endif } - if ( !!stream ) { - if ( NULL == cGlobals->dict ) { + if ( NULL == cGlobals->dict ) { + if ( !!stream ) { cGlobals->dict = makeDictForStream( cGlobals, stream ); + } else { + cGlobals->dict = + linux_dictionary_make( MEMPOOL params, + cGlobals->gi.dictName, XP_TRUE ); } + } + cGlobals->gi.dictLang = dict_getLangCode( cGlobals->dict ); + + if ( !!stream ) { (void)game_makeFromStream( MEMPOOL stream, &cGlobals->game, &cGlobals->gi, cGlobals->dict, &cGlobals->dicts, cGlobals->util, @@ -2032,11 +2040,6 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) } #endif - if ( NULL == cGlobals->dict ) { - cGlobals->dict = - linux_dictionary_make( MEMPOOL params, - cGlobals->gi.dictName, XP_TRUE ); - } model_setDictionary( cGlobals->game.model, cGlobals->dict ); setSquareBonuses( cGlobals ); positionSizeStuff( &g_globals, width, height ); From 88b20769f974c0dfe665ab92b8845c6f320041d1 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 26 Jan 2013 11:56:29 -0800 Subject: [PATCH 082/116] increase buffer size --- xwords4/linux/gtkboard.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index f8d0188ae..baa6fa1c6 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -2177,7 +2177,7 @@ newConnectionInput( GIOChannel *source, if ( (condition & G_IO_IN) != 0 ) { ssize_t nRead; - unsigned char buf[512]; + unsigned char buf[1024]; CommsAddrRec* addrp = NULL; #if defined XWFEATURE_IP_DIRECT || defined XWFEATURE_SMS CommsAddrRec addr; From b324a382e2819798452a39e3dbe770a6ff7da2fc Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 26 Jan 2013 11:58:48 -0800 Subject: [PATCH 083/116] I've seen read() return 0 after being told there's something on the socket. So in that case loop and try again five time -- in case the docs are wrong that 0 means EOF? Hmmm. --- xwords4/linux/linuxmain.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 41bb9816d..018e0b53e 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -1072,26 +1072,35 @@ linux_close_socket( CommonGlobals* cGlobals ) } int -blocking_read( int fd, unsigned char* buf, int len ) +blocking_read( int fd, unsigned char* buf, const int len ) { int nRead = 0; - while ( nRead < len ) { - XP_LOGF( "%s: blocking for %d bytes", __func__, len ); - ssize_t siz = read( fd, buf + nRead, len - nRead ); - if ( siz <= 0 ) { - XP_LOGF( "read => %d, errno=%d (\"%s\")", nRead, - errno, strerror(errno) ); - nRead = -1; + int tries; + for ( tries = 5; nRead < len && tries > 0; --tries ) { + // XP_LOGF( "%s: blocking for %d bytes", __func__, len ); + ssize_t nGot = read( fd, buf + nRead, len - nRead ); + if ( nGot == 0 ) { + XP_LOGF( "%s: read 0; let's try again (%d more times)", __func__, tries ); + usleep( 10000 ); + } else if ( nGot < 0 ) { + XP_LOGF( "read => %d (wanted %d), errno=%d (\"%s\")", nRead, + len - nRead, errno, strerror(errno) ); break; } - nRead += siz; + nRead += nGot; } + + if ( nRead < len ) { + nRead = -1; + } + return nRead; } int linux_relay_receive( CommonGlobals* cGlobals, unsigned char* buf, int bufSize ) { + LOG_FUNC(); int sock = cGlobals->socket; unsigned short tmp; ssize_t nRead = blocking_read( sock, (unsigned char*)&tmp, sizeof(tmp) ); @@ -1101,6 +1110,7 @@ linux_relay_receive( CommonGlobals* cGlobals, unsigned char* buf, int bufSize ) nRead = -1; } else { unsigned short packetSize = ntohs( tmp ); + XP_LOGF( "%s: got packet of size %d", __func__, packetSize ); assert( packetSize <= bufSize ); nRead = blocking_read( sock, buf, packetSize ); if ( nRead == packetSize ) { From 4b335ab464159f58fc090e9d03a8183fbd31c04a Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 26 Jan 2013 18:36:40 -0800 Subject: [PATCH 084/116] make function static --- xwords4/linux/linuxmain.c | 4 +++- xwords4/linux/linuxmain.h | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 018e0b53e..8d5a28b99 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -79,6 +79,8 @@ #define DEFAULT_PORT 10997 #define DEFAULT_LISTEN_PORT 4998 +static int blocking_read( int fd, unsigned char* buf, int len ); + XP_Bool file_exists( const char* fileName ) { @@ -1071,7 +1073,7 @@ linux_close_socket( CommonGlobals* cGlobals ) close( socket ); } -int +static int blocking_read( int fd, unsigned char* buf, const int len ) { int nRead = 0; diff --git a/xwords4/linux/linuxmain.h b/xwords4/linux/linuxmain.h index 86bdedd72..88a9681c4 100644 --- a/xwords4/linux/linuxmain.h +++ b/xwords4/linux/linuxmain.h @@ -73,8 +73,6 @@ XP_Bool getDictPath( const LaunchParams *params, const char* name, GSList* listDicts( const LaunchParams *params ); void saveGame( CommonGlobals* cGlobals ); -int blocking_read( int fd, unsigned char* buf, int len ); - void linux_close_socket( CommonGlobals* cGlobals ); #ifdef KEYBOARD_NAV From 2fdcea54f538163a08aaa8f92d01a31f6f8a9f53 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 26 Jan 2013 18:55:55 -0800 Subject: [PATCH 085/116] fix logging of sockets --- xwords4/relay/cref.cpp | 2 +- xwords4/relay/tpool.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/xwords4/relay/cref.cpp b/xwords4/relay/cref.cpp index c956094aa..bf61d8a70 100644 --- a/xwords4/relay/cref.cpp +++ b/xwords4/relay/cref.cpp @@ -373,7 +373,7 @@ CookieRef::AlreadyHere( HostID hid, unsigned short seed, const AddrInfo* addr, bool* spotTaken ) { logf( XW_LOGINFO, "%s(hid=%d,seed=%x(%d),socket=%d)", __func__, - hid, seed, seed, socket ); + hid, seed, seed, addr->socket() ); bool here = false; RWWriteLock rwl( &m_socketsRWLock ); diff --git a/xwords4/relay/tpool.cpp b/xwords4/relay/tpool.cpp index 36c5857f6..8d0cc1d79 100644 --- a/xwords4/relay/tpool.cpp +++ b/xwords4/relay/tpool.cpp @@ -173,7 +173,7 @@ XWThreadPool::CloseSocket( const AddrInfo* addr ) ++iter; } } - logf( XW_LOGINFO, "CLOSING socket %d", socket ); + logf( XW_LOGINFO, "CLOSING socket %d", addr->socket() ); close( addr->socket() ); /* if ( do_interrupt ) { */ /* We always need to interrupt the poll because the socket we're closing @@ -187,7 +187,7 @@ XWThreadPool::CloseSocket( const AddrInfo* addr ) void XWThreadPool::EnqueueKill( const AddrInfo* addr, const char* const why ) { - logf( XW_LOGINFO, "%s(%d) reason: %s", __func__, socket, why ); + logf( XW_LOGINFO, "%s(%d) reason: %s", __func__, addr->socket(), why ); if ( addr->isTCP() ) { SockInfo si; si.m_type = STYPE_UNKNOWN; From 3be45a67f7553616d9a109d7f00d78599b82cf5c Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 26 Jan 2013 18:56:53 -0800 Subject: [PATCH 086/116] fix erroneous asserts: it's uncommon but ok for a device to get assigne a second connname. (Cause: failure of relay to receive the device's ACK.) --- xwords4/common/comms.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c index 5b8c9ffce..1e96c7edd 100644 --- a/xwords4/common/comms.c +++ b/xwords4/common/comms.c @@ -1386,8 +1386,11 @@ got_connect_cmd( CommsCtxt* comms, XWStreamCtxt* stream, { XP_UCHAR connName[MAX_CONNNAME_LEN+1]; stringFromStreamHere( stream, connName, sizeof(connName) ); - XP_ASSERT( comms->r.connName[0] == '\0' - || 0 == XP_STRCMP( comms->r.connName, connName ) ); + if ( comms->r.connName[0] != '\0' + && 0 != XP_STRCMP( comms->r.connName, connName ) ) { + XP_LOGF( "%s: we're replacing connNames: %s overwritten by %s", + __func__, comms->r.connName, connName ); + } XP_MEMCPY( comms->r.connName, connName, sizeof(comms->r.connName) ); XP_LOGF( "%s: connName: \"%s\"", __func__, connName ); } @@ -1454,8 +1457,11 @@ relayPreProcess( CommsCtxt* comms, XWStreamCtxt* stream, XWHostID* senderID ) { XP_UCHAR connName[MAX_CONNNAME_LEN+1]; stringFromStreamHere( stream, connName, sizeof(connName) ); - XP_ASSERT( comms->r.connName[0] == '\0' - || 0 == XP_STRCMP( comms->r.connName, connName ) ); + if ( comms->r.connName[0] != '\0' + && 0 != XP_STRCMP( comms->r.connName, connName ) ) { + XP_LOGF( "%s: we're replacing connNames: %s overwritten by %s", + __func__, comms->r.connName, connName ); + } XP_MEMCPY( comms->r.connName, connName, sizeof(comms->r.connName) ); XP_LOGF( "%s: connName: \"%s\"", __func__, connName ); From 06bc80fcc91da91f731391745bb91e7c9925bebd Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 26 Jan 2013 20:59:27 -0800 Subject: [PATCH 087/116] let's make 1 the default number of threads --- xwords4/relay/xwrelay.conf_tmplate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/relay/xwrelay.conf_tmplate b/xwords4/relay/xwrelay.conf_tmplate index 2f6bfc00d..27d79ec06 100644 --- a/xwords4/relay/xwrelay.conf_tmplate +++ b/xwords4/relay/xwrelay.conf_tmplate @@ -15,7 +15,7 @@ HEARTBEAT=60 # How many worker threads in the thread pool? Default is five. Let's # keep this at 1 until the race condition is fixed. All interaction # with crefs should be from this one thread, including proxy stuff. -NTHREADS=5 +NTHREADS=1 # How many seconds to wait for device to ack new connName DEVACK=3 From a9b459ea2e40a04e8c5f55996e018b79ef39884f Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 26 Jan 2013 21:16:48 -0800 Subject: [PATCH 088/116] log rather than assert --- xwords4/relay/xwrelay.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index 2d9881669..b5c10e3f5 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -1092,7 +1092,10 @@ handleProxyMsgs( int sock, const AddrInfo* addr, const unsigned char* bufp, } } } - assert( bufp == end ); // don't ship with this!!! + if ( bufp != end ) { + logf( XW_LOGERROR, "%s: buf != end: %p vs %p", __func__, bufp, end ); + } + // assert( bufp == end ); // don't ship with this!!! } } // handleProxyMsgs From cc6990163249cfb2373cd62422191d6c60305f26 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 26 Jan 2013 21:17:46 -0800 Subject: [PATCH 089/116] reset to null in loop in case exit due to exceeding retry count --- xwords4/relay/crefmgr.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xwords4/relay/crefmgr.cpp b/xwords4/relay/crefmgr.cpp index c3441f486..42075d39d 100644 --- a/xwords4/relay/crefmgr.cpp +++ b/xwords4/relay/crefmgr.cpp @@ -413,8 +413,10 @@ CRefMgr::getCookieRef( CookieID cid, bool failOk ) break; } m_cidlock->Relinquish( cinfo, true ); - logf( XW_LOGINFO, "%s: sleeping after failing to get cinfo", __func__ ); + logf( XW_LOGINFO, "%s: (count=%d) sleeping after " + "failing to get cinfo", __func__, count ); usleep(200000); /* 2/10 second */ + cinfo = NULL; } return cinfo; } /* getCookieRef */ From 82dc986c79698fa963a908ba0a90a4b796852818 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 26 Jan 2013 21:49:17 -0800 Subject: [PATCH 090/116] remove bad asserts and unused variable --- xwords4/relay/xwrelay.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index b5c10e3f5..e281cf1da 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -211,6 +211,9 @@ parseRelayID( const unsigned char** const inp, const unsigned char* const end, } *inp = (unsigned char*)endptr; } + if ( !ok ) { + logf( XW_LOGERROR, "%s failed", __func__ ); + } return ok; } @@ -1030,7 +1033,6 @@ handlePutMessage( SafeCref& scr, HostID hid, const AddrInfo* addr, unsigned short len, const unsigned char** bufp, const unsigned char* end ) { - logf( XW_LOGINFO, "%s()", __func__ ); bool success = false; const unsigned char* start = *bufp; HostID src; @@ -1039,9 +1041,9 @@ handlePutMessage( SafeCref& scr, HostID hid, const AddrInfo* addr, // sanity check that cmd and hostids are there if ( getNetByte( bufp, end, &cmd ) && getNetByte( bufp, end, &src ) - && getNetByte( bufp, end, &dest ) ) { - assert( cmd == XWRELAY_MSG_TORELAY_NOCONN ); - assert( hid == dest ); + && getNetByte( bufp, end, &dest ) + && ( cmd == XWRELAY_MSG_TORELAY_NOCONN ) + && ( hid == dest ) ) { scr.PutMsg( src, addr, dest, start, len ); *bufp = start + len; success = true; @@ -1058,8 +1060,6 @@ handleProxyMsgs( int sock, const AddrInfo* addr, const unsigned char* bufp, unsigned short nameCount; int ii; if ( getNetShort( &bufp, end, &nameCount ) ) { - vector out(4); /* space for len and n_msgs */ - assert( out.size() == 4 ); for ( ii = 0; ii < nameCount && bufp < end; ++ii ) { // See NetUtils.java for reply format @@ -1092,7 +1092,7 @@ handleProxyMsgs( int sock, const AddrInfo* addr, const unsigned char* bufp, } } } - if ( bufp != end ) { + if ( end - bufp != 1 ) { logf( XW_LOGERROR, "%s: buf != end: %p vs %p", __func__, bufp, end ); } // assert( bufp == end ); // don't ship with this!!! From 302ac9d55cecaec963fc085602617df81a6c2d55 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 27 Jan 2013 08:30:02 -0800 Subject: [PATCH 091/116] script to list who's attached to the db --- xwords4/relay/scripts/list-psql-users.sh | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100755 xwords4/relay/scripts/list-psql-users.sh diff --git a/xwords4/relay/scripts/list-psql-users.sh b/xwords4/relay/scripts/list-psql-users.sh new file mode 100755 index 000000000..d6f1b8fbd --- /dev/null +++ b/xwords4/relay/scripts/list-psql-users.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +# Show pids of processes holding onto the xwgames DB in some way + +# from http://newstrib.com/main.asp?SectionID=2&SubSectionID=27&ArticleID=26068 + +echo "select pg_class.relname,pg_locks.* from pg_class,pg_locks where pg_class.relfilenode=pg_locks.relation;" | psql xwgames From 4aee75aeab2eb4c65c824b6e7db92ba72eee3ab6 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 27 Jan 2013 21:26:27 -0800 Subject: [PATCH 092/116] ack all packets from clients; log acks. This is for debugging and may not need to stay. --- xwords4/relay/udpack.cpp | 17 +++++++++++------ xwords4/relay/udpack.h | 1 + xwords4/relay/xwrelay.cpp | 14 ++++++++++++++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/xwords4/relay/udpack.cpp b/xwords4/relay/udpack.cpp index 297032c7c..75762b829 100644 --- a/xwords4/relay/udpack.cpp +++ b/xwords4/relay/udpack.cpp @@ -23,13 +23,17 @@ UDPAckTrack* UDPAckTrack::s_self = NULL; +/* static*/ bool +UDPAckTrack::shouldAck( XWRelayReg cmd ) +{ + return ( XWPDEV_ACK != cmd && XWPDEV_ALERT != cmd ); +} + /* static*/ uint32_t UDPAckTrack::nextPacketID( XWRelayReg cmd ) { - uint32_t result; - if ( XWPDEV_ACK == cmd || XWPDEV_ALERT == cmd ) { - result = 0; - } else { + uint32_t result = 0; + if ( shouldAck( cmd ) ) { result = get()->nextPacketIDImpl(); } return result; @@ -115,9 +119,10 @@ UDPAckTrack::threadProc() } string_printf( leaked, ", " ); } - logf( XW_LOGERROR, "these packets leaked: %s", leaked.c_str() ); + logf( XW_LOGERROR, "%s: these packets leaked: %s", __func__, + leaked.c_str() ); } else { - logf( XW_LOGINFO, "no packets leaked" ); + logf( XW_LOGINFO, "%s: no packets leaked", __func__ ); } } return NULL; diff --git a/xwords4/relay/udpack.h b/xwords4/relay/udpack.h index 5df604154..ee94a046b 100644 --- a/xwords4/relay/udpack.h +++ b/xwords4/relay/udpack.h @@ -33,6 +33,7 @@ class UDPAckTrack { public: static uint32_t nextPacketID( XWRelayReg cmd ); static void recordAck( uint32_t packetID ); + static bool shouldAck( XWRelayReg cmd ); private: static UDPAckTrack* get(); diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index e281cf1da..52a8f04e1 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -1293,6 +1293,18 @@ msgToStr( XWRelayReg msg ) } +static void +ackPacketIf( const UDPHeader* header, const AddrInfo* addr ) +{ + if ( UDPAckTrack::shouldAck( header->cmd ) ) { + uint32_t packetID = header->packetID; + logf( XW_LOGINFO, "acking packet %d", packetID ); + packetID = htonl( packetID ); + send_via_udp( addr->socket(), addr->sockaddr(), XWPDEV_ACK, + &packetID, sizeof(packetID), NULL ); + } +} + static void udp_thread_proc( UdpThreadClosure* utc ) { @@ -1302,6 +1314,7 @@ udp_thread_proc( UdpThreadClosure* utc ) UDPHeader header; if ( getHeader( &ptr, end, &header ) ) { logf( XW_LOGINFO, "%s(msg=%s)", __func__, msgToStr( header.cmd ) ); + ackPacketIf( &header, utc->addr() ); switch( header.cmd ) { case XWPDEV_REG: { DevIDType typ = (DevIDType)*ptr++; @@ -1375,6 +1388,7 @@ udp_thread_proc( UdpThreadClosure* utc ) case XWPDEV_ACK: { uint32_t packetID; if ( getNetLong( &ptr, end, &packetID ) ) { + logf( XW_LOGINFO, "ack for packet %d", packetID ); UDPAckTrack::recordAck( packetID ); } break; From d1621c8d4bad556a1b5cba150d2b5ec6fe6a1dbc Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 27 Jan 2013 21:29:36 -0800 Subject: [PATCH 093/116] track and log all packetIDs and acks, including number (but not type) of unacked packets. --- .../org/eehouse/android/xw4/RelayService.java | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java index 1b5a0d7d0..04afdcf0b 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java @@ -33,6 +33,7 @@ import java.net.InetAddress; import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.concurrent.LinkedBlockingQueue; @@ -55,6 +56,9 @@ public class RelayService extends XWService { private static final String ROWID = "ROWID"; private static final String BINBUFFER = "BINBUFFER"; + private static HashSet s_packetsSent = new HashSet(); + private static int s_nextPacketID = 1; + private Thread m_fetchThread = null; private Thread m_UDPReadThread = null; private Thread m_UDPWriteThread = null; @@ -378,6 +382,9 @@ public class RelayService extends XWService { dis.read( msg ); postData( RelayService.this, token, msg ); break; + case XWPDEV_ACK: + noteAck( dis.readInt() ); + break; default: DbgUtils.logf( "RelayService: Unhandled cmd: %d", header.m_cmd ); @@ -503,7 +510,7 @@ public class RelayService extends XWService { { DataOutputStream out = new DataOutputStream( bas ); out.writeByte( XWPDEV_PROTO_VERSION ); - out.writeInt( 0 ); // packetID + out.writeInt( nextPacketID( cmd ) ); // packetID out.writeByte( cmd.ordinal() ); return out; } @@ -711,6 +718,28 @@ public class RelayService extends XWService { } } + private static int nextPacketID( XWRelayReg cmd ) + { + int nextPacketID = 0; + synchronized( s_packetsSent ) { + if ( XWRelayReg.XWPDEV_ACK != cmd ) { + nextPacketID = s_nextPacketID++; + s_packetsSent.add( nextPacketID ); + } + } + DbgUtils.logf( "nextPacketID(%s)=>%d", cmd.toString(), nextPacketID ); + return nextPacketID; + } + + private static void noteAck( int packetID ) + { + synchronized( s_packetsSent ) { + s_packetsSent.remove( packetID ); + DbgUtils.logf( "Got ack for %d; there are %d unacked packets", + packetID, s_packetsSent.size() ); + } + } + private class PacketHeader { public int m_packetID; public XWRelayReg m_cmd; From 1546b15997f375e997ba7859fe927f44d64a9883 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 28 Jan 2013 06:51:39 -0800 Subject: [PATCH 094/116] rowid needs to be autoincrement to be useful as a token with new relay protocol. And you can't make it autoincrement except when creating a table. So add new column set equal to current rowid, then create a new table and copy the old data into it. --- .../src/org/eehouse/android/xw4/DBHelper.java | 57 ++++++++++++++++++- .../src/org/eehouse/android/xw4/DBUtils.java | 51 +++++++++++++++++ .../org/eehouse/android/xw4/GameUtils.java | 3 +- 3 files changed, 108 insertions(+), 3 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java index b13ded1e4..37e3eb299 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java @@ -25,6 +25,9 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.text.TextUtils; +import java.util.ArrayList; +import java.util.Arrays; public class DBHelper extends SQLiteOpenHelper { @@ -34,9 +37,10 @@ public class DBHelper extends SQLiteOpenHelper { public static final String TABLE_NAME_DICTINFO = "dictinfo"; public static final String TABLE_NAME_GROUPS = "groups"; private static final String DB_NAME = "xwdb"; - private static final int DB_VERSION = 16; + private static final int DB_VERSION = 17; public static final String GAME_NAME = "GAME_NAME"; + public static final String VISID = "VISID"; public static final String NUM_MOVES = "NUM_MOVES"; public static final String TURN = "TURN"; public static final String GIFLAGS = "GIFLAGS"; @@ -85,7 +89,9 @@ public class DBHelper extends SQLiteOpenHelper { private Context m_context; private static final String[] s_summaryColsAndTypes = { - GAME_NAME, "TEXT" + "rowid", "INTEGER PRIMARY KEY AUTOINCREMENT" + ,VISID, "INTEGER" + ,GAME_NAME, "TEXT" ,NUM_MOVES, "INTEGER" ,TURN, "INTEGER" ,GIFLAGS, "INTEGER" @@ -200,6 +206,10 @@ public class DBHelper extends SQLiteOpenHelper { createGroupsTable( db ); case 15: moveToCurGames( db ); + case 16: + addSumColumn( db, VISID ); + setColumnsEqual( db, TABLE_NAME_SUM, VISID, "rowid" ); + makeAutoincrement( db, TABLE_NAME_SUM, s_summaryColsAndTypes ); // nothing yet break; default: @@ -280,4 +290,47 @@ public class DBHelper extends SQLiteOpenHelper { } cursor.close(); } + + private void makeAutoincrement( SQLiteDatabase db, String name, String[] data ) + { + db.beginTransaction(); + try { + String query; + String[] columnNames = DBUtils.getColumns( db, name ); + if ( null != columnNames ) { // no data means no need to copy + query = String.format( "ALTER table %s RENAME TO 'temp_%s'", + name, name ); + db.execSQL( query ); + } + createTable( db, name, data ); + + if ( null != columnNames ) { + ArrayList oldCols = + new ArrayList( Arrays.asList( columnNames ) ); + String[] newColNames = DBUtils.getColumns( db, name ); + ArrayList newCols = + new ArrayList( Arrays.asList( newColNames ) ); + oldCols.retainAll( newCols ); + + String cols = TextUtils.join( ",", oldCols ); + query = + String.format( "INSERT INTO %s (%s) SELECT %s from temp_%s", + name, cols, cols, name ); + db.execSQL( query ); + } + db.execSQL( String.format( "DROP table temp_%s", name ) ); + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + private void setColumnsEqual( SQLiteDatabase db, String table, + String dest, String src ) + { + String query = String.format( "UPDATE %s set %s = %s", table, + dest, src ); + db.execSQL( query ); + } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java index 30035bcba..d375a45c6 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java @@ -695,6 +695,7 @@ public class DBUtils { values.put( DBHelper.LASTPLAY_TIME, timestamp ); values.put( DBHelper.GROUPID, XWPrefs.getDefaultNewGameGroup( context ) ); + values.put( DBHelper.VISID, maxVISID( db ) ); long rowid = db.insert( DBHelper.TABLE_NAME_SUM, null, values ); @@ -791,6 +792,28 @@ public class DBUtils { } } + public static int getVisID( Context context, long rowid ) + { + int result = -1; + initDB( context ); + synchronized( s_dbHelper ) { + SQLiteDatabase db = s_dbHelper.getReadableDatabase(); + + String[] columns = { DBHelper.VISID }; + String selection = String.format( ROW_ID_FMT, rowid ); + Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns, + selection, null, null, null, null ); + if ( 1 == cursor.getCount() && cursor.moveToFirst() ) { + result = cursor.getInt( cursor + .getColumnIndex(DBHelper.VISID)); + } + cursor.close(); + db.close(); + } + + return result; + } + // Get either the file name or game name, preferring the latter. public static String getName( Context context, long rowid ) { @@ -1380,6 +1403,15 @@ public class DBUtils { return sdcardDB.exists(); } + public static String[] getColumns( SQLiteDatabase db, String name ) + { + String query = String.format( "SELECT * FROM %s LIMIT 1", name ); + Cursor cursor = db.rawQuery( query, null ); + String[] colNames = cursor.getColumnNames(); + cursor.close(); + return colNames; + } + private static void copyGameDB( Context context, boolean toSDCard ) { String name = DBHelper.getDBName(); @@ -1440,6 +1472,25 @@ public class DBUtils { } } } + + private static int maxVISID( SQLiteDatabase db ) + { + int result = 1; + String query = String.format( "SELECT max(%s) FROM %s", DBHelper.VISID, + DBHelper.TABLE_NAME_SUM ); + Cursor cursor = null; + try { + cursor = db.rawQuery( query, null ); + if ( 1 == cursor.getCount() && cursor.moveToFirst() ) { + result = 1 + cursor.getInt( 0 ); + } + } finally { + if ( null != cursor ) { + cursor.close(); + } + } + return result; + } private static void notifyListeners( long rowid, boolean countChanged ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index 6605d5d3d..3a55b792f 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -227,7 +227,8 @@ public class GameUtils { { String result = DBUtils.getName( context, rowid ); if ( null == result || 0 == result.length() ) { - result = context.getString( R.string.gamef, rowid ); + int visID = DBUtils.getVisID( context, rowid ); + result = context.getString( R.string.gamef, visID ); } return result; } From 3e723c72c5544791c5778d063df530c622edf32b Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 28 Jan 2013 06:52:32 -0800 Subject: [PATCH 095/116] call comms_stop() before closing (removing from memory) a game. --- xwords4/common/game.c | 1 + 1 file changed, 1 insertion(+) diff --git a/xwords4/common/game.c b/xwords4/common/game.c index 178fcc0fb..d4604d4c9 100644 --- a/xwords4/common/game.c +++ b/xwords4/common/game.c @@ -340,6 +340,7 @@ game_dispose( XWGame* game ) #ifndef XWFEATURE_STANDALONE_ONLY if ( !!game->comms ) { + comms_stop( game->comms ); comms_destroy( game->comms ); game->comms = NULL; } From d78a4979da5cbf35da2acfa8234dfa04285ca1ca Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 28 Jan 2013 07:01:49 -0800 Subject: [PATCH 096/116] HACK: if gi's notion of gameID is out of sync, force it in (logging the fact). I'm not sure why but this is required sometimes on Android. --- xwords4/common/comms.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c index 1e96c7edd..b9419fed2 100644 --- a/xwords4/common/comms.c +++ b/xwords4/common/comms.c @@ -1191,9 +1191,27 @@ gameID( const CommsCtxt* comms ) if ( 0 == gameID ) { gameID = comms->util->gameInfo->gameID; } - XP_ASSERT( 0 == comms->connID - || (comms->connID & 0xFFFF) - == (comms->util->gameInfo->gameID & 0xFFFF) ); + // XP_ASSERT( 0 != gameID ); + if ( 0 == gameID ) { + XP_LOGF( "%s: gameID STILL 0", __func__ ); + } else if ( 0 == comms->util->gameInfo->gameID ) { + XP_LOGF( "%s: setting gi's gameID to 0X%lX", __func__, gameID ); + comms->util->gameInfo->gameID = gameID; + } + + /* this next is failing on android b/c comms->util->gameInfo->gameID still 0 */ +#ifdef DEBUG + /* if ( (0 != comms->connID) */ + /* && ((comms->connID & 0xFFFF) */ + /* != (comms->util->gameInfo->gameID & 0xFFFF)) ) { */ + /* XP_LOGF("%s: connID: 0X%lX vs gameID: 0X%lX", __func__, comms->connID, */ + /* comms->util->gameInfo->gameID ); */ + /* XP_ASSERT(0); */ + /* } */ +#endif + /* XP_ASSERT( 0 == comms->connID */ + /* || (comms->connID & 0xFFFF) */ + /* == (comms->util->gameInfo->gameID & 0xFFFF) ); */ /* Most of the time these will be the same, but early in a game they won't be. Would be nice not to have to use gameID. */ return gameID; From 72e100ccac9d35737b3c04972254f83953486184 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 28 Jan 2013 07:02:33 -0800 Subject: [PATCH 097/116] use autoincrement primary key, same as on Android --- xwords4/linux/gamesdb.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c index d6255d48d..d29e16b21 100644 --- a/xwords4/linux/gamesdb.c +++ b/xwords4/linux/gamesdb.c @@ -34,7 +34,8 @@ openGamesDB( const char* dbName ) const char* createGamesStr = "CREATE TABLE games ( " - "game BLOB" + "rowid INTEGER PRIMARY KEY AUTOINCREMENT" + ",game BLOB" ",room VARCHAR(32)" ",ended INT(1)" ",turn INT(2)" From 9ee16adc3a3c25c11de39c0dcc7b9535c0c00e02 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 29 Jan 2013 07:38:07 -0800 Subject: [PATCH 098/116] add support for udp-based delete and anonymous (relay-provided) regIDs --- xwords4/relay/dbmgr.cpp | 55 +++++++++++++++++++++++++++++++++++++-- xwords4/relay/dbmgr.h | 3 +++ xwords4/relay/xwrelay.cpp | 55 +++++++++++++++++++++++++++++---------- xwords4/relay/xwrelay.h | 11 +++++++- 4 files changed, 107 insertions(+), 17 deletions(-) diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index 7ed59e74b..75246fef1 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -43,6 +43,7 @@ static DBMgr* s_instance = NULL; #define DELIM "\1" +#define MAX_NUM_PLAYERS 4 static void formatParams( char* paramValues[], int nParams, const char* fmt, char* buf, int bufLen, ... ); @@ -149,6 +150,54 @@ DBMgr::FindGame( const char* connName, char* cookieBuf, int bufLen, return cid; } /* FindGame */ +bool +DBMgr::FindPlayer( DevIDRelay relayID, AddrInfo::ClientToken token, + string& connName, HostID* hidp, unsigned short* seed ) +{ + int nSuccesses = 0; + + const char* fmt = + "SELECT connName FROM %s WHERE %d = ANY(devids) AND %d = ANY(tokens)"; + string query; + string_printf( query, fmt, GAMES_TABLE, relayID, token ); + + PGresult* result = PQexec( getThreadConn(), query.c_str() ); + int nTuples = PQntuples( result ); + vector names(nTuples); + for ( int ii = 0; ii < nTuples; ++ii ) { + string name( PQgetvalue( result, ii, 0 ) ); + names.push_back( name ); + } + PQclear( result ); + + for ( vector::const_iterator iter = names.begin(); + iter != names.end(); ++iter ) { + const char* name = iter->c_str(); + for ( HostID hid = 1; hid <= MAX_NUM_PLAYERS; ++hid ) { + fmt = "SELECT seeds[%d] FROM %s WHERE connname = '%s' " + "AND devids[%d] = %d AND tokens[%d] = %d"; + string query; + string_printf( query, fmt, hid, GAMES_TABLE, name, + hid, relayID, hid, token ); + result = PQexec( getThreadConn(), query.c_str() ); + int nTuples2 = PQntuples( result ); + for ( int jj = 0; jj < nTuples2; ++jj ) { + connName = name; + *hidp = hid; + *seed = atoi( PQgetvalue( result, 0, 0 ) ); + ++nSuccesses; + } + PQclear( result ); + } + } + + if ( 1 < nSuccesses ) { + logf( XW_LOGERROR, "%s found %d matches!!!", __func__, nSuccesses ); + } + + return nSuccesses >= 1; +} // FindPlayer + bool DBMgr::SeenSeed( const char* cookie, unsigned short seed, int langCode, int nPlayersT, bool wantsPublic, @@ -295,7 +344,8 @@ DBMgr::RegisterDevice( const DevID* host ) NULL, NULL, 0 ); success = PGRES_COMMAND_OK == PQresultStatus(result); if ( !success ) { - logf( XW_LOGERROR, "PQexec=>%s;%s", PQresStatus(PQresultStatus(result)), + logf( XW_LOGERROR, "PQexec=>%s;%s", + PQresStatus(PQresultStatus(result)), PQresultErrorMessage(result) ); } PQclear( result ); @@ -315,7 +365,8 @@ DBMgr::updateDevice( DevIDRelay relayID, bool check ) } if ( exists ) { - const char* fmt = "UPDATE " DEVICES_TABLE " SET mtime='now' WHERE id = %d"; + const char* fmt = + "UPDATE " DEVICES_TABLE " SET mtime='now' WHERE id = %d"; string query; string_printf( query, fmt, relayID ); execSql( query ); diff --git a/xwords4/relay/dbmgr.h b/xwords4/relay/dbmgr.h index ec716422b..6f3c0d2ef 100644 --- a/xwords4/relay/dbmgr.h +++ b/xwords4/relay/dbmgr.h @@ -47,6 +47,9 @@ class DBMgr { void AddNew( const char* cookie, const char* connName, CookieID cid, int langCode, int nPlayersT, bool isPublic ); + bool FindPlayer( DevIDRelay relayID, AddrInfo::ClientToken, + string& connName, HostID* hid, unsigned short* seed ); + CookieID FindGame( const char* connName, char* cookieBuf, int bufLen, int* langP, int* nPlayersTP, int* nPlayersHP, bool* isDead ); diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index 52a8f04e1..35615fa5c 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -271,6 +271,24 @@ getNetString( const unsigned char** bufpp, const unsigned char* end, string& out return success; } +static bool +getRelayDevID( const unsigned char** bufpp, const unsigned char* end, + DevID& devID ) +{ + bool success = false; + unsigned short idLen; + if ( getNetShort( bufpp, end, &idLen ) ) { + if ( end - *bufpp < idLen/* && ID_TYPE_ANON != typ*/ ) { + logf( XW_LOGERROR, "full devID not received" ); + } else { + devID.m_devIDString.append( (const char*)*bufpp, idLen ); + *bufpp += idLen; + success = true; + } + } + return success; +} + static bool getHeader( const unsigned char** bufpp, const unsigned char* end, UDPHeader* header ) @@ -1284,6 +1302,7 @@ msgToStr( XWRelayReg msg ) CASE_STR(XWPDEV_BADREG); CASE_STR(XWPDEV_ALERT); // should not receive this.... CASE_STR(XWPDEV_ACK); + CASE_STR(XWPDEV_DELGAME); default: str = ""; break; @@ -1318,20 +1337,12 @@ udp_thread_proc( UdpThreadClosure* utc ) switch( header.cmd ) { case XWPDEV_REG: { DevIDType typ = (DevIDType)*ptr++; - unsigned short idLen; - if ( !getNetShort( &ptr, end, &idLen ) ) { - break; - } - if ( end - ptr > idLen ) { - logf( XW_LOGERROR, "full devID not received" ); - break; - } DevID devID( typ ); - devID.m_devIDString.append( (const char*)ptr, idLen ); - ptr += idLen; - registerDevice( &devID, utc->saddr() ); - } + if ( getRelayDevID( &ptr, end, devID ) ) { + registerDevice( &devID, utc->saddr() ); + } break; + } case XWPDEV_MSG: { AddrInfo::ClientToken clientToken; memcpy( &clientToken, ptr, sizeof(clientToken) ); @@ -1393,14 +1404,30 @@ udp_thread_proc( UdpThreadClosure* utc ) } break; } + case XWPDEV_DELGAME: { + DevID devID( ID_TYPE_RELAY ); + if ( !getRelayDevID( &ptr, end, devID ) ) { + break; + } + AddrInfo::ClientToken clientToken; + if ( getNetLong( &ptr, end, &clientToken ) && 0 != clientToken ) { + unsigned short seed; + HostID hid; + string connName; + if ( DBMgr::Get()->FindPlayer( devID.asRelayID(), clientToken, + connName, &hid, &seed ) ) { + SafeCref scr( connName.c_str() ); + scr.DeviceGone( hid, seed ); + } + } + break; + } default: logf( XW_LOGERROR, "%s: unexpected msg %d", __func__, header.cmd ); } } } -// This will need to be done in a thread before there can be simulaneous -// connections. static void handle_udp_packet( int udpsock ) { diff --git a/xwords4/relay/xwrelay.h b/xwords4/relay/xwrelay.h index ff6af81d9..f59b94ecd 100644 --- a/xwords4/relay/xwrelay.h +++ b/xwords4/relay/xwrelay.h @@ -27,7 +27,12 @@ /* Set if device is acting a server; cleared if as client */ #define FLAGS_SERVER_BIT 0x01 -/* message types for the udp-based per-device (not per-game) protocol */ +/* message types for the udp-based per-device (not per-game) protocol + * + * A number of these rely on a "clientToken", which is a 32-bit value the + * client provides and that it guarantees uniquely identifies a game on the + * device. A database rowid works great as long as they aren't reused. + */ #define XWPDEV_PROTO_VERSION 0 #ifndef CANT_DO_TYPEDEF typedef @@ -86,6 +91,9 @@ enum { XWPDEV_NONE /* 0 is an illegal value */ msgID: 4 */ + ,XWPDEV_DELGAME /* dev->relay: game's been deleted. format: + header, relayid: 4, clientToken: 4 */ + } #ifndef CANT_DO_TYPEDEF XWRelayReg @@ -176,6 +184,7 @@ typedef enum { ,ID_TYPE_LINUX ,ID_TYPE_ANDROID_GCM ,ID_TYPE_ANDROID_OTHER + ,ID_TYPE_ANON /* please assign me one based on nothing */ ,ID_TYPE_NTYPES } DevIDType; From c75ecef2406c09cad8a864983c5608b7d8851213 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 29 Jan 2013 07:42:10 -0800 Subject: [PATCH 099/116] test deletion of games and generation of anonymous relayIDs --- xwords4/linux/cursesmain.c | 12 ++++++--- xwords4/linux/gamesdb.c | 7 ++++-- xwords4/linux/gamesdb.h | 2 +- xwords4/linux/gtkboard.c | 11 +++++--- xwords4/linux/gtkboard.h | 2 +- xwords4/linux/gtkmain.c | 42 +++++++++++++++++++++---------- xwords4/linux/linuxmain.c | 47 +++++++++++++++++++++-------------- xwords4/linux/linuxmain.h | 1 + xwords4/linux/linuxutl.c | 14 ++--------- xwords4/linux/main.h | 4 ++- xwords4/linux/relaycon.c | 51 ++++++++++++++++++++++++++++++++------ xwords4/linux/relaycon.h | 2 ++ 12 files changed, 132 insertions(+), 63 deletions(-) diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index 30225616f..3e6937ca3 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -1769,14 +1769,18 @@ static void cursesDevIDChanged( void* closure, const XP_UCHAR* devID ) { CursesAppGlobals* globals = (CursesAppGlobals*)closure; - sqlite3* pDb = globals->cGlobals.pDb; + CommonGlobals* cGlobals = &globals->cGlobals; + sqlite3* pDb = cGlobals->pDb; if ( !!devID ) { XP_LOGF( "%s(devID=%s)", __func__, devID ); db_store( pDb, KEY_RDEVID, devID ); } else { XP_LOGF( "%s: bad relayid", __func__ ); db_remove( pDb, KEY_RDEVID ); - sendRelayReg( globals->cGlobals.params, pDb ); + + DevIDType typ; + const XP_UCHAR* devID = linux_getDevID( cGlobals->params, &typ ); + relaycon_reg( cGlobals->params, devID, typ ); } } @@ -1945,7 +1949,9 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) relaycon_init( params, &procs, &g_globals, params->connInfo.relay.relayName, params->connInfo.relay.defaultSendPort ); - sendRelayReg( params, g_globals.cGlobals.pDb ); + DevIDType typ; + const XP_UCHAR* devID = linux_getDevID( params, &typ ); + relaycon_reg( params, devID, typ ); GSList* games = listGames( g_globals.cGlobals.pDb ); if ( !!games ) { diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c index d29e16b21..8107e7c0d 100644 --- a/xwords4/linux/gamesdb.c +++ b/xwords4/linux/gamesdb.c @@ -247,9 +247,10 @@ db_store( sqlite3* pDb, const gchar* key, const gchar* value ) sqlite3_finalize( ppStmt ); } -void +XP_Bool db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen ) { + XP_Bool found; char query[256]; snprintf( query, sizeof(query), "SELECT value from pairs where key = '%s'", key ); @@ -257,12 +258,14 @@ db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen ) int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL ); XP_ASSERT( SQLITE_OK == result ); result = sqlite3_step( ppStmt ); - if ( SQLITE_ROW == result ) { + found = SQLITE_ROW == result; + if ( found ) { getColumnText( ppStmt, 0, buf, buflen ); } else { buf[0] = '\0'; } sqlite3_finalize( ppStmt ); + return found; } void diff --git a/xwords4/linux/gamesdb.h b/xwords4/linux/gamesdb.h index 3272c3c20..2d34f96e3 100644 --- a/xwords4/linux/gamesdb.h +++ b/xwords4/linux/gamesdb.h @@ -52,7 +52,7 @@ void deleteGame( sqlite3* pDb, sqlite3_int64 rowid ); #define KEY_RDEVID "RDEVID" void db_store( sqlite3* dbp, const gchar* key, const gchar* value ); -void db_fetch( sqlite3* dbp, const gchar* key, gchar* buf, gint buflen ); +XP_Bool db_fetch( sqlite3* dbp, const gchar* key, gchar* buf, gint buflen ); void db_remove( sqlite3* dbp, const gchar* key ); #endif diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index baa6fa1c6..65a38c117 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -310,9 +310,11 @@ relay_status_gtk( void* closure, CommsRelayState state ) { XP_LOGF( "%s got status: %s", __func__, CommsRelayState2Str(state) ); GtkGameGlobals* globals = (GtkGameGlobals*)closure; - globals->cGlobals.state = state; - globals->stateChar = 'A' + COMMS_RELAYSTATE_ALLCONNECTED - state; - draw_gtk_status( globals->draw, globals->stateChar ); + if ( !!globals->draw ) { + globals->cGlobals.state = state; + globals->stateChar = 'A' + COMMS_RELAYSTATE_ALLCONNECTED - state; + draw_gtk_status( globals->draw, globals->stateChar ); + } } static void @@ -2570,9 +2572,10 @@ freeGlobals( GtkGameGlobals* globals ) XP_Bool loadGameNoDraw( GtkGameGlobals* globals, LaunchParams* params, - sqlite3* pDb, sqlite3_int64 rowid ) + sqlite3_int64 rowid ) { LOG_FUNC(); + sqlite3* pDb = params->pDb; initGlobalsNoDraw( globals, params ); TransportProcs procs; diff --git a/xwords4/linux/gtkboard.h b/xwords4/linux/gtkboard.h index 29436c4f5..5bffc2481 100644 --- a/xwords4/linux/gtkboard.h +++ b/xwords4/linux/gtkboard.h @@ -170,7 +170,7 @@ void initGlobals( GtkGameGlobals* globals, LaunchParams* params ); void freeGlobals( GtkGameGlobals* globals ); XP_Bool makeNewGame( GtkGameGlobals* globals ); XP_Bool loadGameNoDraw( GtkGameGlobals* globals, LaunchParams* params, - sqlite3* pDb, sqlite3_int64 rowid ); + sqlite3_int64 rowid ); void destroy_board_window( GtkWidget* widget, GtkGameGlobals* globals ); #endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index 1f381f394..cc3065f3b 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -208,7 +208,7 @@ handle_newgame_button( GtkWidget* XP_UNUSED(widget), void* closure ) freeGlobals( globals ); } else { GtkWidget* gameWindow = globals->window; - globals->cGlobals.pDb = apg->pDb; + globals->cGlobals.pDb = apg->params->pDb; globals->cGlobals.selRow = -1; recordOpened( apg, globals ); gtk_widget_show( gameWindow ); @@ -224,7 +224,7 @@ handle_open_button( GtkWidget* XP_UNUSED(widget), void* closure ) apg->params->needsNewGame = XP_FALSE; GtkGameGlobals* globals = malloc( sizeof(*globals) ); initGlobals( globals, apg->params ); - globals->cGlobals.pDb = apg->pDb; + globals->cGlobals.pDb = apg->params->pDb; globals->cGlobals.selRow = selRow; recordOpened( apg, globals ); gtk_widget_show( globals->window ); @@ -235,11 +235,20 @@ static void handle_delete_button( GtkWidget* XP_UNUSED(widget), void* closure ) { GtkAppGlobals* apg = (GtkAppGlobals*)closure; + LaunchParams* params = apg->params; guint len = apg->selRows->len; for ( guint ii = 0; ii < len; ++ii ) { sqlite3_int64 rowid = g_array_index( apg->selRows, sqlite3_int64, ii ); removeRow( apg, rowid ); - deleteGame( apg->pDb, rowid ); + deleteGame( params->pDb, rowid ); + + XP_UCHAR devIDBuf[64] = {0}; + db_fetch( params->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); + if ( '\0' != devIDBuf[0] ) { + relaycon_deleted( params, devIDBuf, rowid ); + } else { + XP_LOGF( "%s: not calling relaycon_deleted: no relayID", __func__ ); + } } apg->selRows = g_array_set_size( apg->selRows, 0 ); updateButtons( apg ); @@ -301,7 +310,7 @@ makeGamesWindow( GtkAppGlobals* apg ) gtk_widget_show( list ); - GSList* games = listGames( apg->pDb ); + GSList* games = listGames( apg->params->pDb ); for ( GSList* iter = games; !!iter; iter = iter->next ) { sqlite3_int64* rowid = (sqlite3_int64*)iter->data; onNewData( apg, *rowid, XP_TRUE ); @@ -346,7 +355,7 @@ static void onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid, XP_Bool isNew ) { GameInfo gib; - if ( getGameInfo( apg->pDb, rowid, &gib ) ) { + if ( getGameInfo( apg->params->pDb, rowid, &gib ) ) { add_to_list( apg->listWidget, rowid, isNew, &gib ); } } @@ -400,7 +409,7 @@ gtkGotBuf( void* closure, const XP_U8* buf, XP_U16 len ) gameGotBuf( &globals->cGlobals, XP_TRUE, buf, len ); } else { GtkGameGlobals tmpGlobals; - if ( loadGameNoDraw( &tmpGlobals, apg->params, apg->pDb, gameToken ) ) { + if ( loadGameNoDraw( &tmpGlobals, apg->params, gameToken ) ) { gameGotBuf( &tmpGlobals.cGlobals, XP_FALSE, buf, len ); saveGame( &tmpGlobals.cGlobals ); } @@ -413,7 +422,7 @@ requestMsgs( gpointer data ) { GtkAppGlobals* apg = (GtkAppGlobals*)data; XP_UCHAR devIDBuf[64] = {0}; - db_fetch( apg->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); + db_fetch( apg->params->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); if ( '\0' != devIDBuf[0] ) { relaycon_requestMsgs( apg->params, devIDBuf ); } else { @@ -434,13 +443,17 @@ static void gtkDevIDChanged( void* closure, const XP_UCHAR* devID ) { GtkAppGlobals* apg = (GtkAppGlobals*)closure; + LaunchParams* params = apg->params; if ( !!devID ) { XP_LOGF( "%s(devID=%s)", __func__, devID ); - db_store( apg->pDb, KEY_RDEVID, devID ); + db_store( params->pDb, KEY_RDEVID, devID ); } else { XP_LOGF( "%s: bad relayid", __func__ ); - db_remove( apg->pDb, KEY_RDEVID ); - sendRelayReg( apg->params, apg->pDb ); + db_remove( params->pDb, KEY_RDEVID ); + + DevIDType typ; + const XP_UCHAR* devID = linux_getDevID( params, &typ ); + relaycon_reg( params, devID, typ ); } } @@ -494,7 +507,7 @@ gtkmain( LaunchParams* params ) apg.selRows = g_array_new( FALSE, FALSE, sizeof( sqlite3_int64 ) ); apg.params = params; - apg.pDb = openGamesDB( params->dbName ); + params->pDb = openGamesDB( params->dbName ); RelayConnProcs procs = { .msgReceived = gtkGotBuf, @@ -507,12 +520,15 @@ gtkmain( LaunchParams* params ) relaycon_init( params, &procs, &apg, params->connInfo.relay.relayName, params->connInfo.relay.defaultSendPort ); - sendRelayReg( params, apg.pDb ); + + DevIDType typ; + const XP_UCHAR* devID = linux_getDevID( params, &typ ); + relaycon_reg( params, devID, typ ); apg.window = makeGamesWindow( &apg ); gtk_main(); - closeGamesDB( apg.pDb ); + closeGamesDB( params->pDb ); relaycon_cleanup( params ); return 0; diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 8d5a28b99..ce9ff07c6 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -580,7 +580,8 @@ setOneSecondTimer( CommonGlobals* cGlobals ) #endif typedef enum { - CMD_SKIP_GAMEOVER + CMD_HELP + ,CMD_SKIP_GAMEOVER ,CMD_SHOW_OTHERSCORES ,CMD_HOSTIP ,CMD_DICT @@ -595,6 +596,7 @@ typedef enum { #ifdef XWFEATURE_DEVID ,CMD_DEVID ,CMD_RDEVID + ,CMD_NOANONDEVID #endif ,CMD_GAMESEED ,CMD_GAMEFILE @@ -678,7 +680,8 @@ typedef struct _CmdInfoRec { } CmdInfoRec; static CmdInfoRec CmdInfoRecs[] = { - { CMD_SKIP_GAMEOVER, false, "skip-final", "skip final scores display" } + { CMD_HELP, false, "help", "print usage" } + ,{ CMD_SKIP_GAMEOVER, false, "skip-final", "skip final scores display" } ,{ CMD_SHOW_OTHERSCORES, false, "show-other", "show robot/remote scores" } ,{ CMD_HOSTIP, true, "hostip", "remote host ip address (for direct connect)" } ,{ CMD_DICT, true, "game-dict", "dictionary name for game" } @@ -693,6 +696,8 @@ static CmdInfoRec CmdInfoRecs[] = { #ifdef XWFEATURE_DEVID ,{ CMD_DEVID, true, "devid", "device ID (for testing GCM stuff)" } ,{ CMD_RDEVID, true, "rdevid", "relay's converted device ID (for testing GCM stuff)" } + ,{CMD_NOANONDEVID, false, "no-anon-devid", + "override default of using anonymous devid registration when no id provided" } #endif ,{ CMD_GAMESEED, true, "game-seed", "game seed (for relay play)" } ,{ CMD_GAMEFILE, true, "file", "file to save to/read from" } @@ -851,24 +856,28 @@ linShiftFocus( CommonGlobals* cGlobals, XP_Key key, const BoardObjectType* order } /* linShiftFocus */ #endif -void -sendRelayReg( LaunchParams* params, sqlite3* pDb ) +const XP_UCHAR* +linux_getDevID( LaunchParams* params, DevIDType* typ ) { - XP_UCHAR devIDBuf[64] = {0}; - XP_UCHAR* devID; - DevIDType typ = ID_TYPE_RELAY; + const XP_UCHAR* result = NULL; + + /* commandline takes precedence over stored values */ + if ( !!params->rDevID ) { - devID = params->rDevID; - } else { - db_fetch( pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); - if ( '\0' != devIDBuf[0] ) { - devID = devIDBuf; - } else { - devID = params->devID; - typ = ID_TYPE_LINUX; - } + result = params->rDevID; + *typ = ID_TYPE_RELAY; + } else if ( !!params->devID ) { + result = params->devID; + *typ = ID_TYPE_LINUX; + } else if ( db_fetch( params->pDb, KEY_RDEVID, params->devIDStore, + sizeof(params->devIDStore) ) ) { + result = params->devIDStore; + *typ = ID_TYPE_RELAY; + } else if ( !params->noAnonDevid ) { + *typ = ID_TYPE_ANON; + result = ""; } - relaycon_reg( params, devID, typ ); + return result; } #ifdef XWFEATURE_RELAY @@ -1843,7 +1852,7 @@ main( int argc, char** argv ) short index; opt = getopt_long_only( argc, argv, "", longopts, NULL ); switch ( opt ) { - case '?': + case CMD_HELP: usage(argv[0], NULL); break; case CMD_SKIP_GAMEOVER: @@ -1906,6 +1915,8 @@ main( int argc, char** argv ) case CMD_RDEVID: mainParams.rDevID = optarg; break; + case CMD_NOANONDEVID: + mainParams.noAnonDevid = true; #endif case CMD_GAMESEED: mainParams.gameSeed = atoi(optarg); diff --git a/xwords4/linux/linuxmain.h b/xwords4/linux/linuxmain.h index 88a9681c4..7429b2fe2 100644 --- a/xwords4/linux/linuxmain.h +++ b/xwords4/linux/linuxmain.h @@ -106,6 +106,7 @@ void gameGotBuf( CommonGlobals* globals, XP_Bool haveDraw, const XP_U8* buf, XP_U16 len ); gboolean app_socket_proc( GIOChannel* source, GIOCondition condition, gpointer data ); +const XP_UCHAR* linux_getDevID( LaunchParams* params, DevIDType* typ ); /* void initParams( LaunchParams* params ); */ /* void freeParams( LaunchParams* params ); */ diff --git a/xwords4/linux/linuxutl.c b/xwords4/linux/linuxutl.c index 4cfba5a22..786ba8eb6 100644 --- a/xwords4/linux/linuxutl.c +++ b/xwords4/linux/linuxutl.c @@ -32,6 +32,7 @@ #include "linuxutl.h" #include "main.h" #include "linuxdict.h" +#include "linuxmain.h" #include "LocalizedStrIncludes.h" #ifdef DEBUG @@ -349,19 +350,8 @@ linux_util_getUserString( XW_UtilCtxt* XP_UNUSED(uc), XP_U16 code ) static const XP_UCHAR* linux_util_getDevID( XW_UtilCtxt* uc, DevIDType* typ ) { - XP_UCHAR* result; CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; - if ( !!cGlobals->params->rDevID ) { - *typ = ID_TYPE_RELAY; - result = cGlobals->params->rDevID; - } else if ( !!cGlobals->params->devID ) { - *typ = ID_TYPE_LINUX; - result = cGlobals->params->devID; - } else { - *typ = ID_TYPE_NONE; - result = NULL; - } - return result; + return linux_getDevID( cGlobals->params, typ ); } static void diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index d3b22c4e1..b28f26b36 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -55,6 +55,7 @@ typedef struct LaunchParams { GSList* dictDirs; char* fileName; char* dbName; + sqlite3* pDb; /* null unless opened */ XP_U16 saveFailPct; const XP_UCHAR* playerDictNames[MAX_NUM_PLAYERS]; #ifdef USE_SQLITE @@ -68,6 +69,8 @@ typedef struct LaunchParams { #ifdef XWFEATURE_DEVID char* devID; char* rDevID; + XP_Bool noAnonDevid; + XP_UCHAR devIDStore[16]; #endif VTableMgr* vtMgr; XP_U16 nLocalPlayers; @@ -231,7 +234,6 @@ typedef struct _SourceData { } SourceData; typedef struct _GtkAppGlobals { - sqlite3* pDb; GArray* selRows; LaunchParams* params; GSList* globalsList; diff --git a/xwords4/linux/relaycon.c b/xwords4/linux/relaycon.c index cc672aa69..4c3aaf753 100644 --- a/xwords4/linux/relaycon.c +++ b/xwords4/linux/relaycon.c @@ -43,8 +43,11 @@ static ssize_t sendIt( RelayConStorage* storage, const XP_U8* msgbuf, XP_U16 len static size_t addStrWithLength( XP_U8* buf, XP_U8* end, const XP_UCHAR* str ); static void getNetString( const XP_U8** ptr, XP_U16 len, XP_UCHAR* buf ); static XP_U16 getNetShort( const XP_U8** ptr ); +static XP_U32 getNetLong( const XP_U8** ptr ); static int writeHeader( XP_U8* dest, XWRelayReg cmd ); static bool readHeader( const XP_U8** buf, MsgHeader* header ); +static size_t writeDevID( XP_U8* buf, size_t len, const XP_UCHAR* str ); + void relaycon_init( LaunchParams* params, const RelayConnProcs* procs, @@ -73,15 +76,10 @@ relaycon_reg( LaunchParams* params, const XP_UCHAR* devID, DevIDType typ ) int indx = 0; RelayConStorage* storage = getStorage( params ); - XP_ASSERT( !!devID ); - XP_U16 idLen = XP_STRLEN( devID ); - XP_U16 lenNBO = XP_HTONS( idLen ); + XP_ASSERT( !!devID || typ == ID_TYPE_ANON ); indx += writeHeader( tmpbuf, XWPDEV_REG ); tmpbuf[indx++] = typ; - XP_MEMCPY( &tmpbuf[indx], &lenNBO, sizeof(lenNBO) ); - indx += sizeof(lenNBO); - XP_MEMCPY( &tmpbuf[indx], devID, idLen ); - indx += idLen; + indx += writeDevID( &tmpbuf[indx], sizeof(tmpbuf) - indx, devID ); sendIt( storage, tmpbuf, indx ); } @@ -154,6 +152,23 @@ relaycon_requestMsgs( LaunchParams* params, const XP_UCHAR* devID ) sendIt( storage, tmpbuf, indx ); } +void +relaycon_deleted( LaunchParams* params, const XP_UCHAR* devID, + XP_U32 gameToken ) +{ + LOG_FUNC(); + RelayConStorage* storage = getStorage( params ); + XP_U8 tmpbuf[128]; + int indx = 0; + indx += writeHeader( tmpbuf, XWPDEV_DELGAME ); + indx += writeDevID( &tmpbuf[indx], sizeof(tmpbuf) - indx, devID ); + gameToken = htonl( gameToken ); + memcpy( &tmpbuf[indx], &gameToken, sizeof(gameToken) ); + indx += sizeof( gameToken ); + + sendIt( storage, tmpbuf, indx ); +} + static void sendAckIf( RelayConStorage* storage, const MsgHeader* header ) { @@ -214,6 +229,11 @@ relaycon_receive( void* closure, int socket ) (*storage->procs.msgErrorMsg)( storage->procsClosure, buf ); break; } + case XWPDEV_ACK: { + XP_U32 packetID = getNetLong( &ptr ); + XP_LOGF( "got ack for packetID %ld", packetID ); + break; + } default: XP_LOGF( "%s: Unexpected cmd %d", __func__, header.cmd ); XP_ASSERT( 0 ); @@ -273,7 +293,7 @@ sendIt( RelayConStorage* storage, const XP_U8* msgbuf, XP_U16 len ) static size_t addStrWithLength( XP_U8* buf, XP_U8* end, const XP_UCHAR* str ) { - XP_U16 len = XP_STRLEN( str ); + XP_U16 len = !!str? XP_STRLEN( str ) : 0; if ( buf + len + sizeof(len) <= end ) { XP_U16 lenNBO = htons( len ); XP_MEMCPY( buf, &lenNBO, sizeof(lenNBO) ); @@ -283,6 +303,12 @@ addStrWithLength( XP_U8* buf, XP_U8* end, const XP_UCHAR* str ) return len + sizeof(len); } +static size_t +writeDevID( XP_U8* buf, size_t len, const XP_UCHAR* str ) +{ + return addStrWithLength( buf, buf + len, str ); +} + static XP_U16 getNetShort( const XP_U8** ptr ) { @@ -292,6 +318,15 @@ getNetShort( const XP_U8** ptr ) return ntohs( result ); } +static XP_U32 +getNetLong( const XP_U8** ptr ) +{ + XP_U32 result; + memcpy( &result, *ptr, sizeof(result) ); + *ptr += sizeof(result); + return ntohl( result ); +} + static void getNetString( const XP_U8** ptr, XP_U16 len, XP_UCHAR* buf ) { diff --git a/xwords4/linux/relaycon.h b/xwords4/linux/relaycon.h index f2498c519..8b21f6df5 100644 --- a/xwords4/linux/relaycon.h +++ b/xwords4/linux/relaycon.h @@ -40,6 +40,8 @@ XP_S16 relaycon_send( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, XP_S16 relaycon_sendnoconn( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, const XP_UCHAR* relayID, XP_U32 gameToken ); void relaycon_requestMsgs( LaunchParams* params, const XP_UCHAR* devID ); +void relaycon_deleted( LaunchParams* params, const XP_UCHAR* devID, + XP_U32 gameToken ); void relaycon_cleanup( LaunchParams* params ); #endif From f3a063698ce398ad2cdae91b0bf1a081713bb79a Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 29 Jan 2013 08:01:14 -0800 Subject: [PATCH 100/116] set initial rowID based on clock to avoid duplicates after an uninstall/reinstall. --- .../src/org/eehouse/android/xw4/DBHelper.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java index 37e3eb299..a1ce7b2fc 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java @@ -169,6 +169,7 @@ public class DBHelper extends SQLiteOpenHelper { createTable( db, TABLE_NAME_OBITS, s_obitsColsAndTypes ); createTable( db, TABLE_NAME_DICTINFO, s_dictInfoColsAndTypes ); createTable( db, TABLE_NAME_DICTBROWSE, s_dictBrowseColsAndTypes ); + forceRowidHigh( db, TABLE_NAME_SUM ); createGroupsTable( db ); } @@ -303,6 +304,7 @@ public class DBHelper extends SQLiteOpenHelper { db.execSQL( query ); } createTable( db, name, data ); + forceRowidHigh( db, name ); if ( null != columnNames ) { ArrayList oldCols = @@ -333,4 +335,16 @@ public class DBHelper extends SQLiteOpenHelper { dest, src ); db.execSQL( query ); } + + private void forceRowidHigh( SQLiteDatabase db, String name ) + { + long now = Utils.getCurSeconds(); + // knock 20 years off; whose clock can be that far back? + now -= 622080000; + String query = String.format( "INSERT INTO %s (rowid) VALUES (%d)", + name, now ); + db.execSQL( query ); + query = String.format( "DELETE FROM %s", name ); + db.execSQL( query ); + } } From 2cfa56c2d8cfc89cdf8c971c4426d4a6ffa74cf6 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 29 Jan 2013 18:34:33 -0800 Subject: [PATCH 101/116] update to not require incomplete gcm module --- xwords4/relay/scripts/gcm_loop.py | 38 +++++++++++++++++-------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/xwords4/relay/scripts/gcm_loop.py b/xwords4/relay/scripts/gcm_loop.py index c60dc6f29..11b9b59de 100755 --- a/xwords4/relay/scripts/gcm_loop.py +++ b/xwords4/relay/scripts/gcm_loop.py @@ -6,8 +6,9 @@ # # Depends on the gcm module -import getpass, sys, gcm, psycopg2, time, signal, shelve +import getpass, sys, psycopg2, time, signal, shelve, json, urllib2 from time import gmtime, strftime +from os import path # I'm not checking my key in... import mykey @@ -25,7 +26,7 @@ import mykey # contact list if it is the target of at least one message in the msgs # table. -k_shelfFile = "gcm_loop.shelf" +k_shelfFile = path.splitext( path.basename( sys.argv[0]) )[0] + ".shelf" k_SENT = 'SENT' g_con = None g_sent = None @@ -65,7 +66,7 @@ def getPendingMsgs( con, typ ): def unregister( gcmid ): global g_con print "unregister(", gcmid, ")" - query = "UPDATE devices SET unreg=TRUE WHERE devid = '%s'" % gcmid + query = "UPDATE devices SET unreg=TRUE WHERE devid = '%s' and devtype = 3" % gcmid g_con.cursor().execute( query ) def asGCMIds(con, devids, typ): @@ -77,20 +78,23 @@ def asGCMIds(con, devids, typ): def notifyGCM( devids, typ ): if typ == DEVTYPE_GCM: - instance = gcm.GCM( mykey.myKey ) - data = { 'getMoves': True, } - response = instance.json_request( registration_ids = devids, - data = data, - ) - if 'errors' in response: - response = response['errors'] - if 'NotRegistered' in response: - for gcmid in response['NotRegistered']: - unregister( gcmid ) - else: - print "got some kind of error" + values = { + 'data' : { 'getMoves': True, }, + 'registration_ids': devids, + } + params = json.dumps( values ) + req = urllib2.Request("https://android.googleapis.com/gcm/send", params ) + req.add_header( 'Content-Type' , 'application/x-www-form-urlencoded;charset=UTF-8' ) + req.add_header( 'Authorization' , 'key=' + mykey.myKey ) + req.add_header('Content-Type', 'application/json' ) + response = urllib2.urlopen( req ).read() + asJson = json.loads( response ) + + if 'success' in asJson and 'failure' in asJson and len(devids) == asJson['success'] and 0 == asJson['failure']: + print "OK" else: - if g_debug: print 'no errors:', response + print "Errors: " + print response else: print "not sending to", len(devids), "devices because typ ==", typ @@ -182,7 +186,7 @@ def main(): if 0 < emptyCount: print "" emptyCount = 0 print strftime("%Y-%m-%d %H:%M:%S", time.localtime()), - print "devices needing notification:", targets + print "devices needing notification:", targets, '=>', notifyGCM( asGCMIds( g_con, targets, typ ), typ ) pruneSent( devids ) elif g_debug: print "no targets after backoff" From d5842e79c248556ade92912ae34df9e81ad522d2 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 29 Jan 2013 18:36:19 -0800 Subject: [PATCH 102/116] use the new ANON devid type --- .../XWords4/src/org/eehouse/android/xw4/RelayService.java | 4 ++-- .../XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java index 04afdcf0b..6b8451b5e 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java @@ -532,8 +532,8 @@ public class RelayService extends XWService { if ( null != devid && 0 < devid.length() ) { typ = UtilCtxt.ID_TYPE_ANDROID_GCM; } else { - devid = "DO NOT SHIP WITH ME"; - typ = UtilCtxt.ID_TYPE_ANDROID_OTHER; + devid = ""; + typ = UtilCtxt.ID_TYPE_ANON; } } if ( null != typp ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java index 95c18bf01..ebf8b9a53 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java @@ -61,6 +61,7 @@ public interface UtilCtxt { public static final int ID_TYPE_RELAY = 1; public static final int ID_TYPE_ANDROID_GCM = 3; public static final int ID_TYPE_ANDROID_OTHER = 4; + public static final int ID_TYPE_ANON = 5; String getDevID( /*out*/ byte[] typ ); void deviceRegistered( int devIDType, String idRelay ); From d88f66a890cc1cf7b25b2bd383b7b2e2b72d4c6f Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 29 Jan 2013 18:57:03 -0800 Subject: [PATCH 103/116] record experiment with serialization -- no code change. --- .../src/org/eehouse/android/xw4/DlgDelegate.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java index a7152518f..460bdb716 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java @@ -499,9 +499,17 @@ public class DlgDelegate { private void addState( DlgState state ) { + // I'm getting serialization failures on devices pointing at + // DlgState but the code below says the object's fine (as it + // should be.) Just to have a record.... + // + // Bundle bundle = new Bundle(); + // DbgUtils.logf( "addState: testing serializable" ); + // bundle.putSerializable( "foo", state ); + // state = (DlgState)bundle.getSerializable( "foo" ); + // DbgUtils.logf( "addState: serializable is ok" ); + m_dlgStates.put( state.m_id, state ); - DbgUtils.logf( "addState: there are now %d active dialogs", - m_dlgStates.size() ); } } From 66d42fe5f2813cd3a806df275ce7538c0f7c7dd4 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 7 May 2013 07:07:10 -0700 Subject: [PATCH 104/116] fix a couple of leaks --- xwords4/linux/linuxmain.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index ce9ff07c6..624eedc7f 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -1730,9 +1730,15 @@ initParams( LaunchParams* params ) static void freeParams( LaunchParams* params ) { + XP_U16 ii; // linux_util_vt_destroy( params->util ); vtmgr_destroy( MPPARM(params->mpool) params->vtMgr ); + XP_FREEP( params->mpool, ¶ms->pgi.dictName ); + for ( ii = 0; ii < params->nLocalPlayers; ++ii ) { + XP_FREEP( params->mpool, ¶ms->pgi.players[ii].name ); + } + mpool_destroy( params->mpool ); // free( params->util ); @@ -1798,6 +1804,7 @@ main( int argc, char** argv ) /* defaults */ #ifdef XWFEATURE_RELAY mainParams.connInfo.relay.defaultSendPort = DEFAULT_PORT; + mainParams.connInfo.relay.relayName = "localhost"; mainParams.connInfo.relay.invite = "INVITE"; #endif #ifdef XWFEATURE_IP_DIRECT From 3e9bb1c6b96ca82d5510ff7ff870929b239b387c Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 8 May 2013 06:38:52 -0700 Subject: [PATCH 105/116] fix leak: dispose struct before overwriting it --- xwords4/linux/linuxmain.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 624eedc7f..33fab2e22 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -2329,8 +2329,8 @@ main( int argc, char** argv ) } if ( mainParams.needsNewGame ) { - gi_initPlayerInfo( MPPARM(mainParams.mpool) - &mainParams.pgi, NULL ); + gi_disposePlayerInfo( MPPARM(mainParams.mpool) &mainParams.pgi ); + gi_initPlayerInfo( MPPARM(mainParams.mpool) &mainParams.pgi, NULL ); } /* curses doesn't have newgame dialog */ From d74814a806097e1310fd46e23599fa5ea05854a5 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 9 May 2013 06:33:19 -0700 Subject: [PATCH 106/116] part of previous (merge) commit that got left out -- files not saved. --- xwords4/linux/gtkboard.c | 6 +- xwords4/linux/linuxmain.c | 177 ++++++++------------------------------ xwords4/relay/xwrelay.cpp | 4 +- 3 files changed, 43 insertions(+), 144 deletions(-) diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 65a38c117..8ec6d4dab 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -1173,9 +1173,9 @@ disenable_buttons( GtkGameGlobals* globals ) XP_Bool canToggle = board_canTogglePending( globals->cGlobals.game.board ); gtk_widget_set_sensitive( globals->toggle_undo_button, canToggle ); - XP_Bool canHing = board_canHint( globals->cGlobals.game.board ); - gtk_widget_set_sensitive( globals->prevhint_button, canHing ); - gtk_widget_set_sensitive( globals->nexthint_button, canHing ); + XP_Bool canHint = board_canHint( globals->cGlobals.game.board ); + gtk_widget_set_sensitive( globals->prevhint_button, canHint ); + gtk_widget_set_sensitive( globals->nexthint_button, canHint ); #ifdef XWFEATURE_CHAT XP_Bool canChat = !!globals->cGlobals.game.comms diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 7178fc07f..6d676874b 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -1765,7 +1765,7 @@ dawg2dict( const LaunchParams* params, GSList* testDicts ) guint count = g_slist_length( testDicts ); for ( int ii = 0; ii < count; ++ii ) { DictionaryCtxt* dict = - linux_dictionary_make( MPPARM(params->util->mpool) params, + linux_dictionary_make( MPPARM(params->mpool) params, g_slist_nth_data( testDicts, ii ), params->useMmap ); if ( NULL != dict ) { @@ -2190,37 +2190,22 @@ main( int argc, char** argv ) } } -<<<<<<< HEAD - XP_ASSERT( mainParams.pgi.nPlayers == mainParams.nLocalPlayers - + mainParams.info.serverInfo.nRemotePlayers ); - - if ( isServer ) { - if ( mainParams.info.serverInfo.nRemotePlayers == 0 ) { - mainParams.pgi.serverRole = SERVER_STANDALONE; - } else { - mainParams.pgi.serverRole = SERVER_ISSERVER; - } - } else { - mainParams.pgi.serverRole = SERVER_ISCLIENT; - } -======= int result; if ( g_str_has_suffix( argv[0], "dawg2dict" ) ) { result = dawg2dict( &mainParams, testDicts ); } else { - XP_ASSERT( mainParams.gi.nPlayers == mainParams.nLocalPlayers + XP_ASSERT( mainParams.pgi.nPlayers == mainParams.nLocalPlayers + mainParams.info.serverInfo.nRemotePlayers ); if ( isServer ) { if ( mainParams.info.serverInfo.nRemotePlayers == 0 ) { - mainParams.gi.serverRole = SERVER_STANDALONE; + mainParams.pgi.serverRole = SERVER_STANDALONE; } else { - mainParams.gi.serverRole = SERVER_ISSERVER; + mainParams.pgi.serverRole = SERVER_ISSERVER; } } else { - mainParams.gi.serverRole = SERVER_ISCLIENT; + mainParams.pgi.serverRole = SERVER_ISCLIENT; } ->>>>>>> android_branch /* sanity checks */ totalPlayerCount = mainParams.nLocalPlayers @@ -2236,39 +2221,21 @@ main( int argc, char** argv ) } } -<<<<<<< HEAD - if ( !!mainParams.pgi.dictName ) { - /* char path[256]; */ - /* getDictPath( &mainParams, mainParams.gi.dictName, path, VSIZE(path) ); */ - /* mainParams.dict = */ - /* linux_dictionary_make( MPPARM(mainParams.mpool) &mainParams, */ - /* mainParams.pgi.dictName, */ - /* mainParams.useMmap ); */ - /* XP_ASSERT( !!mainParams.dict ); */ - /* mainParams.pgi.dictLang = dict_getLangCode( mainParams.dict ); */ - } else if ( isServer ) { -#ifdef STUBBED_DICT - mainParams.dict = make_stubbed_dict( - MPPARM_NOCOMMA(mainParams.util->mpool) ); - XP_WARNF( "no dictionary provided: using English stub dict\n" ); - mainParams.pgi.dictLang = dict_getLangCode( mainParams.dict ); -======= - if ( !!mainParams.gi.dictName ) { + if ( !!mainParams.pgi.dictName ) { /* char path[256]; */ /* getDictPath( &mainParams, mainParams.gi.dictName, path, VSIZE(path) ); */ - mainParams.dict = - linux_dictionary_make( MPPARM(mainParams.util->mpool) &mainParams, - mainParams.gi.dictName, - mainParams.useMmap ); - XP_ASSERT( !!mainParams.dict ); - mainParams.gi.dictLang = dict_getLangCode( mainParams.dict ); + /* mainParams.dict = */ + /* linux_dictionary_make( MPPARM(mainParams.mpool) &mainParams, */ + /* mainParams.pgi.dictName, */ + /* mainParams.useMmap ); */ + /* XP_ASSERT( !!mainParams.dict ); */ + /* mainParams.pgi.dictLang = dict_getLangCode( mainParams.dict ); */ } else if ( isServer ) { #ifdef STUBBED_DICT - mainParams.dict = make_stubbed_dict( - MPPARM_NOCOMMA(mainParams.util->mpool) ); + mainParams.dict = + make_stubbed_dict( MPPARM_NOCOMMA(mainParams.util->mpool) ); XP_WARNF( "no dictionary provided: using English stub dict\n" ); - mainParams.gi.dictLang = dict_getLangCode( mainParams.dict ); ->>>>>>> android_branch + mainParams.pgi.dictLang = dict_getLangCode( mainParams.dict ); #else if ( 0 == nPlayerDicts ) { mainParams.needsNewGame = XP_TRUE; @@ -2278,34 +2245,8 @@ main( int argc, char** argv ) mainParams.needsNewGame = XP_TRUE; } -<<<<<<< HEAD - if ( 0 < mainParams.info.serverInfo.nRemotePlayers - && SERVER_STANDALONE == mainParams.pgi.serverRole ) { - mainParams.needsNewGame = XP_TRUE; - } - - /* per-player dicts are for local players only. Assign in the order - given. It's an error to give too many, or not to give enough if - there's no game-dict */ - if ( 0 < nPlayerDicts ) { - /* XP_U16 nextDict = 0; */ - /* for ( ii = 0; ii < mainParams.pgi.nPlayers; ++ii ) { */ - /* if ( mainParams.pgi.players[ii].isLocal ) { */ - /* const XP_UCHAR* name = mainParams.playerDictNames[nextDict++]; */ - /* XP_ASSERT( !!name ); */ - /* mainParams.dicts.dicts[ii] = */ - /* linux_dictionary_make( MPPARM(mainParams.mpool) */ - /* &mainParams, name, mainParams.useMmap ); */ - /* } */ - /* } */ - /* if ( nextDict < nPlayerDicts ) { */ - /* usage( argv[0], " --player-dict used more times than there are " */ - /* "local players" ); */ - /* } */ - } -======= if ( 0 < mainParams.info.serverInfo.nRemotePlayers - && SERVER_STANDALONE == mainParams.gi.serverRole ) { + && SERVER_STANDALONE == mainParams.pgi.serverRole ) { mainParams.needsNewGame = XP_TRUE; } @@ -2313,22 +2254,21 @@ main( int argc, char** argv ) given. It's an error to give too many, or not to give enough if there's no game-dict */ if ( 0 < nPlayerDicts ) { - XP_U16 nextDict = 0; - for ( ii = 0; ii < mainParams.gi.nPlayers; ++ii ) { - if ( mainParams.gi.players[ii].isLocal ) { - const XP_UCHAR* name = mainParams.playerDictNames[nextDict++]; - XP_ASSERT( !!name ); - mainParams.dicts.dicts[ii] = - linux_dictionary_make( MPPARM(mainParams.util->mpool) - &mainParams, name, mainParams.useMmap ); - } - } - if ( nextDict < nPlayerDicts ) { - usage( argv[0], " --player-dict used more times than there are " - "local players" ); - } + /* XP_U16 nextDict = 0; */ + /* for ( ii = 0; ii < mainParams.gi.nPlayers; ++ii ) { */ + /* if ( mainParams.gi.players[ii].isLocal ) { */ + /* const XP_UCHAR* name = mainParams.playerDictNames[nextDict++]; */ + /* XP_ASSERT( !!name ); */ + /* mainParams.dicts.dicts[ii] = */ + /* linux_dictionary_make( MPPARM(mainParams.util->mpool) */ + /* &mainParams, name, mainParams.useMmap ); */ + /* } */ + /* } */ + /* if ( nextDict < nPlayerDicts ) { */ + /* usage( argv[0], " --player-dict used more times than there are " */ + /* "local players" ); */ + /* } */ } ->>>>>>> android_branch /* if ( !isServer ) { */ /* if ( mainParams.info.serverInfo.nRemotePlayers > 0 ) { */ @@ -2336,17 +2276,11 @@ main( int argc, char** argv ) /* } */ /* } */ #ifdef XWFEATURE_WALKDICT -<<<<<<< HEAD - if ( !!testDicts ) { - walk_dict_test_all( mainParams.mpool, &mainParams, testDicts, testPrefixes, testMinMax ); - exit( 0 ); - } -======= if ( !!testDicts ) { - walk_dict_test_all( &mainParams, testDicts, testPrefixes, testMinMax ); + walk_dict_test_all( MPPARM(mainParams.mpool) &mainParams, testDicts, + testPrefixes, testMinMax ); exit( 0 ); } ->>>>>>> android_branch #endif if ( 0 ) { #ifdef XWFEATURE_RELAY @@ -2409,19 +2343,7 @@ main( int argc, char** argv ) /* mainParams.util->vtable->m_util_makeStreamFromAddr = */ /* linux_util_makeStreamFromAddr; */ -<<<<<<< HEAD - // mainParams.util->gameInfo = &mainParams.pgi; -======= - mainParams.util->gameInfo = &mainParams.gi; - - linux_util_vt_init( MPPARM(mainParams.util->mpool) mainParams.util ); - -#ifndef XWFEATURE_STANDALONE_ONLY - mainParams.util->vtable->m_util_informMissing = linux_util_informMissing; - mainParams.util->vtable->m_util_addrChange = linux_util_addrChange; - mainParams.util->vtable->m_util_setIsServer = linux_util_setIsServer; -#endif ->>>>>>> android_branch + // mainParams.util->gameInfo = &mainParams.pgi; srandom( seed ); /* init linux random number generator */ XP_LOGF( "seeded srandom with %d", seed ); @@ -2443,17 +2365,10 @@ main( int argc, char** argv ) mainParams.serverRole = SERVER_ISCLIENT; } -<<<<<<< HEAD - if ( mainParams.needsNewGame ) { - gi_disposePlayerInfo( MPPARM(mainParams.mpool) &mainParams.pgi ); - gi_initPlayerInfo( MPPARM(mainParams.mpool) &mainParams.pgi, NULL ); - } -======= if ( mainParams.needsNewGame ) { - gi_initPlayerInfo( MPPARM(mainParams.util->mpool) - &mainParams.gi, NULL ); + gi_disposePlayerInfo( MPPARM(mainParams.mpool) &mainParams.pgi ); + gi_initPlayerInfo( MPPARM(mainParams.mpool) &mainParams.pgi, NULL ); } ->>>>>>> android_branch /* curses doesn't have newgame dialog */ if ( useCurses && !mainParams.needsNewGame ) { @@ -2462,31 +2377,15 @@ main( int argc, char** argv ) #endif } else if ( !useCurses ) { #if defined PLATFORM_GTK -<<<<<<< HEAD - gtk_init( &argc, &argv ); - gtkmain( &mainParams ); -======= - gtkmain( &mainParams, argc, argv ); ->>>>>>> android_branch + gtk_init( &argc, &argv ); + gtkmain( &mainParams ); #endif } else { usage( argv[0], "rtfm" ); } -<<<<<<< HEAD - freeParams( &mainParams ); -======= - vtmgr_destroy( MPPARM(mainParams.util->mpool) mainParams.vtMgr ); - - linux_util_vt_destroy( mainParams.util ); - - mpool_destroy( mainParams.util->mpool ); - - free( mainParams.util ); - - result = 0; + freeParams( &mainParams ); } ->>>>>>> android_branch XP_LOGF( "%s exiting main", argv[0] ); return result; diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index 5d0cb9d84..f5c172f48 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -1104,7 +1104,7 @@ handleProxyMsgs( int sock, const AddrInfo* addr, const unsigned char* bufp, if ( getNetShort( &bufp, end, &len ) ) { if ( handlePutMessage( scr, hid, addr, len, &bufp, end ) ) { continue; - } + } } break; } @@ -1196,7 +1196,7 @@ proxy_thread_proc( UdpThreadClosure* utc ) int olen = 0; /* return a 0-length message */ write( socket, &olen, sizeof(olen) ); break; /* PRX_DEVICE_GONE */ - } + } default: logf( XW_LOGERROR, "unexpected command %d", __func__, cmd ); break; From 79a212c985652f61517e2d79d74a4616e7a4d8ca Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 9 May 2013 21:37:58 -0700 Subject: [PATCH 107/116] don't put up dict change dialog (which blocks) unless names are different. --- xwords4/linux/gtkboard.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 8ec6d4dab..cc4cb9db2 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -1573,17 +1573,19 @@ gtk_util_informNetDict( XW_UtilCtxt* uc, XP_LangCode XP_UNUSED(lang), const XP_UCHAR* newName, const XP_UCHAR* newSum, XWPhoniesChoice phoniesAction ) { - GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; - gchar buf[512]; - - int offset = snprintf( buf, VSIZE(buf), - "dict changing from %s to %s (sum=%s).", - oldName, newName, newSum ); - if ( PHONIES_DISALLOW == phoniesAction ) { - snprintf( &buf[offset], VSIZE(buf)-offset, "%s", - "\nPHONIES_DISALLOW is set so this may lead to some surprises." ); + if ( 0 != strcmp( oldName, newName ) ) { + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + gchar buf[512]; + int offset = snprintf( buf, VSIZE(buf), + "dict changing from %s to %s (sum=%s).", + oldName, newName, newSum ); + if ( PHONIES_DISALLOW == phoniesAction ) { + snprintf( &buf[offset], VSIZE(buf)-offset, "%s", + "\nPHONIES_DISALLOW is set so this may " + "lead to some surprises." ); + } + (void)gtkask( globals->window, buf, GTK_BUTTONS_OK ); } - (void)gtkask( globals->window, buf, GTK_BUTTONS_OK ); } static gint From f9f7874a55379ac6efa8b5e63bdbf51dc9f973c9 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 9 May 2013 21:53:54 -0700 Subject: [PATCH 108/116] make use of base-64 encoding runtime-configurable --- xwords4/relay/dbmgr.cpp | 5 +++-- xwords4/relay/xwrelay.conf_tmplate | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index 75246fef1..71014f8e3 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -62,8 +62,9 @@ DBMgr::Get() DBMgr::DBMgr() { - logf( XW_LOGINFO, "%s called", __func__ ); - m_useB64 = false; + int tmp; + RelayConfigs::GetConfigs()->GetValueFor( "USE_B64", &tmp ); + m_useB64 = tmp != 0; pthread_key_create( &m_conn_key, destr_function ); diff --git a/xwords4/relay/xwrelay.conf_tmplate b/xwords4/relay/xwrelay.conf_tmplate index 27d79ec06..66d455a6b 100644 --- a/xwords4/relay/xwrelay.conf_tmplate +++ b/xwords4/relay/xwrelay.conf_tmplate @@ -63,3 +63,8 @@ LOGFILE_PATH=./xwrelay.log # Delay sending packets so devices/emulators on the same machine as # relay have a bit more natural experience # SEND_DELAY_MILLIS=500 + +# Use base64 encoding rather than PQescapeByteaConn for stored +# messages. The latter doesn't seem as reliable with newer psql +# servers. Anything but 0 is treated as true. +USE_B64=1 From 4470a9e66c3fae78a50c0fc76b02ff91e575d9f1 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 9 May 2013 22:15:38 -0700 Subject: [PATCH 109/116] clean up a couple of debug-only strings --- xwords4/android/XWords4/res/values/common_rsrc.xml | 3 ++- xwords4/android/XWords4/res/xml/xwprefs.xml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/xwords4/android/XWords4/res/values/common_rsrc.xml b/xwords4/android/XWords4/res/values/common_rsrc.xml index 261efccf5..86c77e45f 100644 --- a/xwords4/android/XWords4/res/values/common_rsrc.xml +++ b/xwords4/android/XWords4/res/values/common_rsrc.xml @@ -129,7 +129,8 @@ Write DB to SD card Load DB from SD card New/Experimental relay connection - Ignore GCM notices (for testing) + Ignore GCM notices + (for testing relay proxy) diff --git a/xwords4/android/XWords4/res/xml/xwprefs.xml b/xwords4/android/XWords4/res/xml/xwprefs.xml index 2ca59eb67..b7eed6953 100644 --- a/xwords4/android/XWords4/res/xml/xwprefs.xml +++ b/xwords4/android/XWords4/res/xml/xwprefs.xml @@ -300,7 +300,8 @@ android:defaultValue="false" /> Date: Fri, 10 May 2013 07:19:32 -0700 Subject: [PATCH 110/116] toward having commandline params show up as defaults in new game dialog --- xwords4/linux/gtkboard.c | 14 ++++++++++++-- xwords4/linux/linuxmain.c | 25 ++++++++++++++----------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index cc4cb9db2..61c48ba6a 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -2415,6 +2415,8 @@ initGlobalsNoDraw( GtkGameGlobals* globals, LaunchParams* params ) { memset( globals, 0, sizeof(*globals) ); + gi_copy( MPPARM(params->mpool) &globals->cGlobals.gi, ¶ms->pgi ); + globals->cGlobals.params = params; globals->cGlobals.lastNTilesToUse = MAX_TRAY_TILES; #ifndef XWFEATURE_STANDALONE_ONLY @@ -2618,8 +2620,16 @@ makeNewGame( GtkGameGlobals* globals ) if ( !!cGlobals->game.comms ) { comms_getAddr( cGlobals->game.comms, &cGlobals->addr ); } else { - comms_getInitialAddr( &cGlobals->addr, RELAY_NAME_DEFAULT, - RELAY_PORT_DEFAULT ); + LaunchParams* params = globals->cGlobals.params; + const XP_UCHAR* relayName = params->connInfo.relay.relayName; + if ( !relayName ) { + relayName = RELAY_NAME_DEFAULT; + } + XP_U16 relayPort = params->connInfo.relay.defaultSendPort; + if ( 0 == relayPort ) { + relayPort = RELAY_PORT_DEFAULT; + } + comms_getInitialAddr( &cGlobals->addr, relayName, relayPort ); } CurGameInfo* gi = &cGlobals->gi; diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 6d676874b..f248b748b 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -87,7 +87,7 @@ file_exists( const char* fileName ) struct stat statBuf; int statResult = stat( fileName, &statBuf ); - XP_LOGF( "%s(%s)=>%d", __func__, fileName, statResult == 0 ); + // XP_LOGF( "%s(%s)=>%d", __func__, fileName, statResult == 0 ); return statResult == 0; } /* file_exists */ @@ -1906,6 +1906,7 @@ main( int argc, char** argv ) conType == COMMS_CONN_RELAY ); mainParams.connInfo.relay.invite = optarg; conType = COMMS_CONN_RELAY; + isServer = XP_TRUE; /* implicit */ break; #endif case CMD_HOSTIP: @@ -2365,23 +2366,25 @@ main( int argc, char** argv ) mainParams.serverRole = SERVER_ISCLIENT; } - if ( mainParams.needsNewGame ) { - gi_disposePlayerInfo( MPPARM(mainParams.mpool) &mainParams.pgi ); - gi_initPlayerInfo( MPPARM(mainParams.mpool) &mainParams.pgi, NULL ); - } + /* if ( mainParams.needsNewGame ) { */ + /* gi_disposePlayerInfo( MPPARM(mainParams.mpool) &mainParams.pgi ); */ + /* gi_initPlayerInfo( MPPARM(mainParams.mpool) &mainParams.pgi, NULL ); */ + /* } */ - /* curses doesn't have newgame dialog */ - if ( useCurses && !mainParams.needsNewGame ) { + if ( useCurses ) { + if ( mainParams.needsNewGame ) { + /* curses doesn't have newgame dialog */ + usage( argv[0], "game params required for curses version" ); + } else { #if defined PLATFORM_NCURSES - cursesmain( isServer, &mainParams ); + cursesmain( isServer, &mainParams ); #endif - } else if ( !useCurses ) { + } + } else { #if defined PLATFORM_GTK gtk_init( &argc, &argv ); gtkmain( &mainParams ); #endif - } else { - usage( argv[0], "rtfm" ); } freeParams( &mainParams ); From cc2ecdc5b2c2d5df45f815869767953603b0fa8c Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 22 May 2013 07:03:08 -0700 Subject: [PATCH 111/116] two post-conflicted-merge changes git didn't pick up for some reason (emacs?) --- .../eehouse/android/xw4/GCMIntentService.java | 1 - .../org/eehouse/android/xw4/RelayService.java | 45 +++++++------------ 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java index d8edf0c99..923041a84 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java @@ -95,7 +95,6 @@ public class GCMIntentService extends GCMBaseIntentService { if ( null != title ) { int code = value.hashCode() ^ title.hashCode(); Utils.postNotification( context, null, title, value, code ); - } } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java index 16b40a6da..db2f6f268 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java @@ -23,12 +23,9 @@ package org.eehouse.android.xw4; import android.app.Service; import android.content.Context; import android.content.Intent; -<<<<<<< HEAD -import java.io.ByteArrayInputStream; -======= import android.os.AsyncTask; import android.os.IBinder; ->>>>>>> android_branch +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -169,6 +166,21 @@ public class RelayService extends XWService { switch( cmd ) { case -1: break; + case PROCESS_MSGS: + String[] relayIDs = new String[1]; + relayIDs[0] = intent.getStringExtra( RELAY_ID ); + long[] rowIDs = DBUtils.getRowIDsFor( this, relayIDs[0] ); + if ( 0 < rowIDs.length ) { + String[] msgs64 = intent.getStringArrayExtra( MSGS ); + int count = msgs64.length; + + byte[][][] msgs = new byte[1][count][]; + for ( int ii = 0; ii < count; ++ii ) { + msgs[0][ii] = XwJNI.base64Decode( msgs64[ii] ); + } + process( msgs, rowIDs, relayIDs ); + } + break; case UDP_CHANGED: DbgUtils.logf( "RelayService::onStartCommand::UDP_CHANGED" ); if ( XWPrefs.getUDPEnabled( this ) ) { @@ -202,31 +214,6 @@ public class RelayService extends XWService { return result; } - @Override - public int onStartCommand( Intent intent, int flags, int startId ) - { - int cmd = intent.getIntExtra( CMD_STR, -1 ); - switch( cmd ) { - case PROCESS_MSGS: - String[] relayIDs = new String[1]; - relayIDs[0] = intent.getStringExtra( RELAY_ID ); - long[] rowIDs = DBUtils.getRowIDsFor( this, relayIDs[0] ); - if ( 0 < rowIDs.length ) { - String[] msgs64 = intent.getStringArrayExtra( MSGS ); - int count = msgs64.length; - - byte[][][] msgs = new byte[1][count][]; - for ( int ii = 0; ii < count; ++ii ) { - msgs[0][ii] = XwJNI.base64Decode( msgs64[ii] ); - } - process( msgs, rowIDs, relayIDs ); - } - break; - } - stopSelf( startId ); - return Service.START_NOT_STICKY; - } - private void setupNotification( String[] relayIDs ) { for ( String relayID : relayIDs ) { From 79f07e8751f1c0a522a1a8ac0bcfb66f90ce399c Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 9 Jul 2013 07:19:20 -0700 Subject: [PATCH 112/116] cleanup --- xwords4/common/mempool.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/xwords4/common/mempool.c b/xwords4/common/mempool.c index 206c4de04..5a5768ac2 100644 --- a/xwords4/common/mempool.c +++ b/xwords4/common/mempool.c @@ -228,6 +228,7 @@ mpool_free( MemPoolCtx* mpool, void* ptr, const char* file, if ( !entry ) { XP_LOGF( "findEntryFor failed; called from %s, line %ld in %s", func, lineNo, file ); + XP_ASSERT( 0 ); } else { #ifdef MPOOL_DEBUG @@ -251,11 +252,7 @@ mpool_free( MemPoolCtx* mpool, void* ptr, const char* file, ++mpool->nFree; --mpool->nUsed; - - return; } - - XP_ASSERT( 0 ); } /* mpool_free */ void From f5696334a93edf1038bcde4bf0d6e29bf658cb00 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 9 Jul 2013 07:25:23 -0700 Subject: [PATCH 113/116] changes to get the merged code to compile but not included by git in the commit --- xwords4/linux/cursesmain.c | 9 --------- xwords4/linux/gtkboard.h | 1 + xwords4/linux/gtkmain.c | 22 +++++++++++++--------- xwords4/linux/linuxmain.c | 25 +++++++++++++------------ xwords4/linux/scripts/discon_ok2.sh | 14 ++++++++------ 5 files changed, 35 insertions(+), 36 deletions(-) diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index 6f8e68c12..d2c7e20f2 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -1712,15 +1712,6 @@ relay_error_curses( void* XP_UNUSED(closure), XWREASON XP_UNUSED_DBG(relayErr) ) } #ifdef USE_GLIBLOOP -static gboolean -read_quit( GIOChannel* XP_UNUSED(source), GIOCondition XP_UNUSED(condition), - gpointer data ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)data; - handleQuit( globals ); - return TRUE; -} - static gboolean handle_stdin( GIOChannel* XP_UNUSED_DBG(source), GIOCondition condition, gpointer data ) diff --git a/xwords4/linux/gtkboard.h b/xwords4/linux/gtkboard.h index 5bffc2481..9fdfb8a62 100644 --- a/xwords4/linux/gtkboard.h +++ b/xwords4/linux/gtkboard.h @@ -55,6 +55,7 @@ typedef struct GtkDrawCtx { GdkColor black; GdkColor white; + GdkColor grey; GdkColor red; /* for pending tiles */ GdkColor tileBack; /* for pending tiles */ GdkColor cursor; diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index b646ddb67..4c57fc330 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -383,15 +383,16 @@ static void gtkSocketChanged( void* closure, int newSock, int XP_UNUSED(oldSock), SockReceiver proc, void* procClosure ) { - /* GtkAppGlobals* apg = (GtkAppGlobals*)closure; */ - /* SourceData* sd = g_malloc( sizeof(*sd) ); */ - /* sd->channel = g_io_channel_unix_new( newSock ); */ - /* sd->watch = g_io_add_watch( sd->channel, G_IO_IN | G_IO_ERR, */ - /* gtk_app_socket_proc, apg ); */ - /* sd->proc = proc; */ - /* sd->procClosure = procClosure; */ - /* apg->sources = g_list_append( apg->sources, sd ); */ - +#if 1 + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + SourceData* sd = g_malloc( sizeof(*sd) ); + sd->channel = g_io_channel_unix_new( newSock ); + sd->watch = g_io_add_watch( sd->channel, G_IO_IN | G_IO_ERR, + gtk_app_socket_proc, apg ); + sd->proc = proc; + sd->procClosure = procClosure; + apg->sources = g_list_append( apg->sources, sd ); +#else GtkAppGlobals* globals = (GtkAppGlobals*)closure; SockInfo* info = (SockInfo*)*storage; XP_LOGF( "%s(old:%d; new:%d)", __func__, oldSock, newSock ); @@ -430,6 +431,7 @@ gtkSocketChanged( void* closure, int newSock, int XP_UNUSED(oldSock), if ( (comms != NULL) && (comms_getConType(comms) == COMMS_CONN_BT) ) { comms_resendAll( comms, XP_FALSE ); } +#endif LOG_RETURN_VOID(); } /* gtk_socket_changed */ @@ -527,6 +529,8 @@ getSelRow( const GtkAppGlobals* apg ) return result; } +static GtkAppGlobals* g_globals_for_signal = NULL; + static void handle_sigintterm( int XP_UNUSED(sig) ) { diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index a8d24b816..b0eb0eb91 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -77,7 +77,7 @@ #define DEFAULT_PORT 10997 #define DEFAULT_LISTEN_PORT 4998 -static int blocking_read( int fd, unsigned char* buf, int len ); +static int blocking_read( int fd, unsigned char* buf, const int len ); XP_Bool file_exists( const char* fileName ) @@ -1038,21 +1038,22 @@ linux_tcp_send( CommonGlobals* cGlobals, const XP_U8* buf, XP_U16 buflen, if ( sock != -1 ) { assert( cGlobals->socket == sock ); (*cGlobals->socketChanged)( cGlobals->socketChangedClosure, - -1, sock, &cGlobals->storage ); + -1, sock, &cGlobals->storage ); } } - if ( sock != -1 ) { - XP_U16 netLen = htons( buflen ); - XP_U8 tmp[buflen + sizeof(netLen)]; - XP_MEMCPY( &tmp[0], &netLen, sizeof(netLen) ); - XP_MEMCPY( &tmp[sizeof(netLen)], buf, buflen ); + if ( sock != -1 ) { + XP_U16 netLen = htons( buflen ); + XP_U8 tmp[buflen + sizeof(netLen)]; + XP_MEMCPY( &tmp[0], &netLen, sizeof(netLen) ); + XP_MEMCPY( &tmp[sizeof(netLen)], buf, buflen ); - if ( send_per_params( tmp, buflen + sizeof(netLen), globals ) ) { - result = buflen; + if ( send_per_params( tmp, buflen + sizeof(netLen), cGlobals ) ) { + result = buflen; + } + } else { + XP_LOGF( "%s: socket still -1", __func__ ); } - } else { - XP_LOGF( "%s: socket still -1", __func__ ); } return result; } /* linux_tcp_send */ @@ -1856,7 +1857,7 @@ freeParams( LaunchParams* params ) // linux_util_vt_destroy( params->util ); vtmgr_destroy( MPPARM(params->mpool) params->vtMgr ); - XP_FREEP( params->mpool, ¶ms->pgi.dictName ); + // XP_FREEP( params->mpool, ¶ms->pgi.dictName ); for ( ii = 0; ii < params->nLocalPlayers; ++ii ) { XP_FREEP( params->mpool, ¶ms->pgi.players[ii].name ); } diff --git a/xwords4/linux/scripts/discon_ok2.sh b/xwords4/linux/scripts/discon_ok2.sh index 849011643..03307363e 100755 --- a/xwords4/linux/scripts/discon_ok2.sh +++ b/xwords4/linux/scripts/discon_ok2.sh @@ -479,7 +479,8 @@ run_cmds() { try_upgrade $KEY launch $KEY & PID=$! - renice +1 $PID >/dev/null + # renice doesn't work on one of my machines... + renice -n 1 -p $PID >/dev/null || /bin/true PIDS[$KEY]=$PID ROOM_PIDS[$ROOM]=$PID MINEND[$KEY]=$(($NOW + $MINRUN)) @@ -587,6 +588,7 @@ while [ "$#" -gt 0 ]; do --via-udp) UDP_PCT=$(getArg $*) shift + ;; --clean-start) DO_CLEAN=1 ;; @@ -642,14 +644,14 @@ while [ "$#" -gt 0 ]; do UNDO_PCT=$(getArg $*) shift ;; - --send-chat) + --send-chat) SEND_CHAT=$(getArg $*) shift ;; - --resign-ratio) - RESIGN_RATIO=$(getArg $*) - shift - ;; + --resign-ratio) + RESIGN_RATIO=$(getArg $*) + shift + ;; --help) usage ;; From 48fbf15a69ff30091e0dbc6a9ac212dad8f94e40 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 9 Jul 2013 18:07:03 -0700 Subject: [PATCH 114/116] needed to quote variable --- xwords4/linux/scripts/discon_ok2.sh | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/xwords4/linux/scripts/discon_ok2.sh b/xwords4/linux/scripts/discon_ok2.sh index 03307363e..8c7dfdc3c 100755 --- a/xwords4/linux/scripts/discon_ok2.sh +++ b/xwords4/linux/scripts/discon_ok2.sh @@ -224,7 +224,7 @@ build_cmds() { fi PARAMS="$PARAMS --drop-nth-packet $DROP_N $PLAT_PARMS" # PARAMS="$PARAMS --split-packets 2" - if [ -n $SEND_CHAT ]; then + if [ -n "$SEND_CHAT" ]; then PARAMS="$PARAMS --send-chat $SEND_CHAT" fi # PARAMS="$PARAMS --savefail-pct 10" @@ -328,7 +328,7 @@ kill_from_log() { if [ -n "$RELAYID" ]; then OBITS="$OBITS -d $RELAYID" if [ 0 -eq $(($RANDOM%2)) ]; then - ../relay/rq -a $HOST $OBITS 2>/dev/null || true + ../relay/rq -a $HOST $OBITS 2>/dev/null || /bin/true OBITS="" fi return 0 # success @@ -344,7 +344,7 @@ maybe_resign() { if grep -q XWRELAY_ALLHERE $LOG; then if [ 0 -eq $(($RANDOM % $RESIGN_RATIO)) ]; then echo "making $LOG $(connName $LOG) resign..." - kill_from_log $LOG && close_device $KEY $DEADDIR "resignation forced" || true + kill_from_log $LOG && close_device $KEY $DEADDIR "resignation forced" || /bin/true fi fi fi @@ -392,18 +392,18 @@ check_game() { # kill_from_logs $OTHERS $KEY for ID in $OTHERS $KEY; do echo -n "${LOGS[$ID]}, " - kill_from_log ${LOGS[$ID]} || true + kill_from_log ${LOGS[$ID]} || /bin/true close_device $ID $DONEDIR "game over" done echo "" # XWRELAY_ERROR_DELETED may be old elif grep -q 'relay_error_curses(XWRELAY_ERROR_DELETED)' $LOG; then echo "deleting $LOG $(connName $LOG) b/c another resigned" - kill_from_log $LOG || true + kill_from_log $LOG || /bin/true close_device $KEY $DEADDIR "other resigned" elif grep -q 'relay_error_curses(XWRELAY_ERROR_DEADGAME)' $LOG; then echo "deleting $LOG $(connName $LOG) b/c another resigned" - kill_from_log $LOG || true + kill_from_log $LOG || /bin/true close_device $KEY $DEADDIR "other resigned" else maybe_resign $KEY @@ -435,7 +435,7 @@ get_relayid() { update_devid_cmd() { KEY=$1 - HELP="$(${APPS[$KEY]} --help 2>&1 || true)" + HELP="$(${APPS[$KEY]} --help 2>&1 || /bin/true)" if echo $HELP | grep -q '\-\-devid'; then CMD="--devid LINUX_TEST_$(printf %.5d ${KEY})" LOG=${LOGS[$KEY]} @@ -480,7 +480,7 @@ run_cmds() { launch $KEY & PID=$! # renice doesn't work on one of my machines... - renice -n 1 -p $PID >/dev/null || /bin/true + renice -n 1 -p $PID >/dev/null 2>&1 || /bin/true PIDS[$KEY]=$PID ROOM_PIDS[$ROOM]=$PID MINEND[$KEY]=$(($NOW + $MINRUN)) @@ -489,7 +489,7 @@ run_cmds() { if [ -d /proc/$PID ]; then SLEEP=$((${MINEND[$KEY]} - $NOW)) [ $SLEEP -gt 0 ] && sleep $SLEEP - kill $PID || true + kill $PID || /bin/true wait $PID fi PIDS[$KEY]=0 @@ -500,7 +500,7 @@ run_cmds() { fi done - [ -n "$OBITS" ] && ../relay/rq -a $HOST $OBITS 2>/dev/null || true + [ -n "$OBITS" ] && ../relay/rq -a $HOST $OBITS 2>/dev/null || /bin/true # kill any remaining games if [ $COUNT -gt 0 ]; then @@ -541,7 +541,7 @@ run_via_rq() { launch $KEY & PID=$! sleep 2 - kill $PID || true + kill $PID || /bin/true wait $PID fi [ "$DROP_N" -ge 0 ] && increment_drop $KEY From 5a20cc282c6f267ce25d8200cfb1e0465004fee4 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 9 Jul 2013 18:10:41 -0700 Subject: [PATCH 115/116] fix so curses app works well enough that discon_ok2.sh script runs successfully. GTK mode is untested and certainly broken. --- xwords4/linux/cursesmain.c | 19 +++++++++--------- xwords4/linux/gtkboard.c | 40 +++++++++++++++++++------------------- xwords4/linux/linuxmain.c | 35 +++++++++++++-------------------- xwords4/linux/linuxutl.c | 2 +- xwords4/linux/main.h | 2 +- 5 files changed, 45 insertions(+), 53 deletions(-) diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index d2c7e20f2..938bc4cbc 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -240,7 +240,7 @@ curses_util_userPickTileBlank( XW_UtilCtxt* uc, XP_U16 playerNum, CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; char query[128]; XP_S16 index; - char* playerName = globals->cGlobals.gi.players[playerNum].name; + char* playerName = globals->cGlobals.gi->players[playerNum].name; snprintf( query, sizeof(query), "Pick tile for %s! (Tab or type letter to select " @@ -258,7 +258,7 @@ curses_util_userPickTileTray( XW_UtilCtxt* uc, const PickInfo* XP_UNUSED(pi), CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; char query[128]; XP_S16 index; - char* playerName = globals->cGlobals.gi.players[playerNum].name; + char* playerName = globals->cGlobals.gi->players[playerNum].name; snprintf( query, sizeof(query), "Pick tile for %s! (Tab or type letter to select " @@ -1631,7 +1631,7 @@ positionSizeStuff( CursesAppGlobals* globals, int width, int height ) XP_U16 cellWidth, cellHt, scoreLeft, scoreWidth; BoardCtxt* board = globals->cGlobals.game.board; int remWidth = width; - int nRows = globals->cGlobals.gi.boardSize; + int nRows = globals->cGlobals.gi->boardSize; cellWidth = CURSES_CELL_WIDTH; cellHt = CURSES_CELL_HT; @@ -1917,6 +1917,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) g_globals.cGlobals.cp.robotTradePct = params->robotTradePct; #endif + g_globals.cGlobals.gi = ¶ms->pgi; setupUtil( &g_globals.cGlobals ); setupCursesUtilCallbacks( &g_globals, g_globals.cGlobals.util ); @@ -2040,26 +2041,26 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) } else { cGlobals->dict = linux_dictionary_make( MEMPOOL params, - cGlobals->gi.dictName, XP_TRUE ); + cGlobals->gi->dictName, XP_TRUE ); } } - cGlobals->gi.dictLang = dict_getLangCode( cGlobals->dict ); + cGlobals->gi->dictLang = dict_getLangCode( cGlobals->dict ); if ( !!stream ) { (void)game_makeFromStream( MEMPOOL stream, &cGlobals->game, - &cGlobals->gi, cGlobals->dict, + cGlobals->gi, cGlobals->dict, &cGlobals->dicts, cGlobals->util, (DrawCtx*)g_globals.draw, &g_globals.cGlobals.cp, &procs ); stream_destroy( stream ); - if ( !isServer && cGlobals->gi.serverRole == SERVER_ISSERVER ) { + if ( !isServer && cGlobals->gi->serverRole == SERVER_ISSERVER ) { isServer = XP_TRUE; } opened = XP_TRUE; } if ( !opened ) { - game_makeNewGame( MEMPOOL &cGlobals->game, &cGlobals->gi, + game_makeNewGame( MEMPOOL &cGlobals->game, cGlobals->gi, cGlobals->util, (DrawCtx*)g_globals.draw, &g_globals.cGlobals.cp, &procs, params->gameSeed ); g_globals.cGlobals.selRow = -1; @@ -2159,7 +2160,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) saveGame( &g_globals.cGlobals ); game_dispose( &g_globals.cGlobals.game ); /* takes care of the dict */ - gi_disposePlayerInfo( MEMPOOL &cGlobals->gi ); + gi_disposePlayerInfo( MEMPOOL cGlobals->gi ); #ifdef XWFEATURE_BLUETOOTH linux_bt_close( &g_globals.cGlobals ); diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 61c48ba6a..b3e9b8c75 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -476,8 +476,8 @@ createOrLoadObjects( GtkGameGlobals* globals ) } opened = game_makeFromStream( MEMPOOL stream, &cGlobals->game, - &cGlobals->gi, - cGlobals->dict, &cGlobals->dicts, cGlobals->util, + cGlobals->gi, cGlobals->dict, + &cGlobals->dicts, cGlobals->util, (DrawCtx*)globals->draw, &cGlobals->cp, &procs ); XP_LOGF( "%s: loaded gi at %p", __func__, &cGlobals->gi ); @@ -497,7 +497,7 @@ createOrLoadObjects( GtkGameGlobals* globals ) /* = params->connInfo.relay.relayName; */ /* } */ #endif - game_makeNewGame( MEMPOOL &cGlobals->game, &cGlobals->gi, + game_makeNewGame( MEMPOOL &cGlobals->game, cGlobals->gi, cGlobals->util, (DrawCtx*)globals->draw, &cGlobals->cp, &procs, params->gameSeed ); @@ -550,7 +550,7 @@ createOrLoadObjects( GtkGameGlobals* globals ) model_setPlayerDicts( cGlobals->game.model, &cGlobals->dicts ); #ifdef XWFEATURE_SEARCHLIMIT - cGlobals->gi.allowHintRect = params->allowHintRect; + cGlobals->gi->allowHintRect = params->allowHintRect; #endif @@ -558,7 +558,7 @@ createOrLoadObjects( GtkGameGlobals* globals ) new_game_impl( globals, XP_FALSE ); #ifndef XWFEATURE_STANDALONE_ONLY } else { - DeviceRole serverRole = cGlobals->gi.serverRole; + DeviceRole serverRole = cGlobals->gi->serverRole; if ( serverRole == SERVER_ISCLIENT ) { XWStreamCtxt* stream = mem_stream_make( MEMPOOL params->vtMgr, @@ -608,7 +608,7 @@ configure_event( GtkWidget* widget, GdkEventConfigure* XP_UNUSED(event), createOrLoadObjects( globals ); } - nCols = globals->cGlobals.gi.boardSize; + nCols = globals->cGlobals.gi->boardSize; nRows = nCols; bdWidth = widget->allocation.width - (GTK_RIGHT_MARGIN + GTK_BOARD_LEFT_MARGIN); @@ -767,7 +767,7 @@ cleanup( GtkGameGlobals* globals ) linux_close_socket( &globals->cGlobals ); #endif game_dispose( &globals->cGlobals.game ); /* takes care of the dict */ - gi_disposePlayerInfo( MEMPOOL &globals->cGlobals.gi ); + gi_disposePlayerInfo( MEMPOOL globals->cGlobals.gi ); linux_util_vt_destroy( globals->cGlobals.util ); free( globals->cGlobals.util ); @@ -862,7 +862,7 @@ new_game_impl( GtkGameGlobals* globals, XP_Bool fireConnDlg ) comms_getInitialAddr( &addr, RELAY_NAME_DEFAULT, RELAY_PORT_DEFAULT ); } - CurGameInfo* gi = &globals->cGlobals.gi; + CurGameInfo* gi = globals->cGlobals.gi; success = newGameDialog( globals, gi, &addr, XP_TRUE, fireConnDlg ); if ( success ) { #ifndef XWFEATURE_STANDALONE_ONLY @@ -934,7 +934,7 @@ game_info( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) /* Anything to do if OK is clicked? Changed names etc. already saved. Try server_do in case one's become a robot. */ - CurGameInfo* gi = &globals->cGlobals.gi; + CurGameInfo* gi = globals->cGlobals.gi; if ( newGameDialog( globals, gi, &addr, XP_FALSE, XP_FALSE ) ) { if ( server_do( globals->cGlobals.game.server ) ) { board_draw( globals->cGlobals.game.board ); @@ -968,7 +968,7 @@ change_dictionary( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) linux_dictionary_make( MPPARM(cGlobals->util->mpool) params, name, params->useMmap ); game_changeDict( MPPARM(cGlobals->util->mpool) &cGlobals->game, - &cGlobals->gi, dict ); + cGlobals->gi, dict ); } g_slist_free( dicts ); } /* change_dictionary */ @@ -1375,7 +1375,7 @@ handle_hide_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) XP_Bool draw = XP_FALSE; if ( globals->cGlobals.params->nHidden > 0 ) { - gint nRows = globals->cGlobals.gi.boardSize; + gint nRows = globals->cGlobals.gi->boardSize; globals->adjustment->page_size = nRows; globals->adjustment->value = 0.0; @@ -1430,7 +1430,7 @@ gtk_util_userPickTileBlank( XW_UtilCtxt* uc, XP_U16 playerNum, { XP_S16 chosen; GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; - XP_UCHAR* name = globals->cGlobals.gi.players[playerNum].name; + XP_UCHAR* name = globals->cGlobals.gi->players[playerNum].name; chosen = gtkletterask( NULL, XP_FALSE, name, nTiles, texts ); return chosen; @@ -1443,7 +1443,7 @@ gtk_util_userPickTileTray( XW_UtilCtxt* uc, const PickInfo* pi, { XP_S16 chosen; GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; - XP_UCHAR* name = globals->cGlobals.gi.players[playerNum].name; + XP_UCHAR* name = globals->cGlobals.gi->players[playerNum].name; chosen = gtkletterask( pi, XP_TRUE, name, nTiles, texts ); return chosen; @@ -1498,7 +1498,7 @@ gtk_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 maxOffset, { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; if ( !!globals->adjustment ) { - gint nRows = globals->cGlobals.gi.boardSize; + gint nRows = globals->cGlobals.gi->boardSize; globals->adjustment->page_size = nRows - maxOffset; globals->adjustment->value = newOffset; gtk_adjustment_value_changed( GTK_ADJUSTMENT(globals->adjustment) ); @@ -1801,7 +1801,7 @@ gtk_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 player, if ( turnLost ) { char wordsBuf[256]; XP_U16 i; - XP_UCHAR* name = globals->cGlobals.gi.players[player].name; + XP_UCHAR* name = globals->cGlobals.gi->players[player].name; XP_ASSERT( !!name ); for ( i = 0, wordsBuf[0] = '\0'; ; ) { @@ -2415,7 +2415,7 @@ initGlobalsNoDraw( GtkGameGlobals* globals, LaunchParams* params ) { memset( globals, 0, sizeof(*globals) ); - gi_copy( MPPARM(params->mpool) &globals->cGlobals.gi, ¶ms->pgi ); + gi_copy( MPPARM(params->mpool) globals->cGlobals.gi, ¶ms->pgi ); globals->cGlobals.params = params; globals->cGlobals.lastNTilesToUse = MAX_TRAY_TILES; @@ -2514,7 +2514,7 @@ initGlobals( GtkGameGlobals* globals, LaunchParams* params ) /* install scrollbar even if not needed -- since zooming can make it needed later */ GtkWidget* vscrollbar; - gint nRows = globals->cGlobals.gi.boardSize; + gint nRows = globals->cGlobals.gi->boardSize; globals->adjustment = (GtkAdjustment*) gtk_adjustment_new( 0, 0, nRows, 1, 2, nRows - globals->cGlobals.params->nHidden ); @@ -2597,8 +2597,8 @@ loadGameNoDraw( GtkGameGlobals* globals, LaunchParams* params, cGlobals->dict = makeDictForStream( cGlobals, stream ); } loaded = game_makeFromStream( MEMPOOL stream, &cGlobals->game, - &cGlobals->gi, - cGlobals->dict, &cGlobals->dicts, cGlobals->util, + cGlobals->gi, cGlobals->dict, + &cGlobals->dicts, cGlobals->util, (DrawCtx*)NULL, &cGlobals->cp, &procs ); if ( loaded ) { XP_LOGF( "%s: game loaded", __func__ ); @@ -2632,7 +2632,7 @@ makeNewGame( GtkGameGlobals* globals ) comms_getInitialAddr( &cGlobals->addr, relayName, relayPort ); } - CurGameInfo* gi = &cGlobals->gi; + CurGameInfo* gi = cGlobals->gi; XP_Bool success = newGameDialog( globals, gi, &cGlobals->addr, XP_TRUE, XP_FALSE ); if ( success && !!gi->dictName && !cGlobals->dict ) { diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index b0eb0eb91..cd46ffa3e 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -296,7 +296,7 @@ void catFinalScores( const CommonGlobals* cGlobals, XP_S16 quitter ) { XWStreamCtxt* stream; - XP_ASSERT( quitter < cGlobals->gi.nPlayers ); + XP_ASSERT( quitter < cGlobals->gi->nPlayers ); stream = mem_stream_make( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr, @@ -304,7 +304,7 @@ catFinalScores( const CommonGlobals* cGlobals, XP_S16 quitter ) if ( -1 != quitter ) { XP_UCHAR buf[128]; XP_SNPRINTF( buf, VSIZE(buf), "Player %s resigned\n", - cGlobals->gi.players[quitter].name ); + cGlobals->gi->players[quitter].name ); stream_catString( stream, buf ); } server_writeFinalScores( cGlobals->game.server, stream ); @@ -348,8 +348,7 @@ saveGame( CommonGlobals* cGlobals ) cGlobals, 0, onClose ); stream_open( outStream ); - game_saveToStream( &cGlobals->game, - &cGlobals->gi, + game_saveToStream( &cGlobals->game, cGlobals->gi, outStream, ++cGlobals->curSaveToken ); cGlobals->lastStreamSize = stream_getSize( outStream ); stream_destroy( outStream ); @@ -380,7 +379,7 @@ handle_messages_from( CommonGlobals* cGlobals, const TransportProcs* procs, #endif game_makeFromStream( MPPARM(cGlobals->util->mpool) stream, &cGlobals->game, - &cGlobals->gi, cGlobals->dict, + cGlobals->gi, cGlobals->dict, &cGlobals->dicts, cGlobals->util, NULL /*draw*/, &cGlobals->cp, procs ); @@ -441,7 +440,7 @@ read_pipe_then_close( CommonGlobals* cGlobals, const TransportProcs* procs ) #endif game_makeFromStream( MPPARM(cGlobals->util->mpool) stream, &cGlobals->game, - &cGlobals->gi, cGlobals->dict, + cGlobals->gi, cGlobals->dict, &cGlobals->dicts, cGlobals->util, NULL /*draw*/, &cGlobals->cp, procs ); @@ -1363,7 +1362,7 @@ linuxSetIsServer( CommonGlobals* cGlobals, XP_Bool isServer ) XP_LOGF( "%s(isServer=%d)", __func__, isServer ); DeviceRole newRole = isServer? SERVER_ISSERVER : SERVER_ISCLIENT; cGlobals->params->serverRole = newRole; - cGlobals->gi.serverRole = newRole; + cGlobals->gi->serverRole = newRole; } void @@ -1371,7 +1370,7 @@ linuxChangeRoles( CommonGlobals* cGlobals ) { ServerCtxt* server = cGlobals->game.server; server_reset( server, cGlobals->game.comms ); - if ( SERVER_ISCLIENT == cGlobals->gi.serverRole ) { + if ( SERVER_ISCLIENT == cGlobals->gi->serverRole ) { XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr, cGlobals, CHANNEL_NONE, sendOnClose ); @@ -1781,7 +1780,7 @@ initFromParams( CommonGlobals* cGlobals, LaunchParams* params ) { LOG_FUNC(); /* CurGameInfo */ - cGlobals->gi = params->pgi; + cGlobals->gi = ¶ms->pgi; /* addr */ CommsAddrRec* addr = &cGlobals->addr; @@ -1825,7 +1824,7 @@ setupUtil( CommonGlobals* cGlobals ) XW_UtilCtxt* util = calloc( 1, sizeof(*util) ); cGlobals->util = util; linux_util_vt_init( MPPARM(cGlobals->params->mpool) util ); - util->gameInfo = &cGlobals->gi; + util->gameInfo = cGlobals->gi; setupLinuxUtilCallbacks( util ); } @@ -1853,18 +1852,10 @@ initParams( LaunchParams* params ) static void freeParams( LaunchParams* params ) { - XP_U16 ii; - // linux_util_vt_destroy( params->util ); vtmgr_destroy( MPPARM(params->mpool) params->vtMgr ); - // XP_FREEP( params->mpool, ¶ms->pgi.dictName ); - for ( ii = 0; ii < params->nLocalPlayers; ++ii ) { - XP_FREEP( params->mpool, ¶ms->pgi.players[ii].name ); - } - + gi_disposePlayerInfo( MPPARM(params->mpool) ¶ms->pgi ); mpool_destroy( params->mpool ); - - // free( params->util ); } static int @@ -2013,7 +2004,7 @@ main( int argc, char** argv ) conType == COMMS_CONN_RELAY ); mainParams.connInfo.relay.invite = optarg; conType = COMMS_CONN_RELAY; - isServer = XP_TRUE; /* implicit */ + // isServer = XP_TRUE; /* implicit */ break; #endif case CMD_HOSTIP: @@ -2304,7 +2295,7 @@ main( int argc, char** argv ) } } - int result; + int result = 0; if ( g_str_has_suffix( argv[0], "dawg2dict" ) ) { result = dawg2dict( &mainParams, testDicts ); } else { @@ -2503,7 +2494,7 @@ main( int argc, char** argv ) freeParams( &mainParams ); } - XP_LOGF( "%s exiting main", argv[0] ); + XP_LOGF( "%s exiting main, returning %d", argv[0], result ); return result; } /* main */ diff --git a/xwords4/linux/linuxutl.c b/xwords4/linux/linuxutl.c index 786ba8eb6..3da49dde6 100644 --- a/xwords4/linux/linuxutl.c +++ b/xwords4/linux/linuxutl.c @@ -150,7 +150,7 @@ setSquareBonuses( const CommonGlobals* cGlobals ) { XP_U16 nBonuses; XWBonusType* bonuses = - bonusesFor( cGlobals->gi.boardSize, &nBonuses ); + bonusesFor( cGlobals->gi->boardSize, &nBonuses ); if ( !!bonuses ) { model_setSquareBonuses( cGlobals->game.model, bonuses, nBonuses ); } diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index 0b386fc89..a546f20fd 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -183,7 +183,7 @@ struct CommonGlobals { XW_UtilCtxt* util; XWGame game; - CurGameInfo gi; + CurGameInfo* gi; CommsAddrRec addr; DictionaryCtxt* dict; PlayerDicts dicts; From 4bde445c485dbd8819085afa0545ff25d86c2639 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 9 Jul 2013 18:19:22 -0700 Subject: [PATCH 116/116] fix so gtk mode works too by providing storage for CurGameInfo --- xwords4/linux/gtkboard.c | 1 + xwords4/linux/gtkboard.h | 1 + 2 files changed, 2 insertions(+) diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index b3e9b8c75..4c9f9308b 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -2415,6 +2415,7 @@ initGlobalsNoDraw( GtkGameGlobals* globals, LaunchParams* params ) { memset( globals, 0, sizeof(*globals) ); + globals->cGlobals.gi = &globals->gi; gi_copy( MPPARM(params->mpool) globals->cGlobals.gi, ¶ms->pgi ); globals->cGlobals.params = params; diff --git a/xwords4/linux/gtkboard.h b/xwords4/linux/gtkboard.h index 9fdfb8a62..927cbb677 100644 --- a/xwords4/linux/gtkboard.h +++ b/xwords4/linux/gtkboard.h @@ -86,6 +86,7 @@ typedef struct ClientStreamRec { typedef struct GtkGameGlobals { CommonGlobals cGlobals; + CurGameInfo gi; GtkWidget* window; GtkDrawCtx* draw; GtkAppGlobals* apg;