Merge branch 'android_branch' into gtk_multigame

Conflicts:
	xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java
	xwords4/common/comms.c
	xwords4/linux/cursesmain.c
	xwords4/linux/cursesmain.h
	xwords4/linux/gtkmain.c
	xwords4/linux/gtkmain.h
	xwords4/linux/linuxmain.c
	xwords4/linux/main.h
	xwords4/linux/scripts/discon_ok2.sh
	xwords4/relay/xwrelay.cpp

(Note: The curses app crashes on exit with mempool assertions, but that's a problem before the merge.)
This commit is contained in:
Eric House 2013-07-09 07:18:00 -07:00
commit d50c808f96
68 changed files with 1428 additions and 603 deletions

View file

@ -22,11 +22,21 @@
to come from a domain that you own or have control over. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.eehouse.android.xw4"
android:versionCode="52"
android:versionCode="55"
android:versionName="@string/app_version"
>
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="8" />
<!-- BE SURE TO MODIFY project.project AND the variable TARGET in
../scripts/setup_local_props.sh if targetSdkVersion changes!!!
-->
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="11" />
<supports-screens android:resizeable="true"
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true"
/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@ -38,6 +48,10 @@
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-feature android:name="android.hardware.telephony"
android:required = "false"
/>
<!-- GCM stuff -->
<permission android:name="org.eehouse.android.xw4.permission.C2D_MESSAGE"
android:protectionLevel="signature" />

View file

@ -486,14 +486,14 @@ and_draw_drawTrayDivider( DrawCtx* dctx, const XP_Rect* rect, CellFlags flags )
static void
and_draw_score_pendingScore( DrawCtx* dctx, const XP_Rect* rect,
XP_S16 score, XP_U16 playerNum,
CellFlags flags )
XP_S16 curTurn, CellFlags flags )
{
DRAW_CBK_HEADER( "score_pendingScore", "(Landroid/graphics/Rect;III)V" );
DRAW_CBK_HEADER( "score_pendingScore", "(Landroid/graphics/Rect;IIII)V" );
jobject jrect = makeJRect( draw, JCACHE_RECT0, rect );
(*env)->CallVoidMethod( env, draw->jdraw, mid,
jrect, score, playerNum, flags );
jrect, score, playerNum, curTurn, flags );
}
static void

View file

@ -3,11 +3,12 @@
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system use,
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Indicates whether an apk should be generated for each density.
split.density=false
# Project target.
target=android-8
target=Google Inc.:Google APIs:11

View file

@ -57,22 +57,10 @@
style="@style/toolbar_button"
android:src="@drawable/shuffle"
/>
<ImageButton android:id="@+id/zoom_button_horizontal"
style="@style/toolbar_button"
android:src="@drawable/zoom"
/>
<ImageButton android:id="@+id/undo_button_horizontal"
style="@style/toolbar_button"
android:src="@drawable/undo"
/>
<ImageButton android:id="@+id/values_button_horizontal"
style="@style/toolbar_button"
android:src="@drawable/values"
/>
<ImageButton android:id="@+id/flip_button_horizontal"
style="@style/toolbar_button"
android:src="@drawable/flip"
/>
<ImageButton android:id="@+id/dictlist_button_horizontal"
style="@style/toolbar_button"
android:src="@drawable/dicticon"
@ -81,6 +69,18 @@
style="@style/toolbar_button"
android:src="@drawable/stat_notify_chat"
/>
<ImageButton android:id="@+id/values_button_horizontal"
style="@style/toolbar_button"
android:src="@drawable/values"
/>
<ImageButton android:id="@+id/flip_button_horizontal"
style="@style/toolbar_button"
android:src="@drawable/flip"
/>
<ImageButton android:id="@+id/zoom_button_horizontal"
style="@style/toolbar_button"
android:src="@drawable/zoom"
/>
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>

View file

@ -5,7 +5,9 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingLeft="8dp"
android:paddingRight="8dp">
android:paddingRight="8dp"
android:focusableInTouchMode="true"
>
<TextView android:id="@+id/desc"
android:layout_height="wrap_content"

View file

@ -5,26 +5,28 @@
</style>
</head>
<body>
<b>Crosswords 4.4 beta 60 release</b>
<b>Crosswords 4.4 beta 63 release</b>
<h3>New with this release</h3>
<ul>
<li>Allow alternate spellings for tiles in the Find field in the
wordlist browser, e.g. 'a' for 'A' and 'L-L' for 'L&middot;L' (in
Catalan). The new wordlist format requires this upgrade, so I will
wait a few weeks before releasing new wordlists. </li>
<li>Reduce amount of network traffic required to get moves</li>
<li>Upgrade built-in English wordlists.</li>
<li>Make all wordlists available from wordlists button below board
(on Android 3.0 and above)</li>
<li>Don&apos;t run SMSService if play via SSM is disabled</li>
<li>Dim pending score counter (at right end of tray) when it's not
that player's turn or game's over</li>
<li>Move more frequently used game buttons to left where they're
more visible</li>
<li>Fix a few bugs</li>
<li>Fix bug with invites to SMS games where invitee is missing
wordlist</li>
</ul>
<h3>Next up</h3>
<ul>
<li>Improve communication with relay</li>
<li>Improve communication with relay, part 2</li>
</ul>
<p>(The full changelog

View file

@ -15,19 +15,19 @@
-->
<!-- These two messages appear at the top of the list of games
(unless the hide_intro preferences checkbox is checked.)-->
<string name="empty_games_list">Use o botão abaixo para criar um
jogo. Toque num jogo existente para continuá-lo ou dê um toque
<string name="empty_games_list">Use o botão abaixo para criar um novo
jogo. Selecione um jogo existente para continuá-lo ou dê um toque
longo para outras opções.</string>
<!-- -->
<string name="empty_games_list2">Você pode esconder essa mensagem
e os botões abaixo na seção Aparência das Configurações (acessadas
e os botões abaixo na seção Aparência das Configurações (acessada
através do botão menu do seu dispositivo.)</string>
<!-- Text of button at bottom of main games-list screen and of
menuitem in main games-list screen's menu. (The botton can
be hidden in the same way as the above text.) -->
<string name="button_new_game">Adicionar jogo</string>
<string name="button_new_group">Adicionar grupo</string>
<string name="button_new_game">Novo jogo</string>
<string name="button_new_group">Novo grupo</string>
<!-- When the game list is empty and the above messages and button
are hidden via preferences, this text is shown -->
@ -114,13 +114,13 @@
<string name="gamel_menu_checkmoves">Checar jogadas</string>
<!-- Text of progress indicator shown while check is being conducted -->
<string name="msgs_progress">Checando retransmissor por jogadas
<string name="msgs_progress">Procurando jogadas no servidor
etc...</string>
<!-- If you choose the above option and have no networked games
you get this error message -->
<string name="no_games_to_refresh">Nenhum jogo encontrado que conecta
pelo retransmissor.</string>
pelo servidor.</string>
<!-- Deletes all games on the device (after confirmation) -->
<string name="gamel_menu_delete_all">Excluir todos</string>
@ -166,7 +166,7 @@
<!-- If you try to copy a networked game you get this error
message. -->
<string name="no_copy_network">Jogos que já se conectaram ao
retransmissor não podem ser copiados. Use \"Novo a partir de\"
servidor não podem ser copiados. Use \"Novo a partir de\"
para uma cópia pronta para jogar com todas as mesmas
configurações.</string>
<!-- -->
@ -412,13 +412,13 @@
<!-- These are the three choices in the popup above whose text is
phonies_spinner_prompt -->
<!-- Don't care if words played are in the wordlist or not -->
<string name="phonies_ignore">Ignorar falsas</string>
<string name="phonies_ignore">Ignorar palavras falsas</string>
<!-- warn player when word played is not in the wordlist, but
allow him to play it. -->
<string name="phonies_warn">Avisar se for falsa</string>
<string name="phonies_warn">Avisar se a palavra for falsa</string>
<!-- Don't warn, but simply force to skip turn (give 0 points)
when user attempts to play word not in the wordlist. -->
<string name="phonies_disallow">Não permitir falsas</string>
<string name="phonies_disallow">Não permitir palavras falsas</string>
<!-- Shown when using the the Game configure screen to configure a
networked game and you try to make all players local. -->
@ -536,16 +536,16 @@
<!-- Bonus value hint that's displayed in gray text in the colored
bonus square. Double-letter -->
<string name="bonus_l2x_summary">2L</string>
<string name="bonus_l2x_summary">L2</string>
<!-- Bonus value hint that's displayed in gray text in the colored
bonus square. Double-word -->
<string name="bonus_w2x_summary">2P</string>
<string name="bonus_w2x_summary">P2</string>
<!-- Bonus value hint that's displayed in gray text in the colored
bonus square. Triple-letter -->
<string name="bonus_l3x_summary">3L</string>
<string name="bonus_l3x_summary">L3</string>
<!-- Bonus value hint that's displayed in gray text in the colored
bonus square. Triple-word -->
<string name="bonus_w3x_summary">3P</string>
<string name="bonus_w3x_summary">P3</string>
<!-- displayed when you long-tap a scoreboard entry and there's no
most recent score to show -->
@ -588,7 +588,7 @@
registered with the relay in this game. This should be seen
only once per game. -->
<string name="msg_relay_waiting">Dispositivo %1$d conectado ao
retransmissor na sala \"%2$s\". Esperando por %3$d jogador(es).</string>
servidor na sala \"%2$s\". Esperando por %3$d jogador(es).</string>
<!-- Text of "toast" shown when a game is notified by the relay
that all expected players have registered. At this point
@ -612,7 +612,7 @@
usando esse nome. Mude o nome da sua ou tente novamente mais
tarde.</string>
<!-- (I believe this can no longer occur) -->
<string name="msg_lost_other">O retransmissor perdeu contato com
<string name="msg_lost_other">O servidor perdeu contato com
outro dispositivo nesse jogos.</string>
<!-- When a game has been connected and the relay is notified that
@ -971,10 +971,10 @@
############################################################
-->
<!-- title of this sub-preference -->
<string name="prefs_colors">Cores individuais</string>
<string name="prefs_colors">Cores</string>
<!-- clarification of the above -->
<string name="prefs_colors_summary">Editar as cores usadas
no tabuleiro</string>
no jogo</string>
<!-- The remaining strings (down to the color edit dialog below)
are showns as the names of editable colors and as the the
@ -1003,7 +1003,7 @@
touching in order to guide the fat-fingered (most of us) in
operations that require accurately selecting a single square
on the board.-->
<string name="clr_crosshairs">Cor das linhas</string>
<string name="clr_crosshairs">Cor das linhas de posição</string>
<!-- color of the tiles' background -->
<string name="tile_back">Fundo das pedras</string>
@ -1103,7 +1103,7 @@
is distracting, presumably because they're using tablets with
large enough screens that they always know where they're
tapping. -->
<string name="hide_crosshairs">Desligar mira</string>
<string name="hide_crosshairs">Desligar linhas de posição</string>
<!-- explanation of the above -->
<string name="hide_crosshairs_summary">Não indicar visualmente
a casa do tabuleiro selecionada</string>
@ -1644,7 +1644,7 @@
-->
<!-- shown when user chooses the gamel_menu_checkmoves menu -->
<string name="not_again_sync">Essa ação procura no retransmissor
<string name="not_again_sync">Essa ação procura no servidor
por jogadas/mensagens pendentes para todos os jogos de rede e
marca aqueles com jogadas pendentes. Quando você abrir um jogo
marcado, ele fará a conexão e sincronização. (Numa versão futura
@ -1729,7 +1729,7 @@
in the game but not the last either. So it will only occur
for games with more than two devices, which are rare. -->
<string name="not_again_conndmid">Você conectou e entrou num
jogo no retransmissor. Você será notificado quando o(s)
jogo no servidor. Você será notificado quando o(s)
dispositivo(s) restantes(s) tiver(em) entrado na sua sala e
o jogo puder começar.</string>
@ -1738,7 +1738,7 @@
game to do so, i.e. the game is now complete and you should
expect play to begin. -->
<string name="not_again_conndall">Você conectou e entrou num jogo
no retransmissor, a sala agora está cheia. O dispositivo que criou
no servidor, a sala agora está cheia. O dispositivo que criou
a sala fará a distribuição inicial de pedras e o jogo pode
começar.</string>
@ -1853,7 +1853,7 @@
<string name="pick_url_titlef">Procurar %s em</string>
<!-- -->
<string name="board_menu_pass">Pular</string>
<string name="board_menu_pass">Pular vez</string>
<!-- -->
<string name="not_again_lookup">Esse botão permite que você
@ -1888,6 +1888,9 @@
<!-- -->
<string name="not_again_browse">Esse botão abre o navegador de
listas de palavras na lista do jogador atual.</string>
<string name="not_again_browseall">Esse botão abre o seletor de listas
de palavras na lista de sua escolha.</string>
<!-- -->
<string name="alert_empty_dictf">A lista de palavras %s só contém
informações de pedras. Não há palavras para mostrar.</string>
@ -2052,7 +2055,7 @@
<string name="game_list_tmp">Carregando resumo do jogo...</string>
<!-- -->
<string name="connstat_nonet">Este é um jogo independente. Não
<string name="connstat_nonet">Este é um jogo local. Não
há informações de rede.</string>
<!-- -->
<string name="connstat_net">Informações de rede para jogo conectado
@ -2073,7 +2076,7 @@
<!-- -->
<string name="connstat_noreceipt">Nenhuma mensagem foi recebida.</string>
<!-- -->
<string name="connstat_relay">internet/retransmissor</string>
<string name="connstat_relay">internet/servidor</string>
<!-- -->
<string name="connstat_sms">sms/texto</string>
<!-- -->
@ -2124,7 +2127,7 @@
<!-- Used in formatting final scores display -->
<string name="str_resigned">Desistiu</string>
<!-- Used in formatting final scores display -->
<string name="str_winner">Ganhador</string>
<string name="str_winner">Vencedor</string>
<!-- -->
<string name="inform_dict_diffversionf">Você e o hospedeiro
@ -2197,4 +2200,7 @@
<string name="square_tiles_summary">Mesmo se puderem ser mais altas</string>
<string name="change_groupf">Mover jogo %s</string>
<string name="show_wordlist_browser">Listas de palavras</string>
</resources>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_version">4.4 beta 60</string>
<string name="app_version">4.4 beta 63</string>
</resources>

View file

@ -96,6 +96,7 @@
<string name="key_notagain_trading">key_notagain_trading</string>
<string name="key_na_lookup">key_na_lookup</string>
<string name="key_na_browse">key_na_browse</string>
<string name="key_na_browseall">key_na_browseall</string>
<string name="key_na_values">key_na_values</string>
<string name="key_enable_debug">key_enable_debug</string>
<string name="key_download_path">key_download_path</string>

View file

@ -1797,9 +1797,7 @@
<!-- Another paragraph giving credit for work done other than by
Eric House and translators -->
<string name="about_credits">Toolbar icons by Sarah Chu; other
credits pending permission.</string>
<string name="about_credits">Toolbar icons by Sarah Chu.</string>
<!-- text of dialog showing the set of changes made since the last
release -->
@ -1860,6 +1858,9 @@
<string name="not_again_browse">This button opens the wordlist
browser on the current player\'s wordlist.</string>
<!-- -->
<string name="not_again_browseall">This button opens the wordlist
browser on the wordlist of your choice.</string>
<!-- -->
<string name="alert_empty_dictf">The wordlist %s contains only
tile information. There are no words to browse.</string>
@ -2149,7 +2150,7 @@
<string name="game_name_group_title">Name group</string>
<string name="cannot_delete_default_group">The group for new games
cannot be deleted."</string>
cannot be deleted.</string>
<string name="no_move_onegroup">Moving is impossible until there
is more than one group.</string>
@ -2164,4 +2165,6 @@
<string name="square_tiles_summary">Even if they can be taller</string>
<string name="change_groupf">Move game %s</string>
<string name="show_wordlist_browser">Wordlist browser</string>
</resources>

View file

@ -1,6 +1,6 @@
/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */
/*
* Copyright 2009 - 2012 by Eric House (xwords@eehouse.org). All
* Copyright 2009 - 2013 by Eric House (xwords@eehouse.org). All
* rights reserved.
*
* This program is free software; you can redistribute it and/or
@ -102,6 +102,7 @@ public class BoardActivity extends XWActivity
private static final int BT_PICK_ACTION = 17;
private static final int SMS_PICK_ACTION = 18;
private static final int SMS_CONFIG_ACTION = 19;
private static final int BUTTON_BROWSEALL_ACTION = 20;
private static final String DLG_TITLE = "DLG_TITLE";
private static final String DLG_TITLESTR = "DLG_TITLESTR";
@ -149,7 +150,7 @@ public class BoardActivity extends XWActivity
private boolean m_gameOver = false;
// call startActivityForResult synchronously
private Semaphore m_forResultWait = new Semaphore(0);
private Semaphore m_forResultWait = new Semaphore(0);
private int m_resultCode;
private Thread m_blockingThread;
@ -903,9 +904,15 @@ public class BoardActivity extends XWActivity
Utils.showToast( BoardActivity.this, m_toastStr );
m_toastStr = null;
break;
case BUTTON_BROWSEALL_ACTION:
case BUTTON_BROWSE_ACTION:
String dictName = m_gi.dictName( m_view.getCurPlayer() );
DictBrowseActivity.launch( this, dictName );
String curDict = m_gi.dictName( m_view.getCurPlayer() );
View button = m_toolbar.getViewFor( Toolbar.BUTTON_BROWSE_DICT );
if ( BUTTON_BROWSEALL_ACTION == id &&
DictsActivity.handleDictsPopup( this, button, curDict ) ) {
break;
}
DictBrowseActivity.launch( this, curDict );
break;
case PREV_HINT_ACTION:
cmd = JNICmd.CMD_PREV_HINT;
@ -1821,9 +1828,13 @@ public class BoardActivity extends XWActivity
private void populateToolbar()
{
m_toolbar.setListener( Toolbar.BUTTON_BROWSE_DICT,
R.string.not_again_browse,
R.string.key_na_browse,
BUTTON_BROWSE_ACTION );
R.string.not_again_browseall,
R.string.key_na_browseall,
BUTTON_BROWSEALL_ACTION );
m_toolbar.setLongClickListener( Toolbar.BUTTON_BROWSE_DICT,
R.string.not_again_browse,
R.string.key_na_browse,
BUTTON_BROWSE_ACTION );
m_toolbar.setListener( Toolbar.BUTTON_HINT_PREV,
R.string.not_again_hintprev,
R.string.key_notagain_hintprev,

View file

@ -50,6 +50,7 @@ public class BoardView extends View implements DrawCtx, BoardHandler,
private static Bitmap s_bitmap; // the board
private static final int IN_TRADE_ALPHA = 0x3FFFFFFF;
private static final int NOT_TURN_ALPHA = 0x3FFFFFFF;
private static final int PINCH_THRESHOLD = 40;
private static final int SCORE_HT_DROP = 2;
private static final boolean DEBUG_DRAWFRAMES = false;
@ -888,14 +889,19 @@ public class BoardView extends View implements DrawCtx, BoardHandler,
}
public void score_pendingScore( Rect rect, int score, int playerNum,
int flags )
int curTurn, int flags )
{
String text = score >= 0? String.format( "%d", score ) : "??";
int otherIndx = (0 == (flags & CELL_ISCURSOR))
? CommonPrefs.COLOR_BACKGRND : CommonPrefs.COLOR_FOCUS;
++rect.top;
fillRectOther( rect, otherIndx );
m_fillPaint.setColor( m_playerColors[playerNum] );
int playerColor = m_playerColors[playerNum];
if ( playerNum != curTurn ) {
playerColor &= NOT_TURN_ALPHA;
}
m_fillPaint.setColor( playerColor );
rect.bottom -= rect.height() / 2;
drawCentered( text, rect, null );

View file

@ -136,21 +136,21 @@ public class DictImportActivity extends XWActivity {
}
} // class DownloadFilesTask
@Override
protected void onCreate( Bundle savedInstanceState )
@Override
protected void onCreate( Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );
super.onCreate( savedInstanceState );
DownloadFilesTask dft = null;
requestWindowFeature( Window.FEATURE_LEFT_ICON );
setContentView( R.layout.import_dict );
getWindow().setFeatureDrawableResource( Window.FEATURE_LEFT_ICON,
requestWindowFeature( Window.FEATURE_LEFT_ICON );
setContentView( R.layout.import_dict );
getWindow().setFeatureDrawableResource( Window.FEATURE_LEFT_ICON,
R.drawable.icon48x48 );
ProgressBar progressBar = (ProgressBar)findViewById( R.id.progress_bar );
ProgressBar progressBar = (ProgressBar)findViewById( R.id.progress_bar );
Intent intent = getIntent();
Uri uri = intent.getData();
Intent intent = getIntent();
Uri uri = intent.getData();
if ( null == uri ) {
String url = intent.getStringExtra( APK_EXTRA );
boolean isApp = null != url;
@ -175,10 +175,10 @@ public class DictImportActivity extends XWActivity {
String msg = getString( R.string.downloading_dictf, showName );
TextView view = (TextView)findViewById( R.id.dwnld_message );
view.setText( msg );
dft.execute( uri );
}
}
}
private File saveToDownloads( InputStream is, String name )
{

View file

@ -36,6 +36,7 @@ import android.preference.PreferenceManager;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
@ -44,9 +45,12 @@ import android.widget.Button;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.ExpandableListView;
import android.widget.PopupMenu;
import android.widget.TextView;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import junit.framework.Assert;
import org.eehouse.android.xw4.DictUtils.DictAndLoc;
@ -59,6 +63,11 @@ public class DictsActivity extends XWExpandableListActivity
MountEventReceiver.SDCardNotifiee, DlgDelegate.DlgClickNotify,
DictImportActivity.DownloadFinishedListener {
private static interface SafePopup {
public void doPopup( Context context, View button, String curDict );
}
private static SafePopup s_safePopup = null;
private static final String DICT_DOLAUNCH = "do_launch";
private static final String DICT_LANG_EXTRA = "use_lang";
private static final String DICT_NAME_EXTRA = "use_dict";
@ -76,6 +85,11 @@ public class DictsActivity extends XWExpandableListActivity
private static final int MOVE_DICT = DlgDelegate.DIALOG_LAST + 1;
private static final int SET_DEFAULT = DlgDelegate.DIALOG_LAST + 2;
private static final int DICT_OR_DECLINE = DlgDelegate.DIALOG_LAST + 3;
// I can't provide a subclass of MenuItem to hold DictAndLoc, so
// settle for a hash on the side.
private static HashMap<MenuItem, DictAndLoc> s_itemData;
private int m_lang = 0;
private String[] m_langs;
private String m_name = null;
@ -779,5 +793,68 @@ public class DictsActivity extends XWExpandableListActivity
}
}
private static class SafePopupImpl implements SafePopup {
public void doPopup( final Context context, View button,
String curDict ) {
MenuItem.OnMenuItemClickListener listener =
new MenuItem.OnMenuItemClickListener() {
public boolean onMenuItemClick( MenuItem item )
{
DictAndLoc dal = s_itemData.get( item );
s_itemData = null;
if ( null == dal ) {
DictsActivity.start( context );
} else {
DictBrowseActivity.launch( context, dal.name,
dal.loc );
}
return true;
}
};
s_itemData = new HashMap<MenuItem, DictAndLoc>();
PopupMenu popup = new PopupMenu( context, button );
Menu menu = popup.getMenu();
menu.add( R.string.show_wordlist_browser )
.setOnMenuItemClickListener( listener );
// Add at top but save until have dal info
MenuItem curItem = menu.add( curDict );
DictAndLoc[] dals = DictUtils.dictList( context );
for ( DictAndLoc dal : dals ) {
MenuItem item = dal.name.equals(curDict)
? curItem : menu.add( dal.name );
item.setOnMenuItemClickListener( listener );
s_itemData.put( item, dal );
}
popup.show();
}
}
public static boolean handleDictsPopup( Context context, View button,
String curDict )
{
if ( null == s_safePopup ) {
int sdkVersion = Integer.valueOf( android.os.Build.VERSION.SDK );
if ( 11 <= sdkVersion ) {
s_safePopup = new SafePopupImpl();
}
}
boolean canHandle = null != s_safePopup;
if ( canHandle ) {
s_safePopup.doPopup( context, button, curDict );
}
return canHandle;
}
public static void start( Context context )
{
Intent intent = new Intent( context, DictsActivity.class );
context.startActivity( intent );
}
}

View file

@ -102,7 +102,7 @@ public class DlgDelegate {
public Dialog onCreateDialog( int id )
{
DbgUtils.logf("onCreateDialog(id=%d)", id );
// DbgUtils.logf("onCreateDialog(id=%d)", id );
Dialog dialog = null;
DlgState state = findForID( id );
switch( id ) {
@ -138,11 +138,6 @@ public class DlgDelegate {
// Assert.assertNull( m_dlgStates );
DlgState state = new DlgState( DIALOG_OKONLY, msg, callbackID );
addState( state );
// m_msg = msg;
// if ( 0 != callbackID ) {
// Assert.assertTrue( 0 == m_cbckID );
// m_cbckID = callbackID;
// }
m_activity.showDialog( DIALOG_OKONLY );
}
@ -192,18 +187,10 @@ public class DlgDelegate {
public void showConfirmThen( String msg, int posButton, int callbackID )
{
// FIX ME!! Need to store data per message rather than have
// assertions failing or messages dropped.
if ( false /*0 != m_cbckID*/ ) {
// DbgUtils.logf( "showConfirmThen: busy with another message; "
// + "dropping \"%s\" in favor of \"%s\"",
// msg, m_msg );
} else {
DlgState state = new DlgState( CONFIRM_THEN, msg, posButton,
callbackID, 0 );
addState( state );
m_activity.showDialog( CONFIRM_THEN );
}
DlgState state = new DlgState( CONFIRM_THEN, msg, posButton,
callbackID, 0 );
addState( state );
m_activity.showDialog( CONFIRM_THEN );
}
public void showEmailOrSMSThen( final int callbackID )
@ -454,7 +441,7 @@ public class DlgDelegate {
private DlgState findForID( int id )
{
DlgState state = m_dlgStates.get( id );
DbgUtils.logf( "findForID(%d)=>%H", id, state );
// DbgUtils.logf( "findForID(%d)=>%H", id, state );
return state;
}
@ -464,8 +451,8 @@ public class DlgDelegate {
Assert.assertNotNull( state );
// Assert.assertTrue( state == m_dlgStates.get( state.m_id ) );
m_dlgStates.remove( state.m_id );
DbgUtils.logf( "dropState: active dialogs now %d from %d ",
m_dlgStates.size(), nDlgs );
// DbgUtils.logf( "dropState: active dialogs now %d from %d ",
// m_dlgStates.size(), nDlgs );
}
private void addState( DlgState state )

View file

@ -37,48 +37,48 @@ public class FirstRunDialog {
{
String page = null;
InputStream inputStream = null;
try {
try {
inputStream = context.getResources()
.openRawResource(R.raw.changes);
final char[] buf = new char[0x1000];
StringBuilder stringBuilder = new StringBuilder();
Reader reader = new InputStreamReader( inputStream, "UTF-8" );
int nRead;
do {
final char[] buf = new char[0x1000];
StringBuilder stringBuilder = new StringBuilder();
Reader reader = new InputStreamReader( inputStream, "UTF-8" );
int nRead;
do {
nRead = reader.read( buf, 0, buf.length );
if ( nRead > 0 ) {
stringBuilder.append( buf, 0, nRead );
}
} while ( nRead >= 0 );
page = stringBuilder.toString();
}
catch ( IOException ioe ) {
DbgUtils.loge( ioe );
}
finally {
} while ( nRead >= 0 );
page = stringBuilder.toString();
}
catch ( IOException ioe ) {
DbgUtils.loge( ioe );
}
finally {
// could just catch NPE....
if ( null != inputStream ) {
try {
inputStream.close();
} catch ( IOException ioe ) {
if ( null != inputStream ) {
try {
inputStream.close();
} catch ( IOException ioe ) {
DbgUtils.loge( ioe );
}
}
}
}
}
}
// This won't support e.g mailto refs. Probably want to
// launch the browser with an intent eventually.
WebView view = new WebView( context );
view.loadData( page, "text/html", "utf-8" );
WebView view = new WebView( context );
view.loadData( page, "text/html", "utf-8" );
AlertDialog dialog = new AlertDialog.Builder( context )
AlertDialog dialog = new AlertDialog.Builder( context )
.setIcon(android.R.drawable.ic_menu_info_details)
.setTitle( R.string.changes_title )
.setView( view )
.setPositiveButton( R.string.button_ok, null)
.create();
dialog.show();
dialog.show();
}
}

View file

@ -34,7 +34,7 @@ public class GCMIntentService extends GCMBaseIntentService {
super( GCMConsts.SENDER_ID );
}
@Override
@Override
protected void onError( Context context, String error )
{
DbgUtils.logf("GCMIntentService.onError(%s)", error );

View file

@ -189,12 +189,12 @@ public class GameListItem extends LinearLayout
break;
}
String name = GameUtils.getName( m_context, m_rowid );
if ( null != value ) {
String name = GameUtils.getName( m_context, m_rowid );
value = m_context.getString( R.string.str_game_namef,
name, value );
} else {
value = GameUtils.getName( m_context, m_rowid );
value = name;
}
view.setText( value );

View file

@ -282,7 +282,7 @@ public class GameUtils {
}
public static long saveNewGame( Context context, int gamePtr,
CurGameInfo gi )
CurGameInfo gi )
{
byte[] stream = XwJNI.game_saveToStream( gamePtr, gi );
GameLock lock = DBUtils.saveNewGame( context, stream );

View file

@ -102,6 +102,7 @@ public class GamesList extends XWExpandableListActivity
private String m_nameField;
private NetLaunchInfo m_netLaunchInfo;
private GameNamer m_namer;
private boolean m_gameLaunched = false;
@Override
protected Dialog onCreateDialog( int id )
@ -135,7 +136,7 @@ public class GamesList extends XWExpandableListActivity
String message;
String langName =
DictLangCache.getLangName( this, m_missingDictLang );
String gameName = GameUtils.getName( this, m_rowid );
String gameName = GameUtils.getName( this, m_missingDictRowId );
if ( WARN_NODICT == id ) {
message = getString( R.string.no_dictf,
gameName, langName );
@ -385,6 +386,7 @@ public class GamesList extends XWExpandableListActivity
protected void onNewIntent( Intent intent )
{
super.onNewIntent( intent );
m_gameLaunched = false;
Assert.assertNotNull( intent );
invalRelayIDs( intent.getStringArrayExtra( RELAYIDS_EXTRA ) );
invalRowID( intent.getLongExtra( ROWID_EXTRA, -1 ) );
@ -464,6 +466,7 @@ public class GamesList extends XWExpandableListActivity
super.onWindowFocusChanged( hasFocus );
if ( hasFocus ) {
updateField();
m_gameLaunched = false;
}
}
@ -487,21 +490,23 @@ public class GamesList extends XWExpandableListActivity
// We need a way to let the user get back to the basic-config
// dialog in case it was dismissed. That way it to check for
// an empty room name.
if ( summary.conType == CommsAddrRec.CommsConnType.COMMS_CONN_RELAY
&& summary.roomName.length() == 0 ) {
// If it's unconfigured and of the type RelayGameActivity
// can handle send it there, otherwise use the full-on
// config.
Class clazz;
if ( RelayGameActivity.isSimpleGame( summary ) ) {
clazz = RelayGameActivity.class;
if ( !m_gameLaunched ) {
if ( summary.conType == CommsAddrRec.CommsConnType.COMMS_CONN_RELAY
&& summary.roomName.length() == 0 ) {
// If it's unconfigured and of the type RelayGameActivity
// can handle send it there, otherwise use the full-on
// config.
Class clazz;
if ( RelayGameActivity.isSimpleGame( summary ) ) {
clazz = RelayGameActivity.class;
} else {
clazz = GameConfig.class;
}
GameUtils.doConfig( this, rowid, clazz );
} else {
clazz = GameConfig.class;
}
GameUtils.doConfig( this, rowid, clazz );
} else {
if ( checkWarnNoDict( rowid ) ) {
launchGame( rowid );
if ( checkWarnNoDict( rowid ) ) {
launchGame( rowid );
}
}
}
}
@ -666,7 +671,6 @@ public class GamesList extends XWExpandableListActivity
public boolean onOptionsItemSelected( MenuItem item )
{
boolean handled = true;
Intent intent;
switch (item.getItemId()) {
case R.id.gamel_menu_newgame:
@ -678,8 +682,7 @@ public class GamesList extends XWExpandableListActivity
break;
case R.id.gamel_menu_dicts:
intent = new Intent( this, DictsActivity.class );
startActivity( intent );
DictsActivity.start( this );
break;
case R.id.gamel_menu_checkmoves:
@ -1089,7 +1092,10 @@ public class GamesList extends XWExpandableListActivity
private void launchGame( long rowid, boolean invited )
{
GameUtils.launchGame( this, rowid, invited );
if ( !m_gameLaunched ) {
m_gameLaunched = true;
GameUtils.launchGame( this, rowid, invited );
}
}
private void launchGame( long rowid )

View file

@ -92,15 +92,29 @@ public class Toolbar {
}
}
public void setListener( int index, View.OnClickListener listener )
public ImageButton getViewFor( int index )
{
TBButtonInfo info = s_buttonInfo[index];
ImageButton button = (ImageButton)m_activity.findViewById( info.m_id );
return button;
}
private void setListener( int index, View.OnClickListener listener )
{
ImageButton button = getViewFor( index );
if ( null != button ) {
button.setOnClickListener( listener );
}
}
private void setLongClickListener( int index, View.OnLongClickListener listener )
{
ImageButton button = getViewFor( index );
if ( null != button ) {
button.setOnLongClickListener( listener );
}
}
public void setListener( int index, final int msgID, final int prefsKey,
final int callback )
{
@ -112,6 +126,18 @@ public class Toolbar {
setListener( index, listener );
}
public void setLongClickListener( int index, final int msgID, final int prefsKey,
final int callback )
{
View.OnLongClickListener listener = new View.OnLongClickListener() {
public boolean onLongClick( View view ) {
m_activity.showNotAgainDlgThen( msgID, prefsKey, callback );
return true;
}
};
setLongClickListener( index, listener );
}
public void update( int index, boolean enable )
{
TBButtonInfo info = s_buttonInfo[index];

View file

@ -165,7 +165,14 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
String url = String.format( "%s/%s",
XWPrefs.getDefaultUpdateUrl( context ),
proc );
return new HttpPost( url );
HttpPost result;
try {
result = new HttpPost( url );
} catch ( IllegalArgumentException iae ) {
DbgUtils.loge( iae );
result = null;
}
return result;
}
private static String runPost( HttpPost post, JSONObject params )
@ -244,7 +251,10 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
@Override protected String doInBackground( Void... unused )
{
HttpPost post = makePost( m_context, "getUpdates" );
String json = runPost( post, m_params );
String json = null;
if ( null != post ) {
json = runPost( post, m_params );
}
return json;
}

View file

@ -60,7 +60,7 @@ public class Utils {
public static final int TURN_COLOR = 0x7F00FF00;
private static final String DB_PATH = "XW_GAMES";
private static final String HIDDEN_PREFS = "xwprefs_hidden";
private static final String HIDDEN_PREFS = "xwprefs_hidden";
private static final String SHOWN_VERSION_KEY = "SHOWN_VERSION_KEY";
private static Boolean s_isFirstBootThisVersion = null;

View file

@ -33,7 +33,7 @@ public class XWApp extends Application {
public static final boolean GCMSUPPORTED = true;
public static final boolean ATTACH_SUPPORTED = true;
public static final boolean REMATCH_SUPPORTED = false;
public static final boolean DEBUG = true;
public static final boolean DEBUG = false;
public static final boolean DEBUG_LOCKS = false && DEBUG;
public static final boolean DEBUG_EXP_TIMERS = false && DEBUG;

View file

@ -68,7 +68,8 @@ public interface DrawCtx {
int flags );
void drawTileBack( Rect rect, int flags );
void drawTrayDivider( Rect rect, int flags );
void score_pendingScore( Rect rect, int score, int playerNum, int flags );
void score_pendingScore( Rect rect, int score, int playerNum, int curTurn,
int flags );
public static final int BONUS_NONE = 0;
public static final int BONUS_DOUBLE_LETTER = 1;

View file

@ -8,12 +8,6 @@ usage () {
exit 0
}
getSDK() {
LINE=$(grep 'android:minSdkVersion' ./AndroidManifest.xml)
SDK=$(echo $LINE | sed 's/^.*targetSdkVersion=\"\([0-9]*\)\".*$/\1/')
echo $SDK
}
TAG=""
BRANCH=""
VARIANT="XWords4"
@ -67,7 +61,7 @@ git clone $SRCDIR BUILD
cd BUILD
git checkout ${TAG}${BRANCH}
cd ./xwords4/android/${VARIANT}
../scripts/setup_local_props.sh --target android-$(getSDK)
../scripts/setup_local_props.sh
../scripts/arelease.sh --variant ${VARIANT}
mkdir -p /tmp/releases_${VARIANT}
cp *.apk /tmp/releases_${VARIANT}

View file

@ -2,7 +2,7 @@
set -u -e
TARGET="android-7"
TARGET="Google Inc.:Google APIs:11"
usage() {
echo "usage: $0 [--target TARGET]"
@ -24,9 +24,8 @@ while [ $# -ge 1 ]; do
shift
done
# create local.properties for 1.6 sdk (target id 4). Use 'android
# list targets' to get the full set.
android update project --path . --target $TARGET
# create local.properties
android update project --path . --target "$TARGET"
echo "local.properties looks like this:"
echo ""

View file

@ -197,6 +197,7 @@ static void putDevID( const CommsCtxt* comms, XWStreamCtxt* stream );
# endif
# ifdef DEBUG
static const char* relayCmdToStr( XWRELAY_Cmd cmd );
static void printQueue( const CommsCtxt* comms );
# endif
#endif
#if defined RELAY_HEARTBEAT || defined COMMS_HEARTBEAT
@ -287,7 +288,9 @@ static void
init_relay( CommsCtxt* comms, XP_U16 nPlayersHere, XP_U16 nPlayersTotal )
{
comms->r.myHostID = comms->isServer? HOST_ID_SERVER: HOST_ID_NONE;
XP_LOGF( "%s: set hostid: %x", __func__, comms->r.myHostID );
if ( HOST_ID_NONE != comms->r.myHostID ) {
XP_LOGF( "%s: set hostid: %x", __func__, comms->r.myHostID );
}
set_relay_state( comms, COMMS_RELAYSTATE_UNCONNECTED );
comms->r.nPlayersHere = nPlayersHere;
comms->r.nPlayersTotal = nPlayersTotal;
@ -964,6 +967,7 @@ static MsgQueueElem*
makeElemWithID( CommsCtxt* comms, MsgID msgID, AddressRecord* rec,
XP_PlayerAddr channelNo, XWStreamCtxt* stream )
{
XP_LOGF( "%s(channelNo=%x)", __func__, channelNo );
XP_U16 headerLen;
XP_U16 streamSize = NULL == stream? 0 : stream_getSize( stream );
MsgID lastMsgSaved = (!!rec)? rec->lastMsgSaved : 0;
@ -1016,7 +1020,7 @@ makeElemWithID( CommsCtxt* comms, MsgID msgID, AddressRecord* rec,
XP_U16
comms_getChannelSeed( CommsCtxt* comms )
{
while ( comms->channelSeed == 0 ) {
while ( 0 == (comms->channelSeed & ~CHANNEL_MASK) ) {
comms->channelSeed = XP_RANDOM();
XP_LOGF( "%s: channelSeed: %.4X", __func__, comms->channelSeed );
}
@ -1073,10 +1077,10 @@ addToQueue( CommsCtxt* comms, MsgQueueElem* newMsgElem )
XP_ASSERT( comms->queueLen > 0 );
}
++comms->queueLen;
XP_LOGF( "%s: queueLen now %d after channelNo: %d; msgID: " XP_LD
"; len: %d", __func__, comms->queueLen,
newMsgElem->channelNo & CHANNEL_MASK, newMsgElem->msgID,
newMsgElem->len );
XP_ASSERT( comms->queueLen <= 128 ); /* reasonable limit in testing */
#ifdef DEBUG
printQueue( comms );
#endif
} /* addToQueue */
#ifdef DEBUG
@ -1088,7 +1092,7 @@ printQueue( const CommsCtxt* comms )
for ( elem = comms->msgQueueHead, ii = 0; ii < comms->queueLen;
elem = elem->next, ++ii ) {
XP_STATUSF( "\t%d: channel: %x; msgID=" XP_LD
XP_LOGF( "\t%d: channel: %x; msgID=" XP_LD
#ifdef COMMS_CHECKSUM
"; check=%s"
#endif
@ -1139,8 +1143,8 @@ freeElem( const CommsCtxt* XP_UNUSED_DBG(comms), MsgQueueElem* elem )
static void
removeFromQueue( CommsCtxt* comms, XP_PlayerAddr channelNo, MsgID msgID )
{
XP_STATUSF( "%s: remove msgs <= " XP_LD " for channel %x (queueLen: %d)",
__func__, msgID, channelNo, comms->queueLen );
XP_LOGF( "%s: remove msgs <= " XP_LD " for channel %x (queueLen: %d)",
__func__, msgID, channelNo, comms->queueLen );
if ( (channelNo == 0) || !!getRecordFor( comms, NULL, channelNo,
XP_FALSE ) ) {
@ -1176,7 +1180,7 @@ removeFromQueue( CommsCtxt* comms, XP_PlayerAddr channelNo, MsgID msgID )
}
}
XP_STATUSF( "%s: queueLen now %d", __func__, comms->queueLen );
XP_LOGF( "%s: queueLen now %d", __func__, comms->queueLen );
#ifdef DEBUG
assertQueueOk( comms );
@ -1191,6 +1195,7 @@ gameID( const CommsCtxt* comms )
if ( 0 == gameID ) {
gameID = comms->util->gameInfo->gameID;
}
// XP_ASSERT( 0 != gameID );
if ( 0 == gameID ) {
XP_LOGF( "%s: gameID STILL 0", __func__ );
@ -1199,21 +1204,15 @@ gameID( const CommsCtxt* comms )
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. */
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;
}
return gameID;
}
@ -1228,6 +1227,11 @@ sendMsg( CommsCtxt* comms, MsgQueueElem* elem )
channelNo = elem->channelNo;
#ifdef COMMS_CHECKSUM
XP_LOGF( "%s: sending message of len %d with sum %s", __func__, elem->len,
elem->checksum );
#endif
if ( 0 ) {
#ifdef XWFEATURE_RELAY
} else if ( conType == COMMS_CONN_RELAY ) {
@ -1317,25 +1321,27 @@ comms_resendAll( CommsCtxt* comms, XP_Bool force )
void
comms_ackAny( CommsCtxt* comms )
{
if ( CONN_ID_NONE == comms->connID ) {
XP_LOGF( "%s: doing nothing because connID still unset", __func__ );
} else {
#ifdef DEBUG
XP_Bool noneSent = XP_TRUE;
XP_U16 nSent = 0;
#endif
AddressRecord* rec;
for ( rec = comms->recs; !!rec; rec = rec->next ) {
if ( rec->lastMsgAckd < rec->lastMsgRcd ) {
AddressRecord* rec;
for ( rec = comms->recs; !!rec; rec = rec->next ) {
if ( rec->lastMsgAckd < rec->lastMsgRcd ) {
#ifdef DEBUG
noneSent = XP_FALSE;
++nSent;
#endif
XP_LOGF( "%s: channel %x; %ld < %ld: rec needs ack", __func__,
rec->channelNo, rec->lastMsgAckd, rec->lastMsgRcd );
sendEmptyMsg( comms, rec );
XP_LOGF( "%s: channel %x; %ld < %ld: rec needs ack", __func__,
rec->channelNo, rec->lastMsgAckd, rec->lastMsgRcd );
sendEmptyMsg( comms, rec );
}
}
}
#ifdef DEBUG
if ( noneSent ) {
XP_LOGF( "%s: nothing to send", __func__ );
}
XP_LOGF( "%s: sent for %d channels", __func__, nSent );
#endif
}
}
#endif
@ -1410,7 +1416,8 @@ got_connect_cmd( CommsCtxt* comms, XWStreamCtxt* stream,
__func__, comms->r.connName, connName );
}
XP_MEMCPY( comms->r.connName, connName, sizeof(comms->r.connName) );
XP_LOGF( "%s: connName: \"%s\"", __func__, connName );
XP_LOGF( "%s: connName: \"%s\" (reconnect=%d)", __func__, connName,
reconnected );
}
#else
stringFromStreamHere( stream, comms->r.connName,
@ -1460,8 +1467,11 @@ relayPreProcess( CommsCtxt* comms, XWStreamCtxt* stream, XWHostID* senderID )
case XWRELAY_ALLHERE:
srcID = (XWHostID)stream_getU8( stream );
XP_ASSERT( comms->r.myHostID == HOST_ID_NONE
|| comms->r.myHostID == srcID );
if ( comms->r.myHostID != HOST_ID_NONE
&& comms->r.myHostID != srcID ) {
XP_LOGF( "%s: changing hostid from %d to %d", __func__,
comms->r.myHostID, srcID );
}
if ( 0 == comms->r.cookieID ) {
XP_LOGF( "%s: cookieID still 0; background send?",
@ -1649,7 +1659,7 @@ preProcess( CommsCtxt* comms, XWStreamCtxt* stream,
static AddressRecord*
getRecordFor( CommsCtxt* comms, const CommsAddrRec* addr,
XP_PlayerAddr channelNo, XP_Bool maskChannel )
const XP_PlayerAddr channelNo, XP_Bool maskChannel )
{
CommsConnType conType;
AddressRecord* rec;
@ -1703,6 +1713,8 @@ getRecordFor( CommsCtxt* comms, const CommsAddrRec* addr,
break;
}
}
XP_LOGF( "%s(channelNo=%x, maskChannel=%s) => %p", __func__,
channelNo, maskChannel? "true":"false", rec );
return rec;
} /* getRecordFor */
@ -1756,7 +1768,7 @@ validateInitialMessage( CommsCtxt* comms,
if ( addRec ) {
if ( comms->isServer ) {
XP_LOGF( "%s: looking at channelNo: %x", __func__, *channelNo );
XP_ASSERT( (*channelNo && CHANNEL_MASK) == 0 );
XP_ASSERT( (*channelNo & CHANNEL_MASK) == 0 );
*channelNo |= ++comms->nextChannelNo;
XP_ASSERT( comms->nextChannelNo <= CHANNEL_MASK );
}
@ -1778,12 +1790,22 @@ validateInitialMessage( CommsCtxt* comms,
} else {
if ( comms->isServer ) {
XP_ASSERT( (*channelNo & CHANNEL_MASK) == 0 );
*channelNo |= ++comms->nextChannelNo;
XP_ASSERT( comms->nextChannelNo <= CHANNEL_MASK );
if ( 0 == (*channelNo & CHANNEL_MASK) ) {
*channelNo |= ++comms->nextChannelNo;
XP_ASSERT( comms->nextChannelNo <= CHANNEL_MASK );
} else {
/* Why do I sometimes see these in the middle of a game
with lots of messages already sent? connID of 0 should
only happen at the start! */
XP_LOGF( "%s: dropping msg because channel already set",
__func__ );
goto errExit;
}
}
rec = rememberChannelAddress( comms, *channelNo, senderID, addr );
}
}
errExit:
LOG_RETURNF( XP_P, rec );
return rec;
} /* validateInitialMessage */
@ -1829,6 +1851,9 @@ comms_checkIncomingStream( CommsCtxt* comms, XWStreamCtxt* stream,
XP_Bool usingRelay = XP_FALSE;
XP_ASSERT( retAddr == NULL || comms->addr.conType == retAddr->conType );
#ifdef COMMS_CHECKSUM
XP_U16 initialLen = stream_getSize( stream );
#endif
if ( !preProcess( comms, stream, &usingRelay, &senderID ) ) {
XP_U32 connID;
@ -1836,6 +1861,17 @@ comms_checkIncomingStream( CommsCtxt* comms, XWStreamCtxt* stream,
MsgID msgID;
MsgID lastMsgRcd;
#ifdef COMMS_CHECKSUM
{
XP_U16 len = stream_getSize( stream );
// stream_getPtr pts at base, but sum excludes relay header
const XP_U8* ptr = initialLen - len + stream_getPtr( stream );
gchar* sum = g_compute_checksum_for_data( G_CHECKSUM_MD5, ptr, len );
XP_LOGF( "%s: got message of len %d with sum %s", __func__, len, sum );
g_free( sum );
}
#endif
/* reject too-small message */
if ( stream_getSize( stream ) >=
(sizeof(connID) + sizeof(channelNo)
@ -1861,6 +1897,8 @@ comms_checkIncomingStream( CommsCtxt* comms, XWStreamCtxt* stream,
} else if ( comms->connID == connID ) {
rec = validateChannelMessage( comms, retAddr, channelNo, msgID,
lastMsgRcd );
} else {
XP_LOGF( "%s: unexpected connID; dropping message", __func__ );
}
messageValid = (NULL != rec)

View file

@ -165,6 +165,7 @@ typedef struct DrawCtxVTable {
const XP_Rect* rect,
XP_S16 score,
XP_U16 playerNum,
XP_S16 curTurn,
CellFlags flags );
void DRAW_VTABLE_NAME(drawTimer) ( DrawCtx* dctx, const XP_Rect* rect,
@ -295,8 +296,8 @@ struct DrawCtx {
# define draw_score_drawPlayer(dc, ri, ro, gp, dsi) \
CALL_DRAW_NAME4(score_drawPlayer,(dc),(ri),(ro),(gp),(dsi))
#endif
#define draw_score_pendingScore(dc, r, s, p, f ) \
CALL_DRAW_NAME4(score_pendingScore,(dc), (r), (s), (p), (f))
#define draw_score_pendingScore(dc, r, s, p, t, f ) \
CALL_DRAW_NAME5(score_pendingScore,(dc), (r), (s), (p), (t), (f))
#define draw_drawTimer( dc, r, plyr, sec ) \
CALL_DRAW_NAME3(drawTimer,(dc),(r),(plyr),(sec))
#define draw_drawCell( dc, rect, txt, bmap, t, v,o, bon, hi, f ) \

View file

@ -169,7 +169,6 @@ getStateStr( XW_State st )
CASESTR(XWSTATE_NONE);
CASESTR(XWSTATE_BEGIN);
CASESTR(XWSTATE_NEED_SHOWSCORE);
CASESTR(XWSTATE_WAITING_ALL_REG);
CASESTR(XWSTATE_RECEIVED_ALL_REG);
CASESTR(XWSTATE_NEEDSEND_BADWORD_INFO);
CASESTR(XWSTATE_MOVE_CONFIRM_WAIT);
@ -1171,13 +1170,17 @@ registerRemotePlayer( ServerCtxt* server, XWStreamCtxt* stream )
RemoteAddress* addr;
addr = &server->nv.addresses[server->nv.nDevices];
deviceIndex = server->nv.nDevices++;
XP_ASSERT( channelNo != 0 );
addr->channelNo = channelNo;
XP_LOGF( "%s: set channelNo to %x for device %d", __func__,
channelNo, server->nv.nDevices );
deviceIndex = server->nv.nDevices++;
#ifdef STREAM_VERS_BIGBOARD
addr->streamVersion = STREAM_SAVE_PREVWORDS;
#endif
} else {
XP_LOGF( "%s: deviceIndex already set", __func__ );
}
player->deviceIndex = deviceIndex;
@ -1272,11 +1275,12 @@ client_readInitialMessage( ServerCtxt* server, XWStreamCtxt* stream )
channelNo = stream_getAddress( stream );
XP_ASSERT( channelNo != 0 );
server->nv.addresses[0].channelNo = channelNo;
XP_LOGF( "%s: assigning channelNo %x for 0", __func__, channelNo );
model_setSize( model, nCols );
nPlayers = localGI.nPlayers;
XP_STATUSF( "reading in %d players", localGI.nPlayers );
XP_LOGF( "%s: reading in %d players", __func__, localGI.nPlayers );
gi_disposePlayerInfo( MPPARM(server->mpool) &localGI );
@ -3037,10 +3041,9 @@ server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream )
{
ScoresArray scores;
ScoresArray tilePenalties;
XP_U16 place, nPlayers;
XP_U16 place;
XP_S16 quitter = server->nv.quitter;
XP_Bool quitterDone = XP_FALSE;
XP_USE(quitter);
ModelCtxt* model = server->vol.model;
const XP_UCHAR* addString = util_getUserString( server->vol.util,
STRD_REMAINING_TILES_ADD );
@ -3048,18 +3051,18 @@ server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream )
STRD_UNUSED_TILES_SUB );
XP_UCHAR* timeStr;
CurGameInfo* gi = server->vol.gi;
const XP_U16 nPlayers = gi->nPlayers;
XP_ASSERT( server->nv.gameState == XWSTATE_GAMEOVER );
model_figureFinalScores( model, &scores, &tilePenalties );
nPlayers = gi->nPlayers;
XP_S16 winningScore = IMPOSSIBLY_LOW_SCORE;
for ( place = 1; !quitterDone; ++place ) {
XP_UCHAR timeBuf[16];
XP_UCHAR buf[128];
XP_S16 highestScore = IMPOSSIBLY_LOW_SCORE;
XP_S16 highestIndex = -1;
XP_S16 thisScore = IMPOSSIBLY_LOW_SCORE;
XP_S16 thisIndex = -1;
const XP_UCHAR* placeStr = NULL;
XP_UCHAR placeBuf[32];
XP_UCHAR tmpbuf[48];
@ -3068,22 +3071,27 @@ server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream )
/* Find the next player we should print */
for ( ii = 0; ii < nPlayers; ++ii ) {
if ( quitter != ii && scores.arr[ii] > highestScore ) {
highestIndex = ii;
highestScore = scores.arr[ii];
if ( quitter != ii && scores.arr[ii] > thisScore ) {
thisIndex = ii;
thisScore = scores.arr[ii];
}
}
if ( highestIndex == -1 ) {
/* save top score overall to test for winner, including tie case */
if ( 1 == place ) {
winningScore = thisScore;
}
if ( thisIndex == -1 ) {
if ( quitter >= 0 ) {
XP_ASSERT( !quitterDone );
highestIndex = quitter;
thisIndex = quitter;
quitterDone = XP_TRUE;
placeKey = STR_RESIGNED;
} else {
break; /* we're done */
}
} else if ( place == 1 ) {
} else if ( thisScore == winningScore ) {
placeKey = STR_WINNER;
}
@ -3098,7 +3106,7 @@ server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream )
timeStr = (XP_UCHAR*)"";
if ( gi->timerEnabled ) {
XP_U16 penalty = player_timePenalty( gi, highestIndex );
XP_U16 penalty = player_timePenalty( gi, thisIndex );
if ( penalty > 0 ) {
XP_SNPRINTF( timeBuf, sizeof(timeBuf),
util_getUserString(
@ -3109,18 +3117,18 @@ server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream )
}
}
firstDone = model_getNumTilesTotal( model, highestIndex) == 0;
firstDone = model_getNumTilesTotal( model, thisIndex) == 0;
XP_SNPRINTF( tmpbuf, sizeof(tmpbuf),
(firstDone? addString:subString),
firstDone?
tilePenalties.arr[highestIndex]:
-tilePenalties.arr[highestIndex] );
tilePenalties.arr[thisIndex]:
-tilePenalties.arr[thisIndex] );
XP_SNPRINTF( buf, sizeof(buf),
(XP_UCHAR*)"[%s] %s: %d" XP_CR " (%d %s%s)", placeStr,
emptyStringIfNull(gi->players[highestIndex].name),
scores.arr[highestIndex],
model_getPlayerScore( model, highestIndex ),
emptyStringIfNull(gi->players[thisIndex].name),
scores.arr[thisIndex],
model_getPlayerScore( model, thisIndex ),
tmpbuf, timeStr );
if ( 1 < place ) {
@ -3129,7 +3137,7 @@ server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream )
stream_catString( stream, buf );
/* Don't consider this one next time around */
scores.arr[highestIndex] = IMPOSSIBLY_LOW_SCORE;
scores.arr[thisIndex] = IMPOSSIBLY_LOW_SCORE;
}
} /* server_writeFinalScores */

View file

@ -25,7 +25,7 @@ enum {
XWSTATE_BEGIN,
__UNUSED1, /* was XWSTATE_POOL_INITED */
XWSTATE_NEED_SHOWSCORE, /* client-only */
XWSTATE_WAITING_ALL_REG, /* includes waiting for dict from server */
__XWSTATE_WAITING_ALL_REG, /* unused */
XWSTATE_RECEIVED_ALL_REG, /* includes waiting for dict from server */
XWSTATE_NEEDSEND_BADWORD_INFO,
XWSTATE_MOVE_CONFIRM_WAIT, /* client's waiting to hear back */

View file

@ -324,11 +324,12 @@ drawPendingScore( BoardCtxt* board, XP_S16 turnScore, XP_Bool hasCursor )
/* Draw the pending score down in the last tray's rect */
if ( countTilesToShow( board ) < MAX_TRAY_TILES ) {
XP_U16 selPlayer = board->selPlayer;
XP_S16 curTurn = server_getCurrentTurn( board->server );
XP_Rect lastTileR;
figureTrayTileRect( board, MAX_TRAY_TILES-1, &lastTileR );
draw_score_pendingScore( board->draw, &lastTileR, turnScore,
selPlayer,
selPlayer, curTurn,
hasCursor?CELL_ISCURSOR:CELL_NONE );
}
} /* drawPendingScore */

View file

@ -283,6 +283,7 @@ curses_draw_score_drawPlayer( DrawCtx* p_dctx, const XP_Rect* rInner,
static void
curses_draw_score_pendingScore( DrawCtx* p_dctx, const XP_Rect* rect,
XP_S16 score, XP_U16 XP_UNUSED(playerNum),
XP_S16 XP_UNUSED(curTurn),
CellFlags XP_UNUSED(flags) )
{
CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx;

View file

@ -23,6 +23,8 @@
#include <signal.h>
#include <assert.h>
#include <ctype.h>
#include <sys/time.h>
#include <time.h>
#include <netdb.h> /* gethostbyname */
#include <errno.h>
@ -971,7 +973,9 @@ SIGWINCH_handler( int signal )
static void
SIGINTTERM_handler( int XP_UNUSED(signal) )
{
write( g_globals.quitPipe[1], "0", 1 );
if ( 1 != write( g_globals.quitpipe[1], "!", 1 ) ) {
XP_ASSERT(0);
}
}
static void
@ -1133,6 +1137,14 @@ curses_onGameSaved( void* closure, sqlite3_int64 rowid,
}
#ifdef USE_GLIBLOOP
static gboolean
handle_quitwrite( GIOChannel* XP_UNUSED(source), GIOCondition XP_UNUSED(condition), gpointer data )
{
CursesAppGlobals* globals = (CursesAppGlobals*)data;
handleQuit( globals );
return TRUE;
}
static gboolean
fire_acceptor( GIOChannel* source, GIOCondition condition, gpointer data )
{
@ -1523,6 +1535,18 @@ curses_util_makeStreamFromAddr(XW_UtilCtxt* uc, XP_PlayerAddr channelNo )
} /* curses_util_makeStreamFromAddr */
#endif
#ifdef XWFEATURE_CHAT
static void
curses_util_showChat( XW_UtilCtxt* uc,
const XP_UCHAR* const XP_UNUSED_DBG(msg) )
{
CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure;
globals->nChatsSent = 0;
XP_LOGF( "%s: got \"%s\"", __func__, msg );
}
#endif
static void
setupCursesUtilCallbacks( CursesAppGlobals* globals, XW_UtilCtxt* util )
{
@ -1536,6 +1560,10 @@ setupCursesUtilCallbacks( CursesAppGlobals* globals, XW_UtilCtxt* util )
#ifndef XWFEATURE_STANDALONE_ONLY
util->vtable->m_util_makeStreamFromAddr = curses_util_makeStreamFromAddr;
#endif
#ifdef XWFEATURE_CHAT
util->vtable->m_util_showChat = curses_util_showChat;
#endif
util->vtable->m_util_userQuery = curses_util_userQuery;
util->vtable->m_util_confirmTrade = curses_util_confirmTrade;
util->vtable->m_util_userPickTileBlank = curses_util_userPickTileBlank;
@ -1658,9 +1686,10 @@ relay_sendNoConn_curses( const XP_U8* msg, XP_U16 len,
} /* relay_sendNoConn_curses */
static void
relay_status_curses( void* XP_UNUSED(closure),
CommsRelayState XP_UNUSED_DBG(state) )
relay_status_curses( void* closure, CommsRelayState state )
{
CursesAppGlobals* globals = (CursesAppGlobals*)closure;
globals->commsRelayState = state;
XP_LOGF( "%s got status: %s", __func__, CommsRelayState2Str(state) );
}
@ -1670,7 +1699,8 @@ relay_connd_curses( void* XP_UNUSED(closure), XP_UCHAR* const XP_UNUSED(room),
XP_Bool XP_UNUSED_DBG(allHere),
XP_U16 XP_UNUSED_DBG(nMissing) )
{
XP_LOGF( "%s got allHere: %d; nMissing: %d", __func__, allHere, nMissing );
XP_LOGF( "%s got allHere: %s; nMissing: %d", __func__,
allHere?"true":"false", nMissing );
}
static void
@ -1833,6 +1863,31 @@ cursesUDPSocketChanged( void* closure, int newSock, int XP_UNUSED(oldSock),
globals->sources = g_list_append( globals->sources, sd );
}
static gboolean
chatsTimerFired( gpointer data )
{
CursesAppGlobals* globals = (CursesAppGlobals*)data;
if ( COMMS_RELAYSTATE_ALLCONNECTED == globals->commsRelayState
&& 3 > globals->nChatsSent ) {
XP_UCHAR msg[128];
struct tm* timp;
struct timeval tv;
struct timezone tz;
gettimeofday( &tv, &tz );
timp = localtime( &tv.tv_sec );
snprintf( msg, sizeof(msg), "Saying hi via chat at %.2d:%.2d:%.2d",
timp->tm_hour, timp->tm_min, timp->tm_sec );
XP_LOGF( "%s: sending \"%s\"", __func__, msg );
server_sendChat( globals->cGlobals.game.server, msg );
++globals->nChatsSent;
}
return TRUE;
}
void
cursesmain( XP_Bool isServer, LaunchParams* params )
{
@ -1884,12 +1939,18 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
#endif
#ifdef USE_GLIBLOOP
cursesListenOnSocket( &g_globals, 0, handle_stdin );
if ( !params->closeStdin ) {
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 );
# ifdef DEBUG
int piperesult =
# endif
pipe( g_globals.quitpipe );
XP_ASSERT( piperesult == 0 );
cursesListenOnSocket( &g_globals, g_globals.quitpipe[0], handle_quitwrite );
#else
cursesListenOnSocket( &g_globals, 0 ); /* stdin */
@ -1927,6 +1988,11 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
} else if ( !!params->nbs && !!params->fileName ) {
do_nbs_then_close( &g_globals.cGlobals, &procs );
} else {
if ( 0 != params->chatsInterval ) {
(void)g_timeout_add_seconds( params->chatsInterval, chatsTimerFired,
&g_globals );
}
XP_Bool opened = XP_FALSE;
initCurses( &g_globals );
getmaxyx( g_globals.boardWin, height, width );

View file

@ -70,6 +70,8 @@ struct CursesAppGlobals {
XP_U16 nLinesMenu;
gchar* lastErr;
XP_U16 nChatsSent;
union {
struct {
XWStreamCtxt* stream; /* how we can reach the server */
@ -82,12 +84,13 @@ struct CursesAppGlobals {
short statusLine;
XWGameState state;
CommsRelayState commsRelayState;
struct sockaddr_in listenerSockAddr;
#ifdef USE_GLIBLOOP
GMainLoop* loop;
GList* sources;
int quitPipe[2];
int quitpipe[2];
#else
XP_Bool timeToExit;
short fdCount;

View file

@ -1124,8 +1124,8 @@ gtk_draw_measureScoreText( DrawCtx* p_dctx, const XP_Rect* bounds,
static void
gtk_draw_score_pendingScore( DrawCtx* p_dctx, const XP_Rect* rect,
XP_S16 score, XP_U16 XP_UNUSED(playerNum),
CellFlags flags )
XP_S16 score, XP_U16 playerNum,
XP_S16 curTurn, CellFlags flags )
{
GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
XP_UCHAR buf[5];
@ -1133,6 +1133,7 @@ gtk_draw_score_pendingScore( DrawCtx* p_dctx, const XP_Rect* rect,
XP_Rect localR;
GdkColor* cursor = ((flags & CELL_ISCURSOR) != 0)
? &dctx->cursor : NULL;
GdkColor* txtColor;
if ( score >= 0 ) {
XP_SNPRINTF( buf, VSIZE(buf), "%.3d", score );
@ -1152,12 +1153,11 @@ gtk_draw_score_pendingScore( DrawCtx* p_dctx, const XP_Rect* rect,
}
ht = localR.height >> 2;
txtColor = (playerNum == curTurn) ? &dctx->black : &dctx->grey;
draw_string_at( dctx, NULL, "Pts:", ht,
&localR, XP_GTK_JUST_TOPLEFT,
&dctx->black, cursor );
&localR, XP_GTK_JUST_TOPLEFT, txtColor, cursor );
draw_string_at( dctx, NULL, buf, ht,
&localR, XP_GTK_JUST_BOTTOMRIGHT,
&dctx->black, cursor );
&localR, XP_GTK_JUST_BOTTOMRIGHT, txtColor, cursor );
} /* gtk_draw_score_pendingScore */
@ -1375,11 +1375,6 @@ gtkDrawCtxtMake( GtkWidget* drawing_area, GtkGameGlobals* globals )
dctx->drawing_area = drawing_area;
dctx->globals = globals;
map = gdk_colormap_get_system();
allocAndSet( map, &dctx->black, 0x0000, 0x0000, 0x0000 );
allocAndSet( map, &dctx->white, 0xFFFF, 0xFFFF, 0xFFFF );
{
// GdkWindow *window = NULL;
/* if ( GTK_WIDGET_FLAGS(GTK_WIDGET(drawing_area)) & GTK_NO_WINDOW ) { */
@ -1406,6 +1401,7 @@ gtkDrawCtxtMake( GtkWidget* drawing_area, GtkGameGlobals* globals )
map = gdk_colormap_get_system();
allocAndSet( map, &dctx->black, 0x0000, 0x0000, 0x0000 );
allocAndSet( map, &dctx->grey, 0x7FFF, 0x7FFF, 0x7FFF );
allocAndSet( map, &dctx->white, 0xFFFF, 0xFFFF, 0xFFFF );
allocAndSet( map, &dctx->bonusColors[0], 0xFFFF, 0xAFFF, 0xAFFF );

View file

@ -383,15 +383,55 @@ 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 );
}
/* 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 ); */
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;
if ( !!*storage ) {
XP_FREE( globals->cGlobals.params->util->mpool, *storage );
}
*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 void
gtkGotBuf( void* closure, const XP_U8* buf, XP_U16 len )
@ -487,7 +527,6 @@ getSelRow( const GtkAppGlobals* apg )
return result;
}
static GtkAppGlobals* g_globals_for_signal;
static void
handle_sigintterm( int XP_UNUSED(sig) )
{
@ -501,6 +540,7 @@ 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 );

View file

@ -1,6 +1,6 @@
/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */
/*
* Copyright 1997 - 2011 by Eric House (xwords@eehouse.org). All rights
* Copyright 1997 - 2013 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
@ -32,6 +32,7 @@
#include "dictnryp.h"
#include "linuxmain.h"
#include "strutils.h"
#include "linuxutl.h"
typedef struct DictStart {
XP_U32 numNodes;
@ -92,8 +93,6 @@ getNullTermParam( LinuxDictionaryCtxt* XP_UNUSED_DBG(dctx), const XP_U8** ptr,
XP_U16 len = 1 + XP_STRLEN( (XP_UCHAR*)*ptr );
XP_UCHAR* result = XP_MALLOC( dctx->super.mpool, len );
XP_MEMCPY( result, *ptr, len );
XP_LOGF( "%s: got param of len %d: \"%s\"", __func__,
len, result );
*ptr += len;
*headerLen -= len;
return result;
@ -374,16 +373,14 @@ initFromDictFile( LinuxDictionaryCtxt* dctx, const LaunchParams* params,
) {
XP_U32 curPos = ptr - dctx->dictBase;
gssize dictLength = dctx->dictLength - curPos;
GChecksum* cksum = g_checksum_new( G_CHECKSUM_MD5 );
g_checksum_update( cksum, ptr, dictLength );
const gchar* sum = g_checksum_get_string( cksum );
XP_LOGF( "calculated sum on %d bytes: %s", dictLength, sum );
gchar* checksum = g_compute_checksum_for_data( G_CHECKSUM_MD5, ptr, dictLength );
if ( NULL == dctx->super.md5Sum ) {
dctx->super.md5Sum = copyString( dctx->super.mpool, sum );
dctx->super.md5Sum = copyString( dctx->super.mpool, checksum );
} else {
XP_ASSERT( 0 == XP_STRCMP( dctx->super.md5Sum, sum ) );
XP_ASSERT( 0 == XP_STRCMP( dctx->super.md5Sum, checksum ) );
}
g_checksum_free( cksum );
g_free( checksum );
}
dctx->super.nFaces = numFaces;

View file

@ -48,8 +48,6 @@
# include <bluetooth/hci_lib.h>
#endif
/* #include <pthread.h> */
#include "linuxmain.h"
#include "linuxutl.h"
#include "linuxbt.h"
@ -633,6 +631,8 @@ typedef enum {
,CMD_SKIPCONFIRM
,CMD_VERTICALSCORE
,CMD_NOPEEK
,CMD_SPLITPACKETS
,CMD_CHAT
#ifdef XWFEATURE_CROSSHAIRS
,CMD_NOCROSSHAIRS
#endif
@ -680,7 +680,7 @@ typedef struct _CmdInfoRec {
} CmdInfoRec;
static CmdInfoRec CmdInfoRecs[] = {
{ CMD_HELP, false, "help", "print usage" }
{ CMD_HELP, false, "help", "print this message" }
,{ 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)" }
@ -737,6 +737,9 @@ static CmdInfoRec CmdInfoRecs[] = {
,{ CMD_SKIPCONFIRM, false, "skip-confirm", "don't confirm before commit" }
,{ CMD_VERTICALSCORE, false, "vertical", "scoreboard is vertical" }
,{ CMD_NOPEEK, false, "no-peek", "disallow scoreboard tap changing player" }
,{ CMD_SPLITPACKETS, true, "split-packets", "send tcp packets in "
"sections every random MOD <n> seconds to test relay reassembly" }
,{ CMD_CHAT, true, "send-chat", "send a chat every <n> seconds" }
#ifdef XWFEATURE_CROSSHAIRS
,{ CMD_NOCROSSHAIRS, false, "hide-crosshairs",
"don't show crosshairs on board" }
@ -892,15 +895,14 @@ linux_init_relay_socket( CommonGlobals* cGlobals, const CommsAddrRec* addrRec )
/* make a local copy of the address to send to */
sock = socket( AF_INET, SOCK_STREAM, 0 );
if ( sock == -1 ) {
XP_DEBUGF( "socket returned -1\n" );
XP_DEBUGF( "%s: socket returned -1\n", __func__ );
goto done;
}
to_sock.sin_port = htons( addrRec->u.ip_relay.port );
XP_STATUSF( "1: sending to port %d", addrRec->u.ip_relay.port );
host = gethostbyname( addrRec->u.ip_relay.hostName );
if ( NULL == host ) {
XP_WARNF( "%s: gethostbyname(%s) returned -1", __func__,
XP_WARNF( "%s: gethostbyname(%s) failed", __func__,
addrRec->u.ip_relay.hostName );
sock = -1;
goto done;
@ -928,6 +930,96 @@ linux_init_relay_socket( CommonGlobals* cGlobals, const CommsAddrRec* addrRec )
return sock;
} /* linux_init_relay_socket */
typedef struct _SendQueueElem {
XP_U32 id;
size_t len;
XP_U8* buf;
} SendQueueElem;
static void
free_elem_proc( gpointer data )
{
SendQueueElem* elem = (SendQueueElem*)data;
free( elem->buf );
free( elem );
}
static bool
send_or_close( CommonGlobals* cGlobals, const XP_U8* buf, size_t len )
{
size_t nSent = send( cGlobals->socket, buf, len, 0 );
bool success = len == nSent;
if ( !success ) {
close( cGlobals->socket );
(*cGlobals->socketChanged)( cGlobals->socketChangedClosure,
cGlobals->socket, -1,
&cGlobals->storage );
cGlobals->socket = -1;
/* delete all pending packets since the socket's bad */
g_slist_free_full( cGlobals->packetQueue, free_elem_proc );
cGlobals->packetQueue = NULL;
}
LOG_RETURNF( "%d", success );
return success;
}
static gboolean
sendTimerFired( gpointer data )
{
CommonGlobals* cGlobals = (CommonGlobals*)data;
if ( !!cGlobals->packetQueue ) {
guint listLen = g_slist_length( cGlobals->packetQueue );
assert( 0 < listLen );
SendQueueElem* elem = (SendQueueElem*)cGlobals->packetQueue->data;
cGlobals->packetQueue = cGlobals->packetQueue->next;
XP_LOGF( "%s: sending packet %ld of len %d (%d left)", __func__,
elem->id, elem->len, listLen - 1 );
bool sent = send_or_close( cGlobals, elem->buf, elem->len );
free( elem->buf );
free( elem );
if ( sent && 1 < listLen ) {
int when = XP_RANDOM() % (1 + cGlobals->params->splitPackets);
(void)g_timeout_add_seconds( when, sendTimerFired, cGlobals );
}
}
return FALSE;
}
static bool
send_per_params( const XP_U8* buf, const XP_U16 buflen,
CommonGlobals* cGlobals )
{
bool success;
if ( 0 == cGlobals->params->splitPackets ) {
success = send_or_close( cGlobals, buf, buflen );
} else {
for ( int nSent = 0; nSent < buflen; ) {
int toSend = buflen / 2;
if ( toSend > buflen - nSent ) {
toSend = buflen - nSent;
}
SendQueueElem* elem = malloc( sizeof(*elem) );
elem->id = ++cGlobals->nextPacketID;
elem->buf = malloc( toSend );
XP_MEMCPY( elem->buf, &buf[nSent], toSend );
elem->len = toSend;
cGlobals->packetQueue =
g_slist_append( cGlobals->packetQueue, elem );
nSent += toSend;
XP_LOGF( "%s: added packet %ld of len %d", __func__,
elem->id, elem->len );
}
int when = XP_RANDOM() % (1 + cGlobals->params->splitPackets);
(void)g_timeout_add_seconds( when, sendTimerFired, cGlobals );
success = TRUE;
}
return success;
}
static XP_S16
linux_tcp_send( CommonGlobals* cGlobals, const XP_U8* buf, XP_U16 buflen,
const CommsAddrRec* addrRec )
@ -950,27 +1042,17 @@ linux_tcp_send( CommonGlobals* cGlobals, const XP_U8* buf, XP_U16 buflen,
}
}
if ( sock != -1 ) {
XP_U16 netLen = htons( buflen );
errno = 0;
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 );
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__ );
if ( send_per_params( tmp, buflen + sizeof(netLen), globals ) ) {
result = buflen;
}
} else {
XP_LOGF( "%s: socket still -1", __func__ );
}
return result;
} /* linux_tcp_send */
@ -1085,13 +1167,15 @@ linux_close_socket( CommonGlobals* cGlobals )
static int
blocking_read( int fd, unsigned char* buf, const int len )
{
assert( -1 != fd );
int nRead = 0;
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 );
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,
@ -1105,6 +1189,7 @@ blocking_read( int fd, unsigned char* buf, const int len )
nRead = -1;
}
XP_LOGF( "%s(fd=%d, sought=%d) => %d", __func__, fd, len, nRead );
return nRead;
}
@ -1136,14 +1221,36 @@ linux_relay_receive( CommonGlobals* cGlobals, unsigned char* buf, int bufSize )
nRead = -1;
}
} else {
if ( 0 == XP_RANDOM() % -params->dropNthRcvd ) {
XP_LOGF( "%s: RANDOMLY dropping %dth packet "
"per --drop-nth-packet",
__func__, params->nPacketsRcvd );
nRead = blocking_read( sock, buf, packetSize );
if ( nRead != packetSize ) {
nRead = -1;
} else {
LaunchParams* params = cGlobals->params;
++params->nPacketsRcvd;
if ( params->dropNthRcvd == 0 ) {
/* do nothing */
} else if ( params->dropNthRcvd > 0 ) {
if ( params->nPacketsRcvd == params->dropNthRcvd ) {
XP_LOGF( "%s: dropping %dth packet per --drop-nth-packet",
__func__, params->nPacketsRcvd );
nRead = -1;
}
} else {
if ( 0 == XP_RANDOM() % -params->dropNthRcvd ) {
XP_LOGF( "%s: RANDOMLY dropping %dth packet "
"per --drop-nth-packet",
__func__, params->nPacketsRcvd );
nRead = -1;
}
}
}
}
}
if ( -1 == nRead ) {
linux_close_socket( cGlobals );
comms_transportFailed( cGlobals->game.comms );
}
}
XP_LOGF( "%s=>%d", __func__, nRead );
return nRead;
@ -1785,7 +1892,6 @@ main( int argc, char** argv )
XP_Bool isServer = XP_FALSE;
char* portNum = NULL;
char* hostName = "localhost";
XP_Bool closeStdin = XP_FALSE;
unsigned int seed = defaultRandomSeed();
LaunchParams mainParams;
XP_U16 nPlayerDicts = 0;
@ -2106,7 +2212,7 @@ main( int argc, char** argv )
break;
#endif
case CMD_CLOSESTDIN:
closeStdin = XP_TRUE;
mainParams.closeStdin = XP_TRUE;
break;
case CMD_QUITAFTER:
mainParams.quitAfter = atoi(optarg);
@ -2134,6 +2240,12 @@ main( int argc, char** argv )
case CMD_NOPEEK:
mainParams.allowPeek = XP_FALSE;
break;
case CMD_SPLITPACKETS:
mainParams.splitPackets = atoi( optarg );
break;
case CMD_CHAT:
mainParams.chatsInterval = atoi(optarg);
break;
#ifdef XWFEATURE_CROSSHAIRS
case CMD_NOCROSSHAIRS:
mainParams.hideCrosshairs = XP_TRUE;
@ -2349,7 +2461,7 @@ main( int argc, char** argv )
srandom( seed ); /* init linux random number generator */
XP_LOGF( "seeded srandom with %d", seed );
if ( closeStdin ) {
if ( mainParams.closeStdin ) {
fclose( stdin );
if ( mainParams.quitAfter < 0 ) {
fprintf( stderr, "stdin closed; you'll need some way to quit\n" );

View file

@ -98,6 +98,9 @@ typedef struct LaunchParams {
XP_Bool duplicatePackets;
XP_Bool skipGameOver;
XP_Bool useMmap;
XP_Bool closeStdin;
XP_U16 splitPackets;
XP_U16 chatsInterval; /* 0 means disabled */
#ifdef XWFEATURE_SEARCHLIMIT
XP_Bool allowHintRect;
#endif
@ -194,6 +197,8 @@ struct CommonGlobals {
void* socketChangedClosure;
OnSaveFunc onSave;
void* onSaveClosure;
GSList* packetQueue;
XP_U32 nextPacketID; /* for debugging */
CommsRelayState state;

View file

@ -1,7 +1,9 @@
#!/bin/bash
set -u -e
LOGDIR=$(basename $0)_logs
APP_NEW=""
DO_CLEAN=""
APP_NEW_PARAMS=""
NGAMES=""
UDP_PCT=0
@ -25,6 +27,7 @@ SEED=""
BOARD_SIZES_OLD=(15)
BOARD_SIZES_NEW=(15)
NAMES=(UNUSED Brynn Ariela Kati Eric)
SEND_CHAT=''
declare -A PIDS
declare -A APPS
@ -40,11 +43,30 @@ declare -a APPS_OLD
declare -a DICTS
declare -A CHECKED_ROOMS
function cleanup() {
APP="$(basename $APP_NEW)"
while pidof $APP; do
echo "killing existing $APP instances..."
killall -9 $APP
sleep 1
done
echo "cleaning everything up...."
if [ -d $LOGDIR ]; then
mv $LOGDIR /tmp/${LOGDIR}_$$
fi
if [ -e $(dirname $0)/../../relay/xwrelay.log ]; then
mkdir -p /tmp/${LOGDIR}_$$
mv $(dirname $0)/../../relay/xwrelay.log /tmp/${LOGDIR}_$$
fi
echo "delete from games;" | psql -q -t xwgames
}
function connName() {
LOG=$1
grep 'got_connect_cmd: connName' $LOG | \
tail -n 1 | \
sed 's,^.*connName: \"\(.*\)\"$,\1,'
sed 's,^.*connName: \"\(.*\)\" (reconnect=.)$,\1,'
}
function check_room() {
@ -201,6 +223,10 @@ build_cmds() {
PARAMS="$PARAMS --file $FILE"
fi
PARAMS="$PARAMS --drop-nth-packet $DROP_N $PLAT_PARMS"
# PARAMS="$PARAMS --split-packets 2"
if [ -n $SEND_CHAT ]; then
PARAMS="$PARAMS --send-chat $SEND_CHAT"
fi
# PARAMS="$PARAMS --savefail-pct 10"
[ -n "$SEED" ] && PARAMS="$PARAMS --seed $RANDOM"
PARAMS="$PARAMS $PUBLIC"
@ -453,14 +479,18 @@ run_cmds() {
try_upgrade $KEY
launch $KEY &
PID=$!
renice +1 $PID >/dev/null
PIDS[$KEY]=$PID
ROOM_PIDS[$ROOM]=$PID
MINEND[$KEY]=$(($NOW + $MINRUN))
else
SLEEP=$((${MINEND[$KEY]} - $NOW))
[ $SLEEP -gt 0 ] && sleep $SLEEP
kill ${PIDS[$KEY]} || true
wait ${PIDS[$KEY]}
PID=${PIDS[$KEY]}
if [ -d /proc/$PID ]; then
SLEEP=$((${MINEND[$KEY]} - $NOW))
[ $SLEEP -gt 0 ] && sleep $SLEEP
kill $PID || true
wait $PID
fi
PIDS[$KEY]=0
ROOM_PIDS[$ROOM]=0
[ "$DROP_N" -ge 0 ] && increment_drop $KEY
@ -527,6 +557,7 @@ function usage() {
[ $# -gt 0 ] && echo "Error: $1" >&2
echo "Usage: $(basename $0) \\" >&2
echo " [--via-udp <pct>] \\" >&2
echo " [--clean-start] \\" >&2
echo " [--game-dict <path/to/dict>]* \\" >&2
echo " [--old-app <path/to/app]* \\" >&2
echo " [--new-app <path/to/app] \\" >&2
@ -540,6 +571,8 @@ function usage() {
echo " [--port <int>] \\" >&2
echo " [--seed <int>] \\" >&2
echo " [--undo-pct <int>] \\" >&2
echo " [--send-chat <interval-in-seconds> \\" >&2
echo " [--resign-ratio <0 <= n <=1000 > \\" >&2
echo " [--help] \\" >&2
exit 1
@ -554,6 +587,8 @@ while [ "$#" -gt 0 ]; do
--via-udp)
UDP_PCT=$(getArg $*)
shift
--clean-start)
DO_CLEAN=1
;;
--num-games)
NGAMES=$(getArg $*)
@ -607,6 +642,14 @@ while [ "$#" -gt 0 ]; do
UNDO_PCT=$(getArg $*)
shift
;;
--send-chat)
SEND_CHAT=$(getArg $*)
shift
;;
--resign-ratio)
RESIGN_RATIO=$(getArg $*)
shift
;;
--help)
usage
;;
@ -637,7 +680,8 @@ done
[ -n "$SEED" ] && RANDOM=$SEED
[ -z "$ONEPER" -a $NROOMS -lt $NGAMES ] && usage "use --one-per if --num-rooms < --num-games"
LOGDIR=$(basename $0)_logs
[ -n "$DO_CLEAN" ] && cleanup
RESUME=""
for FILE in $(ls $LOGDIR/*.{xwg,txt} 2>/dev/null); do
if [ -e $FILE ]; then

View file

@ -24,7 +24,7 @@ while [ $# -ge 1 ]; do
while read LINE; do
case "$LINE" in
*got_connect_cmd:\ connName* )
CONNNAME="$(echo $LINE | sed 's,^.*connName: "\(.*\)"$,\1,')"
CONNNAME="$(echo $LINE | sed 's,^.*connName: "\(.*\)" .*$,\1,')"
;;
*got_connect_cmd:\ set\ hostid* )
HOSTID=$(echo $LINE | sed 's,^.*set hostid: \(.\)$,\1,')

View file

@ -45,6 +45,7 @@ OBJ = $(patsubst %.cpp,%.o,$(SRC))
LDFLAGS += -pthread -g $(STATIC)
LDFLAGS += -L$(shell pg_config --libdir)
LDFLAGS += $(shell pkg-config --libs glib-2.0)
LDFLAGS += -lrt
CPPFLAGS += -DSPAWN_SELF -g -Wall
CPPFLAGS += -I $(shell pg_config --includedir)

View file

@ -21,8 +21,39 @@
#include <assert.h>
#include <string.h>
#include <time.h>
#include "addrinfo.h"
#include "xwrelay_priv.h"
#include "tpool.h"
// static uint32_t s_prevCreated = 0L;
void
AddrInfo::construct( int socket, const AddrUnion* saddr, bool isTCP )
{
memset( this, 0, sizeof(*this) );
struct timespec tp;
clock_gettime( CLOCK_MONOTONIC, &tp );
/* convert to milliseconds */
m_created = (tp.tv_sec * 1000) + (tp.tv_nsec / 1000000);
logf( XW_LOGINFO, "%s: m_created for socket %d: %lx",
__func__, socket, m_created );
/* assert( m_created >= s_prevCreated ); */
/* s_prevCreated = m_created; */
m_socket = socket;
m_isTCP = isTCP;
memcpy( &m_saddr, saddr, sizeof(m_saddr) );
m_isValid = true;
}
bool
AddrInfo::isCurrent() const
{
return XWThreadPool::GetTPool()->IsCurrent( this );
}
bool
AddrInfo::equals( const AddrInfo& other ) const
@ -31,6 +62,11 @@ AddrInfo::equals( const AddrInfo& other ) const
if ( equal ) {
if ( isTCP() ) {
equal = m_socket == other.m_socket;
if ( equal && created() != other.created() ) {
logf( XW_LOGINFO, "%s: rejecting on time mismatch (%lx vs %lx)",
__func__, created(), other.created() );
equal = false;
}
} else {
// assert( m_socket == other.m_socket ); /* both same UDP socket */
/* what does equal mean on udp addresses? Same host, or same host AND game */

View file

@ -61,18 +61,13 @@ class AddrInfo {
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; }
uint32_t created() const { return m_created; }
bool isCurrent() const;
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;
}
void construct( int socket, const AddrUnion* saddr, bool isTCP );
// AddrInfo& operator=(const AddrInfo&); // Prevent assignment
int m_socket;
@ -80,6 +75,7 @@ class AddrInfo {
bool m_isValid;
ClientToken m_clientToken; /* must be 32 bit */
AddrUnion m_saddr;
uint32_t m_created; /* microseconds since boot, from clock_gettime() */
};
#endif

View file

@ -1,4 +1,4 @@
/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */
/* -*- -*- */
/*
* Copyright 2005-2011 by Eric House (xwords@eehouse.org). All rights
@ -34,6 +34,22 @@ CidInfo::GetAddrs( void )
m_addrs : m_cref->GetAddrs();
}
void
CidInfo::SetOwner( pthread_t owner )
{
if ( 0 == owner ) {
if ( 0 == --m_ownerCount ) {
m_owner = 0;
}
} else {
++m_ownerCount;
assert( 0 == m_owner || owner == m_owner );
m_owner = owner;
}
assert( 0 <= m_ownerCount );
logf( XW_LOGINFO, "%s(owner=%d); m_ownerCount=%d", __func__, owner, m_ownerCount );
}
CidLock* CidLock::s_instance = NULL;
CidLock::CidLock() : m_nextCID(0)
@ -56,7 +72,7 @@ CidLock::print_claimed( const char* caller )
string str;
string_printf( str, "after %s: ", caller );
// Assume we have the mutex!!!!
map< CookieID, CidInfo*>::iterator iter;
map< CookieID, CidInfo*>::const_iterator iter;
for ( iter = m_infos.begin(); iter != m_infos.end(); ++iter ) {
CidInfo* info = iter->second;
if ( 0 == info->GetOwner() ) {
@ -65,7 +81,7 @@ CidLock::print_claimed( const char* caller )
string_printf( str, "%d,", info->GetCid() );
}
}
string_printf( str, "%d,", " (plus %d unclaimed.)", unclaimed );
string_printf( str, " (plus %d unclaimed.)", unclaimed );
logf( XW_LOGINFO, "%s: claimed: %s", __func__, str.c_str() );
}
#else
@ -73,12 +89,14 @@ CidLock::print_claimed( const char* caller )
#endif
CidInfo*
CidLock::Claim( CookieID cid )
CidLock::Claim( const CookieID origCid )
{
CookieID cid = origCid;
#ifdef CIDLOCK_DEBUG
logf( XW_LOGINFO, "%s(%d)", __func__, cid );
logf( XW_LOGINFO, "%s(%d)", __func__, origCid );
#endif
CidInfo* info = NULL;
pthread_t self = pthread_self();
for ( ; ; ) {
MutexLock ml( &m_infos_mutex );
@ -92,13 +110,14 @@ CidLock::Claim( CookieID cid )
info = new CidInfo( cid );
m_infos.insert( pair<CookieID, CidInfo*>( cid, info ) );
} else {
if ( 0 == iter->second->GetOwner() ) {
pthread_t owner = iter->second->GetOwner();
if ( 0 == owner || self == owner ) {
info = iter->second;
}
}
if ( NULL != info ) { // we're done
info->SetOwner( pthread_self() );
info->SetOwner( self );
PRINT_CLAIMED();
break;
}
@ -109,7 +128,7 @@ CidLock::Claim( CookieID cid )
pthread_cond_wait( &m_infos_condvar, &m_infos_mutex );
}
#ifdef CIDLOCK_DEBUG
logf( XW_LOGINFO, "%s(%d): DONE", __func__, cid );
logf( XW_LOGINFO, "%s(%d): DONE", __func__, origCid );
#endif
return info;
} /* CidLock::Claim */
@ -119,23 +138,25 @@ CidLock::ClaimSocket( const AddrInfo* addr )
{
CidInfo* info = NULL;
#ifdef CIDLOCK_DEBUG
logf( XW_LOGINFO, "%s(sock=%d)", __func__, sock );
logf( XW_LOGINFO, "%s(sock=%d)", __func__, addr->socket() );
#endif
for ( ; ; ) {
MutexLock ml( &m_infos_mutex );
map<CookieID, CidInfo*>::iterator iter;
for ( iter = m_infos.begin(); NULL == info && iter != m_infos.end(); ++iter ) {
map<CookieID, CidInfo*>::const_iterator iter;
for ( iter = m_infos.begin(); NULL == info && iter != m_infos.end();
++iter ) {
const vector<AddrInfo>& addrs = iter->second->GetAddrs();
vector<AddrInfo>::const_iterator iter2;
for ( iter2 = addrs.begin(); iter2 != addrs.end(); ++iter2 ) {
if ( iter2->equals(*addr) ) {
assert( !info ); // I hit this -- twice!!!!
if ( 0 == iter->second->GetOwner() ) {
info = iter->second;
info->SetOwner( pthread_self() );
PRINT_CLAIMED();
}
break;
// break;
}
}
}
@ -145,7 +166,7 @@ CidLock::ClaimSocket( const AddrInfo* addr )
break;
}
#ifdef CIDLOCK_DEBUG
logf( XW_LOGINFO, "%s(sock=%d): waiting....", __func__, sock );
logf( XW_LOGINFO, "%s(sock=%d): waiting....", __func__, addr->socket() );
#endif
pthread_cond_wait( &m_infos_condvar, &m_infos_mutex );
}
@ -171,9 +192,11 @@ CidLock::Relinquish( CidInfo* claim, bool drop )
assert( claim->GetOwner() == pthread_self() );
if ( drop ) {
#ifdef CIDLOCK_DEBUG
logf( XW_LOGINFO, "%s: deleting %p", __func__, iter->second );
logf( XW_LOGINFO, "%s: deleting %p (cid=%d)",
__func__, claim, claim->GetCid() );
#endif
m_infos.erase( iter );
claim->SetOwner( 0 );
delete claim;
} else {
CookieRef* ref = claim->GetRef();

View file

@ -34,7 +34,10 @@ class CidInfo {
CidInfo( CookieID cid )
:m_cid(cid),
m_cref(NULL),
m_owner(0) {}
m_owner(0),
m_ownerCount(0) {}
~CidInfo() { assert( 0 == m_ownerCount ); }
CookieID GetCid( void ) { return m_cid; }
CookieRef* GetRef( void ) { return m_cref; }
@ -43,12 +46,13 @@ class CidInfo {
void SetAddrs( vector<AddrInfo> addrs ) { m_addrs = addrs; };
void SetRef( CookieRef* cref ) { m_cref = cref; }
void SetOwner( pthread_t owner ) { m_owner = owner; }
void SetOwner( pthread_t owner );
private:
CookieID m_cid;
CookieRef* m_cref;
pthread_t m_owner;
int m_ownerCount;
vector<AddrInfo> m_addrs;
};

View file

@ -102,7 +102,7 @@ CookieRef::ReInit( const char* cookie, const char* connName, CookieID cid,
} else {
m_delayMicros = 0;
}
RelayConfigs::GetConfigs()->GetValueFor( "HEARTBEAT", &m_heatbeat );
RelayConfigs::GetConfigs()->GetValueFor( "HEARTBEAT", &m_heartbeat );
logf( XW_LOGINFO, "initing cref for cookie %s, connName %s",
m_cookie.c_str(), m_connName.c_str() );
@ -422,8 +422,8 @@ CookieRef::removeSocket( const AddrInfo* addr )
if ( iter->m_addr.equals( *addr ) ) {
if ( iter->m_ackPending ) {
logf( XW_LOGINFO,
"Never got ack; removing hid %d from DB",
iter->m_hostID );
"%s: Never got ack; removing hid %d from DB",
__func__, iter->m_hostID );
DBMgr::Get()->RmDeviceByHid( ConnName(),
iter->m_hostID );
m_nPlayersHere -= iter->m_nPlayersH;
@ -857,22 +857,19 @@ void
CookieRef::send_stored_messages( HostID dest, const AddrInfo* addr )
{
logf( XW_LOGVERBOSE0, "%s(dest=%d)", __func__, dest );
assert( dest > 0 && dest <= 4 );
assert( -1 != addr->socket() );
for ( ; ; ) {
unsigned char buf[MAX_MSG_LEN];
size_t buflen = sizeof(buf);
int msgID;
if ( !DBMgr::Get()->GetStoredMessage( ConnName(), dest,
buf, &buflen, &msgID ) ) {
break;
}
if ( ! send_with_length( addr, dest, buf, buflen, true ) ) {
break;
}
DBMgr::Get()->RemoveStoredMessages( &msgID, 1 );
DBMgr* dbmgr = DBMgr::Get();
const char* cname = ConnName();
while ( addr->isCurrent() ) {
unsigned char buf[MAX_MSG_LEN];
size_t buflen = sizeof(buf);
int msgID;
if ( !dbmgr->GetStoredMessage( cname, dest, buf, &buflen, &msgID )
|| ! send_with_length( addr, dest, buf, buflen, true ) ) {
break;
}
dbmgr->RemoveStoredMessages( &msgID, 1 );
}
} /* send_stored_messages */
@ -936,6 +933,8 @@ CookieRef::increasePlayerCounts( CRefEvent* evt, bool reconn, HostID* hidp,
{
RWWriteLock rwl( &m_socketsRWLock );
HostRec hr( hostid, &evt->addr, nPlayersH, seed, !reconn );
logf( XW_LOGINFO, "%s: adding socket rec with ts %lx", __func__,
evt->addr.created() );
m_sockets.push_back( hr );
}
@ -1291,7 +1290,7 @@ CookieRef::sendAllHere( bool initial )
message for it. Would be better if could look up rather than run
through the vector each time. */
HostID dest;
for ( dest = 1; dest <= m_nPlayersHere; ++dest ) {
for ( dest = 1; dest <= m_nPlayersSought; ++dest ) {
bool sent = false;
*idLoc = dest; /* write in this target's hostId */
@ -1461,7 +1460,7 @@ CookieRef::logf( XW_LogLevel level, const char* format, ... )
char buf[256];
int len;
len = snprintf( buf, sizeof(buf), "cid:%d ", m_cid );
len = snprintf( buf, sizeof(buf), "cid:%d(%s) ", m_cid, m_connName.c_str() );
va_list ap;
va_start( ap, format );

View file

@ -104,7 +104,7 @@ class CookieRef {
const char* Cookie() const { return m_cookie.c_str(); }
const char* ConnName() { return m_connName.c_str(); }
int GetHeartbeat() { return m_heatbeat; }
int GetHeartbeat() { return m_heartbeat; }
const AddrInfo* SocketForHost( HostID dest );
HostID HostForSocket( const AddrInfo* addr );
@ -275,7 +275,7 @@ class CookieRef {
pthread_rwlock_t m_socketsRWLock;
vector<HostRec> m_sockets;
int m_heatbeat; /* might change per carrier or something. */
int m_heartbeat; /* might change per carrier or something. */
string m_cookie; /* cookie used for initial connections */
string m_connName; /* globally unique name */
CookieID m_cid; /* Unique among current games on this server */

View file

@ -216,9 +216,8 @@ CRefMgr::getFromFreeList( void )
/* connect case */
CidInfo*
CRefMgr::getMakeCookieRef( const char* cookie, HostID hid,
int nPlayersH, int nPlayersT, int langCode,
int seed, bool wantsPublic,
CRefMgr::getMakeCookieRef( const char* cookie, int nPlayersH, int nPlayersT,
int langCode, int seed, bool wantsPublic,
bool makePublic, bool* seenSeed )
{
CidInfo* cinfo;
@ -291,18 +290,21 @@ CRefMgr::getMakeCookieRef( const char* connName, const char* cookie,
int langCode, bool isPublic, bool* isDead )
{
CookieRef* cref = NULL;
CidInfo* cinfo;
CidInfo* cinfo = NULL;
for ( ; ; ) { /* for: see comment above */
/* fetch these from DB */
char curCookie[MAX_INVITE_LEN+1];
int curLangCode;
int nPlayersT = 0;
int nAlreadyHere = 0;
int nAlreadyHere = nPlayersH;
CookieID cid;
if ( !m_db->FindGameFor( connName, curCookie, sizeof(curCookie),
seed, hid, nPlayersH, nPlayersS,
&curLangCode, isDead, &cid ) ) {
break;
}
CookieID cid = m_db->FindGame( connName, curCookie, sizeof(curCookie),
&curLangCode, &nPlayersT, &nAlreadyHere,
isDead );
if ( 0 != cid ) { /* already open */
cinfo = m_cidlock->Claim( cid );
if ( NULL == cinfo->GetRef() ) {
@ -315,25 +317,19 @@ CRefMgr::getMakeCookieRef( const char* connName, const char* cookie,
cinfo = m_cidlock->Claim();
cid = cinfo->GetCid();
if ( nPlayersT == 0 ) { /* wasn't in the DB */
m_db->AddNew( cookie, connName, cid, langCode, nPlayersS, isPublic );
curLangCode = langCode;
nPlayersT = nPlayersS;
} else {
if ( !m_db->AddCID( connName, cid ) ) {
m_cidlock->Relinquish( cinfo, true );
continue;
}
cookie = curCookie;
if ( !m_db->AddCID( connName, cid ) ) {
m_cidlock->Relinquish( cinfo, true );
continue;
}
cookie = curCookie;
cref = AddNew( cookie, connName, cid, curLangCode, nPlayersT,
cref = AddNew( cookie, connName, cid, curLangCode, nPlayersS,
nAlreadyHere );
cinfo->SetRef( cref );
}
break;
} /* for */
assert( cinfo->GetRef() );
assert( NULL == cinfo || cinfo->GetRef() );
return cinfo;
} /* getMakeCookieRef */
@ -402,12 +398,12 @@ CRefMgr::PrintSocketInfo( int socket, string& out )
}
CidInfo*
CRefMgr::getCookieRef( CookieID cid, bool failOk )
CRefMgr::getCookieRef( CookieID cid, bool failOk /* = false */ )
{
CidInfo* cinfo = NULL;
for ( int count = 0; ; ++count ) {
cinfo = m_cidlock->Claim( cid );
if ( NULL != cinfo->GetRef() ) {
if ( NULL != cinfo->GetRef() ) { /* What's it mean to get a cinfo back but have it be empty??? */
break;
} else if ( failOk || count > 20 ) {
break;
@ -474,9 +470,10 @@ CRefMgr::AddNew( const char* cookie, const char* connName, CookieID cid,
if ( m_cookieMap.size() == 1 ) {
RelayConfigs* cfg = RelayConfigs::GetConfigs();
int heartbeat;
cfg->GetValueFor( "HEARTBEAT", &heartbeat );
TimerMgr::GetTimerMgr()->SetTimer( heartbeat, heartbeatProc, this,
heartbeat );
if ( cfg->GetValueFor( "HEARTBEAT", &heartbeat ) ) {
TimerMgr::GetTimerMgr()->SetTimer( heartbeat, heartbeatProc, this,
heartbeat );
}
}
#endif
@ -609,8 +606,8 @@ SafeCref::SafeCref( const char* cookie, const AddrInfo* addr, int clientVers,
{
CidInfo* cinfo;
cinfo = m_mgr->getMakeCookieRef( cookie, 0, nPlayersH, nPlayersS, langCode,
gameSeed, wantsPublic, makePublic,
cinfo = m_mgr->getMakeCookieRef( cookie, nPlayersH, nPlayersS,
langCode, gameSeed, wantsPublic, makePublic,
&m_seenSeed );
if ( cinfo != NULL ) {
CookieRef* cref = cinfo->GetRef();
@ -620,16 +617,23 @@ SafeCref::SafeCref( const char* cookie, const AddrInfo* addr, int clientVers,
}
}
/* REconnect case */
/* Reconnect case
*
* Device thinks it's connected, but we may disagree, e.g. if it sent an ACK
* we didn't receive in time. So we may actually wind up creating a new row,
* with a new connname, in the games DB in response to this!
*
*/
SafeCref::SafeCref( const char* connName, const char* cookie, HostID hid,
const AddrInfo* addr, int clientVers, DevID* devID, int nPlayersH,
int nPlayersS, unsigned short gameSeed, int langCode,
bool wantsPublic, bool makePublic )
const AddrInfo* addr, int clientVers, DevID* devID,
int nPlayersH, int nPlayersS, unsigned short gameSeed,
int langCode, bool wantsPublic, bool makePublic )
: m_cinfo( NULL )
, m_mgr( CRefMgr::Get() )
, m_addr( *addr )
, m_clientVersion( clientVers )
, m_devID( devID )
, m_hid( hid )
, m_isValid( false )
{
CidInfo* cinfo;
@ -639,6 +643,15 @@ SafeCref::SafeCref( const char* connName, const char* cookie, HostID hid,
cinfo = m_mgr->getMakeCookieRef( connName, cookie, hid, nPlayersH,
nPlayersS, gameSeed, langCode,
wantsPublic || makePublic, &isDead );
/* If the reconnect doesn't check out, treat it as a connect */
if ( NULL == cinfo ) {
logf( XW_LOGINFO, "%s: taking a second crack", __func__ );
m_hid = HOST_ID_NONE;
cinfo = m_mgr->getMakeCookieRef( cookie, nPlayersH, nPlayersS,
langCode, gameSeed,
wantsPublic, makePublic, &m_seenSeed );
}
if ( cinfo != NULL ) {
assert( cinfo->GetCid() == cinfo->GetRef()->GetCid() );
m_locked = cinfo->GetRef()->Lock();
@ -665,7 +678,7 @@ SafeCref::SafeCref( const char* const connName )
}
}
SafeCref::SafeCref( CookieID cid, bool failOk )
SafeCref::SafeCref( CookieID cid, bool failOk /* = false */ )
: m_cinfo( NULL )
, m_mgr( CRefMgr::Get() )
, m_isValid( false )

View file

@ -117,7 +117,7 @@ class CRefMgr {
CookieRef* getFromFreeList( void );
/* connect case */
CidInfo* getMakeCookieRef( const char* cookie, HostID hid, int nPlayersH,
CidInfo* getMakeCookieRef( const char* cookie, int nPlayersH,
int nPlayersS, int langCode, int seed,
bool wantsPublic, bool makePublic,
bool* seenSeed );
@ -187,14 +187,15 @@ class SafeCref {
bool Forward( HostID src, const AddrInfo* addr, HostID dest,
const unsigned char* buf, int buflen ) {
if ( IsValid() ) {
bool success = IsValid();
if ( success ) {
CookieRef* cref = m_cinfo->GetRef();
assert( 0 != cref->GetCid() );
cref->_Forward( src, addr, dest, buf, buflen );
return true;
} else {
return false;
logf( XW_LOGINFO, "%s: unable to forward", __func__ );
}
return success;
}
void PutMsg( HostID srcID, const AddrInfo* addr, HostID destID,
@ -217,8 +218,7 @@ class SafeCref {
return false;
}
}
bool Reconnect( HostID srcID, int nPlayersH, int nPlayersS,
int seed, XWREASON* errp ) {
bool Reconnect( int nPlayersH, int nPlayersS, int seed, XWREASON* errp ) {
bool success = false;
*errp = XWRELAY_ERROR_NONE;
if ( IsValid() ) {
@ -228,7 +228,7 @@ class SafeCref {
*errp = XWRELAY_ERROR_DEADGAME;
} else {
success = cref->_Reconnect( m_clientVersion, m_devID,
srcID, nPlayersH, nPlayersS, seed,
m_hid, nPlayersH, nPlayersS, seed,
&m_addr, m_dead );
}
}
@ -252,14 +252,14 @@ class SafeCref {
}
bool HandleAck(HostID hostID ) {
if ( IsValid() ) {
bool handled = IsValid();
if ( handled ) {
CookieRef* cref = m_cinfo->GetRef();
assert( 0 != cref->GetCid() );
cref->_HandleAck( hostID );
return true;
} else {
return false;
}
logf( XW_LOGINFO, "%s => %d", __func__, handled );
return handled;
}
void Shutdown() {
if ( IsValid() ) {
@ -391,6 +391,7 @@ class SafeCref {
AddrInfo m_addr;
int m_clientVersion;
DevID* m_devID;
HostID m_hid;
bool m_isValid;
bool m_locked;
bool m_dead;

View file

@ -65,6 +65,7 @@ DBMgr::DBMgr()
int tmp;
RelayConfigs::GetConfigs()->GetValueFor( "USE_B64", &tmp );
m_useB64 = tmp != 0;
logf( XW_LOGINFO, "%s: m_useB64=%d", __func__, m_useB64 );
pthread_key_create( &m_conn_key, destr_function );
@ -123,6 +124,39 @@ DBMgr::AddNew( const char* cookie, const char* connName, CookieID cid,
PQclear( result );
}
/* Grab the row for a connname. If the params don't check out, return false.
*/
bool
DBMgr::FindGameFor( const char* connName, char* cookieBuf, int bufLen,
unsigned short seed, HostID hid,
int nPlayersH, int nPlayersS,
int* langP, bool* isDead, CookieID* cidp )
{
bool found = false;
const char* fmt = "SELECT cid, room, lang, nPerDevice, dead FROM "
GAMES_TABLE " WHERE connName = '%s' AND nTotal = %d "
"AND %d = seeds[%d] AND 'A' = ack[%d] "
;
string query;
string_printf( query, fmt, connName, nPlayersS, seed, hid, hid );
logf( XW_LOGINFO, "query: %s", query.c_str() );
PGresult* result = PQexec( getThreadConn(), query.c_str() );
assert( 1 >= PQntuples( result ) );
found = 1 == PQntuples( result );
if ( found ) {
*cidp = atoi( PQgetvalue( result, 0, 0 ) );
snprintf( cookieBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) );
*langP = atoi( PQgetvalue( result, 0, 2 ) );
*isDead = 't' == PQgetvalue( result, 0, 4 )[0];
}
PQclear( result );
logf( XW_LOGINFO, "%s(%s)=>%d", __func__, connName, found );
return found;
} /* FindGameFor */
CookieID
DBMgr::FindGame( const char* connName, char* cookieBuf, int bufLen,
int* langP, int* nPlayersTP, int* nPlayersHP, bool* isDead )
@ -131,12 +165,14 @@ DBMgr::FindGame( const char* connName, char* cookieBuf, int bufLen,
const char* fmt = "SELECT cid, room, lang, nTotal, nPerDevice, dead FROM "
GAMES_TABLE " WHERE connName = '%s'"
" LIMIT 1";
// " LIMIT 1"
;
string query;
string_printf( query, fmt, connName );
logf( XW_LOGINFO, "query: %s", query.c_str() );
PGresult* result = PQexec( getThreadConn(), query.c_str() );
assert( 1 >= PQntuples( result ) );
if ( 1 == PQntuples( result ) ) {
cid = atoi( PQgetvalue( result, 0, 0 ) );
snprintf( cookieBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) );
@ -324,10 +360,10 @@ 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 = (DevIDRelay)random();
if ( DEVID_NONE == devID ) {
continue;
}
do {
devID = (DevIDRelay)random();
} while ( DEVID_NONE == devID );
const char* command = "INSERT INTO " DEVICES_TABLE
" (id, devType, devid)"
" VALUES( $1, $2, $3 )";
@ -383,13 +419,26 @@ DBMgr::AddDevice( const char* connName, HostID curID, int clientVersion,
HostID newID = curID;
if ( newID == HOST_ID_NONE ) {
int arr[4] = {0};
readArray( connName, arr );
int ackArr[4] = {0};
int seedArr[4] = {0};
readArray( connName, "nPerDevice", ackArr );
readArray( connName, "seeds", seedArr );
// If our seed's already there, grab that slot. Otherwise grab the
// first empty one.
HostID firstEmpty = HOST_ID_NONE;
for ( newID = HOST_ID_SERVER; newID <= 4; ++newID ) {
if ( arr[newID-1] == 0 ) {
if ( seedArr[newID-1] == seed ) {
break;
} else if ( HOST_ID_NONE == firstEmpty && 0 == ackArr[newID-1] ) {
firstEmpty = newID;
}
}
if ( 4 < newID && HOST_ID_NONE != firstEmpty ) {
newID = firstEmpty;
}
logf( XW_LOGINFO, "%s: set newID = %d", __func__, newID );
}
assert( newID <= 4 );
@ -537,10 +586,10 @@ DBMgr::RecordSent( const char* const connName, HostID hid, int nBytes )
{
assert( hid >= 0 && hid <= 4 );
const char* fmt = "UPDATE " GAMES_TABLE " SET"
" nsent = nsent + %d, mtimes[%d] = 'now'"
" nsents[%d] = nsents[%d] + %d, mtimes[%d] = 'now'"
" WHERE connName = '%s'";
string query;
string_printf( query, fmt, nBytes, hid, connName );
string_printf( query, fmt, hid, hid, nBytes, hid, connName );
logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() );
execSql( query );
@ -674,8 +723,15 @@ DBMgr::TokenFor( const char* const connName, int hid, DevIDRelay* devid,
}
}
PQclear( result );
logf( XW_LOGINFO, "%s(%s,%d)=>%s (%d, %d)", __func__, connName, hid,
(found?"true":"false"), *devid, *token );
if ( found ) {
logf( XW_LOGINFO, "%s(%s,%d)=>true (%d, %d)", __func__, connName, hid,
*devid, *token );
} else {
logf( XW_LOGINFO, "%s(%s,%d)=>false", __func__, connName, hid );
}
return found;
}
@ -699,27 +755,36 @@ DBMgr::execSql( const string& query )
bool
DBMgr::execSql( const char* const query )
{
PGresult* result = PQexec( getThreadConn(), query );
bool ok = PGRES_COMMAND_OK == PQresultStatus(result);
if ( !ok ) {
logf( XW_LOGERROR, "PQexec=>%s;%s", PQresStatus(PQresultStatus(result)), PQresultErrorMessage(result) );
bool ok = false;
for ( int ii = 0; !ok && ii < 3; ++ii ) {
PGresult* result = PQexec( getThreadConn(), query );
ok = PGRES_COMMAND_OK == PQresultStatus(result);
if ( !ok ) {
logf( XW_LOGERROR, "%s: PQexec=>%s;%s", __func__,
PQresStatus(PQresultStatus(result)),
PQresultErrorMessage(result) );
clearThreadConn();
usleep( 20000 );
}
PQclear( result );
}
PQclear( result );
assert( ok );
return ok;
}
void
DBMgr::readArray( const char* const connName, int arr[] ) /* len 4 */
DBMgr::readArray( const char* const connName, const char* column, int arr[] ) /* len 4 */
{
const char* fmt = "SELECT nPerDevice FROM " GAMES_TABLE " WHERE connName='%s'";
const char* fmt = "SELECT %s FROM " GAMES_TABLE " WHERE connName='%s'";
string query;
string_printf( query, fmt, connName );
string_printf( query, fmt, column, connName );
logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() );
PGresult* result = PQexec( getThreadConn(), query.c_str() );
assert( 1 == PQntuples( result ) );
const char* arrStr = PQgetvalue( result, 0, 0 );
logf( XW_LOGINFO, "%s: arrStr=\"%s\"", __func__, arrStr );
sscanf( arrStr, "{%d,%d,%d,%d}", &arr[0], &arr[1], &arr[2], &arr[3] );
PQclear( result );
}
@ -727,15 +792,16 @@ DBMgr::readArray( const char* const connName, int arr[] ) /* len 4 */
DevIDRelay
DBMgr::getDevID( const char* connName, int hid )
{
DevIDRelay devID;
DevIDRelay devID = DEVID_NONE;
const char* fmt = "SELECT devids[%d] FROM " GAMES_TABLE " WHERE connName='%s'";
string query;
string_printf( query, fmt, hid, connName );
logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() );
PGresult* result = PQexec( getThreadConn(), query.c_str() );
assert( 1 == PQntuples( result ) );
devID = (DevIDRelay)strtoul( PQgetvalue( result, 0, 0 ), NULL, 10 );
if ( 1 == PQntuples( result ) ) {
devID = (DevIDRelay)strtoul( PQgetvalue( result, 0, 0 ), NULL, 10 );
}
PQclear( result );
return devID;
}
@ -755,20 +821,22 @@ DBMgr::getDevID( const DevID* devID )
string_printf( query, fmt, cur );
}
} else {
const char* fmt = "SELECT id FROM " DEVICES_TABLE " WHERE devtype=%d and devid = '%s'";
const char* fmt = "SELECT id FROM " DEVICES_TABLE
" WHERE devtype=%d and devid = '%s'";
string_printf( query, fmt, devIDType, devID->m_devIDString.c_str() );
}
if ( 0 < query.size() ) {
logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() );
PGresult* result = PQexec( getThreadConn(), query.c_str() );
assert( 1 >= PQntuples( result ) );
if ( 1 == PQntuples( result ) ) {
int nTuples = PQntuples( result );
assert( 1 >= nTuples );
if ( 1 == nTuples ) {
rDevID = (DevIDRelay)strtoul( PQgetvalue( result, 0, 0 ), NULL, 10 );
}
PQclear( result );
}
logf( XW_LOGINFO, "%s(in=%s)=>%d (0x.8X)", __func__,
logf( XW_LOGINFO, "%s(in=%s)=>%d (0x%.8X)", __func__,
devID->m_devIDString.c_str(), rDevID, rDevID );
return rDevID;
}
@ -839,11 +907,15 @@ DBMgr::StoreMessage( const char* const connName, int hid,
const unsigned char* buf, int len )
{
DevIDRelay devID = getDevID( connName, hid );
if ( DEVID_NONE == devID ) {
logf( XW_LOGERROR, "%s: warning: devid not found for connName=%s, hid=%d",
__func__, connName, hid );
}
size_t newLen;
const char* fmt = "INSERT INTO " MSGS_TABLE
" (connname, hid, devid, token, %s, msglen)"
" VALUES( '%s', %d, %d, "
const char* fmt = "INSERT INTO " MSGS_TABLE " "
"(connname, hid, devid, token, %s, msglen) "
"VALUES( '%s', %d, %d, "
"(SELECT tokens[%d] from " GAMES_TABLE " where connname='%s'), "
"%s'%s', %d)";
@ -855,7 +927,7 @@ DBMgr::StoreMessage( const char* const connName, int hid,
g_free( b64 );
} else {
unsigned char* bytes = PQescapeByteaConn( getThreadConn(), buf,
len, &newLen );
len, &newLen );
assert( NULL != bytes );
string_printf( query, fmt, "msg", connName, hid, devID, hid, connName,
@ -1028,7 +1100,6 @@ DBMgr::getCountWhere( const char* table, string& test )
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;
}
@ -1073,6 +1144,18 @@ destr_function( void* conn )
PQfinish( pgconn );
}
void
DBMgr::clearThreadConn()
{
logf( XW_LOGERROR, "%s called()", __func__ );
PGconn* conn = (PGconn*)pthread_getspecific( m_conn_key );
if ( NULL != conn ) {
PQfinish( conn );
int result = pthread_setspecific( m_conn_key, NULL );
assert( 0 == result );
}
}
PGconn*
DBMgr::getThreadConn( void )
{
@ -1080,12 +1163,19 @@ DBMgr::getThreadConn( void )
if ( NULL == conn ) {
char buf[128];
int len = snprintf( buf, sizeof(buf), "dbname = " );
if ( !RelayConfigs::GetConfigs()->
GetValueFor( "DB_NAME", &buf[len], sizeof(buf)-len ) ) {
int port;
if ( !RelayConfigs::GetConfigs()->GetValueFor( "DB_NAME", buf,
sizeof(buf) ) ) {
assert( 0 );
}
conn = PQconnectdb( buf );
if ( !RelayConfigs::GetConfigs()->GetValueFor( "DB_PORT", &port ) ) {
assert( 0 );
}
string params;
string_printf( params, "dbname = %s ", buf );
string_printf( params, "port = %d ", port );
conn = PQconnectdb( params.c_str() );
pthread_setspecific( m_conn_key, conn );
}
return conn;

View file

@ -54,6 +54,11 @@ class DBMgr {
int* langP, int* nPlayersTP, int* nPlayersHP,
bool* isDead );
bool FindGameFor( const char* connName, char* cookieBuf, int bufLen,
unsigned short seed, HostID hid,
int nPlayersH, int nPlayersS,
int* langP, bool* isDead, CookieID* cidp );
bool SeenSeed( const char* cookie, unsigned short seed,
int langCode, int nPlayersT, bool wantsPublic,
char* connNameBuf, int bufLen, int* nPlayersHP,
@ -121,7 +126,7 @@ class DBMgr {
DBMgr();
bool execSql( const string& query );
bool execSql( const char* const query ); /* no-results query */
void readArray( const char* const connName, int arr[] );
void readArray( const char* const connName, const char* column, int arr[] );
DevIDRelay getDevID( const char* connName, int hid );
DevIDRelay getDevID( const DevID* devID );
int getCountWhere( const char* table, string& test );
@ -130,6 +135,7 @@ class DBMgr {
int byteaIndex, unsigned char* buf, size_t* buflen );
PGconn* getThreadConn( void );
void clearThreadConn();
void conn_key_alloc();
pthread_key_t m_conn_key;

View file

@ -21,6 +21,7 @@
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <assert.h>
#include <algorithm>

View file

@ -144,8 +144,8 @@ do_rooms( int sockfd, int lang, int nPlayers )
{
unsigned char msg[] = { 0, /* protocol */
PRX_PUB_ROOMS,
lang,
nPlayers };
(unsigned char)lang,
(unsigned char)nPlayers };
unsigned short len = htons( sizeof(msg) );
write( sockfd, &len, sizeof(len) );
write( sockfd, msg, sizeof(msg) );
@ -184,7 +184,7 @@ write_connnames( int sockfd, char cmd,
len += 1 + strlen( connNames[ii] );
}
unsigned char hdr[] = { 0, cmd };
unsigned char hdr[] = { 0, (unsigned char)cmd };
unsigned short netNConnNames = htons( nConnNames );
netlen = sizeof(hdr) + sizeof( netNConnNames ) + len;
netlen = htons( netlen );
@ -234,6 +234,22 @@ connect_socket( void )
to_sock.sin_family = AF_INET;
to_sock.sin_port = htons( g_port );
struct timeval tv;
tv.tv_sec = 5; /* seconds */
tv.tv_usec = 0; /* microseconds */
int result = setsockopt( sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv) );
if ( 0 != result ) {
fprintf( stderr, "setsockopt=>%d (%s)", errno, strerror(errno) );
assert( 0 );
}
result = setsockopt( sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv) );
if ( 0 != result ) {
fprintf( stderr, "setsockopt=>%d (%s)", errno, strerror(errno) );
assert( 0 );
}
struct hostent* hostip;
hostip = gethostbyname( g_host );
memcpy( &(to_sock.sin_addr.s_addr), hostip->h_addr_list[0],
@ -277,6 +293,7 @@ do_fetch( int sockfd, const char** connNames, int nConnNames,
unsigned char reply[1024];
int nRead = read_packet( sockfd, reply, sizeof(reply) );
if ( nRead > 2 ) {
int ii;
const unsigned char* bufp = reply;
const unsigned char* const end = bufp + nRead;
@ -292,7 +309,7 @@ do_fetch( int sockfd, const char** connNames, int nConnNames,
STDOUT -- e.g. by passing in named pipes to correspond to each
deviceid provided */
for ( int ii = 0; ii < count && bufp < end; ++ii ) {
for ( ii = 0; ii < count && bufp < end; ++ii ) {
int fd = STDOUT_FILENO;
int nbsfd = -1;
unsigned short countPerDev;
@ -350,7 +367,6 @@ do_fetch( int sockfd, const char** connNames, int nConnNames,
nwritten = write( fd, &len, sizeof(len) );
assert( nwritten == sizeof(len) );
int ii;
for ( ii = 0; -1 != nbsfd; ++ii ) {
short len;
ssize_t nRead = read( nbsfd, &len, sizeof(len) );

View file

@ -112,7 +112,7 @@ def asGCMIds(con, devids, typ):
def notifyGCM( devids, typ, target ):
success = False
if typ == DEVTYPE_GCM:
if 3 <= target['clntVers']:
if 3 <= target['clntVers'] and target['msg64']:
connname = "%s/%d" % (target['connname'], target['hid'])
data = { 'msgs64': [ target['msg64'] ],
'connname': connname,
@ -235,8 +235,9 @@ def main():
toDelete = []
for devid in targets.keys():
target = targets[devid]
if notifyGCM( asGCMIds(g_con, [devid], typ), typ, target )\
and 3 <= target['clntVers']:
if notifyGCM( asGCMIds(g_con, [devid], typ), typ, target ) \
and 3 <= target['clntVers'] \
and target['msg64']:
toDelete.append( str(target['id']) )
pruneSent( devids )
deleteMsgs( g_con, toDelete )

View file

@ -99,7 +99,7 @@ $cols = array( new Column("dead", "D", "capitalize", false ),
new Column("nperdevice", "NP", "identity", true ),
new Column("ack", "A", "identity", true ),
new Column("devids", "DevIDs", "identity", true ),
new Column("nsent", "Sent", "identity", false ),
new Column("nsents", "Sent", "identity", true ),
new Column("addrs", "Dev. addr", "ip_to_host", true ),
new Column("ctime", "Created", "print_date", false ),
new Column("mtimes", "Last contact", "print_date", true ),

View file

@ -27,7 +27,7 @@ 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 as cv ,ntotal,nperdevice,seeds,addrs,tokens,devids,ack,nsent as snt "\
echo "SELECT dead,connname,cid,room,lang as lg,clntVers as cv ,ntotal as tot,nperdevice as nPerDev,seeds,tokens,ack,nsents as snts "\
"FROM games $QUERY ORDER BY NOT dead, connname LIMIT $LIMIT;" \
| psql xwgames

View file

@ -29,6 +29,7 @@
TimerMgr::TimerMgr()
: m_nextFireTime(0)
,m_nextID(0)
{
pthread_mutex_init( &m_timersMutex, NULL );
}
@ -44,17 +45,18 @@ TimerMgr::GetTimerMgr()
}
void
TimerMgr::SetTimer( time_t inMillis, TimerProc proc, void* closure,
TimerMgr::SetTimer( time_t inSeconds, TimerProc proc, void* closure,
int interval )
{
logf( XW_LOGINFO, "%s: uptime = %ld", __func__, uptime() );
TimerInfo ti;
ti.proc = proc;
ti.closure = closure;
ti.when = uptime() + inMillis;
ti.when = uptime() + inSeconds;
ti.interval = interval;
MutexLock ml( &m_timersMutex );
ti.id = ++m_nextID;
if ( getTimer( proc, closure ) ) {
logf( XW_LOGINFO, "%s: clearing old timer", __func__ );
@ -68,7 +70,7 @@ TimerMgr::SetTimer( time_t inMillis, TimerProc proc, void* closure,
}
time_t
TimerMgr::GetPollTimeout()
TimerMgr::GetPollTimeoutMillis()
{
MutexLock ml( &m_timersMutex );
@ -80,10 +82,10 @@ TimerMgr::GetPollTimeout()
if ( tout < 0 ) {
tout = 0;
}
tout *= 1000;
tout *= 1000; /* convert to milliseconds */
}
return tout;
} /* GetPollTimeout */
} /* GetPollTimeoutMillis */
bool
TimerMgr::getTimer( TimerProc proc, void* closure )
@ -145,6 +147,7 @@ TimerMgr::FireElapsedTimers()
vector<TimerProc> procs;
vector<void*> closures;
vector<uint32_t> ids;
{
MutexLock ml( &m_timersMutex );
/* loop until we get through without firing a single one. Only fire one
@ -157,6 +160,7 @@ TimerMgr::FireElapsedTimers()
procs.push_back(tip->proc);
closures.push_back(tip->closure);
ids.push_back(tip->id);
if ( tip->interval ) {
tip->when += tip->interval;
@ -167,10 +171,12 @@ TimerMgr::FireElapsedTimers()
}
}
vector<TimerProc>::iterator iter1 = procs.begin();
vector<void*>::iterator iter2 = closures.begin();
while ( iter1 != procs.end() ) {
(*iter1++)(*iter2++);
vector<TimerProc>::const_iterator procs_iter = procs.begin();
vector<void*>::const_iterator closures_iter = closures.begin();
vector<uint32_t>::const_iterator ids_iter = ids.begin();
while ( procs_iter != procs.end() ) {
logf( XW_LOGINFO, "%s: firing timer id=%d", __func__, *ids_iter++ );
(*procs_iter++)(*closures_iter++);
}
MutexLock ml( &m_timersMutex );
@ -184,6 +190,7 @@ TimerMgr::clearTimerImpl( TimerProc proc, void* closure )
for ( iter = m_timers.begin(); iter != m_timers.end(); ++iter ) {
TimerInfo* tip = &(*iter);
if ( tip->proc == proc && tip->closure == closure ) {
logf( XW_LOGINFO, "clearing timer id=%d", tip->id );
m_timers.erase(iter);
break;
}

View file

@ -41,7 +41,7 @@ class TimerMgr {
int interval ); /* 0 means non-recurring */
void ClearTimer( TimerProc proc, void* closure );
time_t GetPollTimeout();
time_t GetPollTimeoutMillis();
void FireElapsedTimers();
private:
@ -51,6 +51,7 @@ class TimerMgr {
void* closure;
time_t when;
int interval;
uint32_t id;
} TimerInfo;
@ -65,6 +66,7 @@ class TimerMgr {
list<TimerInfo> m_timers;
time_t m_nextFireTime;
uint32_t m_nextID;
};
#endif

View file

@ -118,18 +118,36 @@ void
XWThreadPool::AddSocket( SockType stype, QueueCallback proc, const AddrInfo* from )
{
{
int sock = from->socket();
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__,
m_activeSockets.insert( pair<int, SockInfo>( sock, si ) );
logf( XW_LOGINFO, "%s(sock=%d): %d sockets active", __func__, sock,
m_activeSockets.size() );
}
interrupt_poll();
}
bool
XWThreadPool::SocketFound( const AddrInfo* addr )
{
assert( addr->isTCP() );
bool found = false;
{
RWWriteLock ml( &m_activeSocketsRWLock );
map<int, SockInfo>::iterator iter = m_activeSockets.find( addr->socket() );
if ( m_activeSockets.end() != iter
&& iter->second.m_addr.equals( *addr ) ) {
found = true;
}
}
return found;
}
bool
XWThreadPool::RemoveSocket( const AddrInfo* addr )
{
@ -138,20 +156,15 @@ XWThreadPool::RemoveSocket( const AddrInfo* addr )
{
RWWriteLock ml( &m_activeSocketsRWLock );
logf( XW_LOGINFO, "%s: START: %d sockets active", __func__,
m_activeSockets.size() );
size_t prevSize = m_activeSockets.size();
vector<SockInfo>::iterator iter;
for ( iter = m_activeSockets.begin();
iter != m_activeSockets.end(); ++iter ) {
if ( iter->m_addr.equals( *addr ) ) {
m_activeSockets.erase( iter );
found = true;
break;
}
map<int, SockInfo>::iterator iter = m_activeSockets.find( addr->socket() );
if ( m_activeSockets.end() != iter && iter->second.m_addr.equals( *addr ) ) {
m_activeSockets.erase( iter );
found = true;
}
logf( XW_LOGINFO, "%s: AFTER: %d sockets active", __func__,
m_activeSockets.size() );
logf( XW_LOGINFO, "%s: AFTER: %d sockets active (was %d)", __func__,
m_activeSockets.size(), prevSize );
}
return found;
} /* RemoveSocket */
@ -159,7 +172,6 @@ XWThreadPool::RemoveSocket( const AddrInfo* addr )
void
XWThreadPool::CloseSocket( const AddrInfo* addr )
{
/* bool do_interrupt = false; */
assert( addr->isTCP() );
if ( !RemoveSocket( addr ) ) {
MutexLock ml( &m_queueMutex );
@ -167,7 +179,6 @@ XWThreadPool::CloseSocket( const AddrInfo* addr )
while ( iter != m_queue.end() ) {
if ( iter->m_info.m_addr.equals( *addr ) ) {
m_queue.erase( iter );
/* do_interrupt = true; */
break;
}
++iter;
@ -175,13 +186,12 @@ XWThreadPool::CloseSocket( const AddrInfo* addr )
}
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
will be in the list being listened to. That or we need to drop sockets
that have been removed on some other thread while the poll call's
blocking.*/
interrupt_poll();
/* } */
}
void
@ -196,29 +206,28 @@ XWThreadPool::EnqueueKill( const AddrInfo* addr, const char* const why )
}
}
// return true if the addr passed in has a timestamp >= what we have as the
// creation time of the now-open socket. If the socket isn't open, return false.
bool
XWThreadPool::get_process_packet( SockType stype, QueueCallback proc, const AddrInfo* addr )
XWThreadPool::IsCurrent( const AddrInfo* addr )
{
bool success = false;
short packetSize;
assert( sizeof(packetSize) == 2 );
// Fix this to return an allocated buffer
unsigned char buf[MAX_MSG_LEN+1];
int nRead = read_packet( addr->socket(), buf, sizeof(buf) );
if ( nRead < 0 ) {
EnqueueKill( addr, "bad packet" );
} else if ( STYPE_PROXY == stype && NULL != proc ) {
buf[nRead] = '\0';
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);
bool result = false;
bool sockFound = false; // for debugging
int sock = addr->socket();
if ( -1 != sock ) {
RWReadLock ml( &m_activeSocketsRWLock );
map<int, SockInfo>::const_iterator iter = m_activeSockets.find( sock );
if ( iter != m_activeSockets.end() ) {
assert( !sockFound );
sockFound = true;
result = iter->second.m_addr.created() <= addr->created();
logf( XW_LOGINFO, "%s(sock=%d)=>%d (%lx vs %lx)",
__func__, sock, result,
iter->second.m_addr.created(), addr->created() );
}
}
return success;
} /* get_process_packet */
return result;
}
void*
XWThreadPool::tpool_main( void* closure )
@ -261,10 +270,11 @@ XWThreadPool::real_tpool_main( ThreadInfo* tip )
logf( XW_LOGINFO, "worker thread got socket %d from queue", socket );
switch ( pr.m_act ) {
case Q_READ:
assert( socket >= 0 );
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 );
}
assert( 0 );
// assert( socket >= 0 );
// 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:
(*m_kFunc)( &pr.m_info.m_addr );
@ -332,11 +342,11 @@ XWThreadPool::real_listener()
#endif
++curfd;
vector<SockInfo>::iterator iter;
map<int, SockInfo>::iterator iter;
for ( iter = m_activeSockets.begin(); iter != m_activeSockets.end();
++iter ) {
fds[curfd].fd = iter->m_addr.socket();
sinfos[curfd] = *iter;
fds[curfd].fd = iter->first;
sinfos[curfd] = iter->second;
fds[curfd].events = flags;
#ifdef LOG_POLL
if ( logCapacity > logLen ) {
@ -349,7 +359,7 @@ XWThreadPool::real_listener()
}
pthread_rwlock_unlock( &m_activeSocketsRWLock );
int nMillis = tmgr->GetPollTimeout();
int nMillis = tmgr->GetPollTimeoutMillis();
#ifdef LOG_POLL
logf( XW_LOGINFO, "polling %s nmillis=%d", log, nMillis );
@ -387,10 +397,12 @@ XWThreadPool::real_listener()
for ( ii = 0; ii < nSockets && nEvents > 0; ++ii ) {
if ( fds[curfd].revents != 0 ) {
int socket = fds[curfd].fd;
const AddrInfo* addr = &sinfos[curfd].m_addr;
assert( socket == addr->socket() );
if ( !RemoveSocket( addr ) ) {
// int socket = fds[curfd].fd;
SockInfo* sinfo = &sinfos[curfd];
const AddrInfo* addr = &sinfo->m_addr;
assert( fds[curfd].fd == addr->socket() );
if ( !SocketFound( addr ) ) {
/* no further processing if it's been removed while
we've been sleeping in poll */
--nEvents;
@ -398,10 +410,14 @@ XWThreadPool::real_listener()
}
if ( 0 != (fds[curfd].revents & (POLLIN | POLLPRI)) ) {
enqueue( sinfos[curfd] );
if ( !UdpQueue::get()->handle( addr, sinfo->m_proc ) ) {
RemoveSocket( addr );
EnqueueKill( addr, "bad packet" );
}
} else {
logf( XW_LOGERROR, "odd revents: %x",
fds[curfd].revents );
RemoveSocket( addr );
EnqueueKill( addr, "error/hup in poll()" );
}
--nEvents;
@ -450,7 +466,8 @@ XWThreadPool::grab_elem_locked( QueuePr* prp )
for ( iter = m_queue.begin(); !found && iter != m_queue.end(); ++iter ) {
int socket = iter->m_info.m_addr.socket();
/* If NOT found */
if ( m_sockets_in_use.end() == m_sockets_in_use.find( socket ) ) {
if ( -1 != socket
&& m_sockets_in_use.end() == m_sockets_in_use.find( socket ) ) {
*prp = *iter;
m_queue.erase( iter ); /* this was a double-free once! */
m_sockets_in_use.insert( socket );
@ -481,10 +498,10 @@ XWThreadPool::print_in_use( void )
for ( iter = m_sockets_in_use.begin();
iter != m_sockets_in_use.end(); ++iter ) {
string_printf( str, "%d ", *iter );
string_printf( str, "%d ", *iter );
}
if ( 0 < str.size() ) {
logf( XW_LOGINFO, "Sockets in use: %s", str.c_str() );
logf( XW_LOGINFO, "Sockets in use: %s", str.c_str() );
}
}

View file

@ -68,12 +68,16 @@ class XWThreadPool {
void EnqueueKill( const AddrInfo* addr, const char* const why );
bool IsCurrent( const AddrInfo* addr );
private:
typedef enum { Q_READ, Q_KILL } QAction;
typedef struct { QAction m_act; SockInfo m_info; } QueuePr;
/* Remove from set being listened on */
bool RemoveSocket( const AddrInfo* addr );
/* test if is in set being listened on */
bool SocketFound( const AddrInfo* addr );
void enqueue( QAction act = Q_READ );
void enqueue( SockInfo si, QAction act = Q_READ );
@ -92,7 +96,7 @@ class XWThreadPool {
static void* listener_main( void* closure );
/* Sockets main thread listens on */
vector<SockInfo>m_activeSockets;
map<int, SockInfo>m_activeSockets;
pthread_rwlock_t m_activeSocketsRWLock;
/* Sockets waiting for a thread to read 'em */

View file

@ -1,4 +1,4 @@
/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */
/* -*- compile-command: "make -j3"; -*- */
/*
* Copyright 2013 by Eric House (xwords@eehouse.org). All rights reserved.
*
@ -17,6 +17,7 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <unistd.h>
#include "udpack.h"
#include "mlock.h"

View file

@ -1,7 +1,7 @@
/* -*- compile-command: "make -k -j3"; -*- */
/*
* Copyright 2010-2012 by Eric House (xwords@eehouse.org). All rights
* Copyright 2010-2013 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
@ -19,6 +19,7 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <errno.h>
#include "udpqueue.h"
#include "mlock.h"
@ -36,8 +37,43 @@ UdpThreadClosure::logStats()
}
}
bool
PartialPacket::stillGood() const
{
return 0 == m_errno
|| EAGAIN == m_errno
|| EWOULDBLOCK == m_errno;
}
bool
PartialPacket::readAtMost( int len )
{
bool success = false;
uint8_t tmp[len];
ssize_t nRead = recv( m_sock, tmp, len, 0 );
if ( 0 > nRead ) { // error case
m_errno = errno;
if ( !stillGood() ) {
logf( XW_LOGERROR, "%s(len=%d, socket=%d): recv failed: %d (%s)", __func__,
len, m_sock, m_errno, strerror(m_errno) );
}
} else if ( 0 == nRead ) { // remote socket closed
logf( XW_LOGINFO, "%s: remote closed (socket=%d)", __func__, m_sock );
m_errno = -1; // so stillGood will fail
} else {
m_errno = 0;
success = len == nRead;
int curSize = m_buf.size();
m_buf.resize( nRead + curSize );
memcpy( &m_buf[curSize], tmp, nRead );
}
return success;
}
UdpQueue::UdpQueue()
{
m_nextID = 0;
pthread_mutex_init ( &m_partialsMutex, NULL );
pthread_mutex_init ( &m_queueMutex, NULL );
pthread_cond_init( &m_queueCondVar, NULL );
@ -52,6 +88,7 @@ UdpQueue::~UdpQueue()
{
pthread_cond_destroy( &m_queueCondVar );
pthread_mutex_destroy ( &m_queueMutex );
pthread_mutex_destroy ( &m_partialsMutex );
}
UdpQueue*
@ -63,16 +100,91 @@ UdpQueue::get()
return s_instance;
}
// return false if socket should no longer be used
bool
UdpQueue::handle( const AddrInfo* addr, QueueCallback cb )
{
PartialPacket* packet;
bool success = true;
int sock = addr->socket();
// Hang onto this mutex for as long as we may be writing to the packet
// since having it deleted while in use would be bad.
MutexLock ml( &m_partialsMutex );
map<int, PartialPacket*>::iterator iter = m_partialPackets.find( sock );
if ( m_partialPackets.end() == iter ) {
packet = new PartialPacket( sock );
m_partialPackets.insert( pair<int, PartialPacket*>( sock, packet ) );
} else {
packet = iter->second;
}
// First see if we've read the length bytes
if ( packet->readSoFar() < sizeof( packet->m_len ) ) {
if ( packet->readAtMost( sizeof(packet->m_len) - packet->readSoFar() ) ) {
uint16_t tmp;
memcpy( &tmp, packet->data(), sizeof(tmp) );
packet->m_len = ntohs(tmp);
success = 0 < packet->m_len;
}
}
if ( success && packet->readSoFar() >= sizeof( packet->m_len ) ) {
assert( 0 < packet->m_len );
int leftToRead =
packet->m_len - (packet->readSoFar() - sizeof(packet->m_len));
if ( packet->readAtMost( leftToRead ) ) {
handle( addr, packet->data() + sizeof(packet->m_len),
packet->m_len, cb );
packet = NULL;
newSocket_locked( sock );
}
}
success = success && (NULL == packet || packet->stillGood());
return success;
}
void
UdpQueue::handle( const AddrInfo* addr, unsigned char* buf, int len,
UdpQueue::handle( const AddrInfo* addr, const uint8_t* buf, int len,
QueueCallback cb )
{
UdpThreadClosure* utc = new UdpThreadClosure( addr, buf, len, cb );
MutexLock ml( &m_queueMutex );
int id = ++m_nextID;
utc->setID( id );
logf( XW_LOGINFO, "%s: enqueuing packet %d (socket %d, len %d)",
__func__, id, addr->socket(), len );
m_queue.push_back( utc );
pthread_cond_signal( &m_queueCondVar );
}
void
UdpQueue::newSocket_locked( int sock )
{
map<int, PartialPacket*>::iterator iter = m_partialPackets.find( sock );
if ( m_partialPackets.end() != iter ) {
delete iter->second;
m_partialPackets.erase( iter );
}
}
void
UdpQueue::newSocket( int sock )
{
MutexLock ml( &m_partialsMutex );
newSocket_locked( sock );
}
void
UdpQueue::newSocket( const AddrInfo* addr )
{
assert( addr->isTCP() );
newSocket( addr->socket() );
}
void*
UdpQueue::thread_main()
{
@ -83,9 +195,12 @@ UdpQueue::thread_main()
}
UdpThreadClosure* utc = m_queue.front();
m_queue.pop_front();
pthread_mutex_unlock( &m_queueMutex );
utc->noteDequeued();
logf( XW_LOGINFO, "%s: dispatching packet %d (socket %d)", __func__,
utc->getID(), utc->addr()->socket() );
(*utc->cb())( utc );
utc->logStats();
delete utc;

View file

@ -23,6 +23,7 @@
#include <pthread.h>
#include <deque>
#include <map>
#include "xwrelay_priv.h"
#include "addrinfo.h"
@ -35,9 +36,9 @@ typedef void (*QueueCallback)( UdpThreadClosure* closure );
class UdpThreadClosure {
public:
UdpThreadClosure( const AddrInfo* addr, unsigned char* buf,
UdpThreadClosure( const AddrInfo* addr, const uint8_t* buf,
int len, QueueCallback cb )
: m_buf(new unsigned char[len])
: m_buf(new uint8_t[len])
, m_len(len)
, m_addr(*addr)
, m_cb(cb)
@ -46,7 +47,7 @@ public:
memcpy( m_buf, buf, len );
}
~UdpThreadClosure() { delete m_buf; }
~UdpThreadClosure() { delete[] m_buf; }
const unsigned char* buf() const { return m_buf; }
int len() const { return m_len; }
@ -55,14 +56,37 @@ public:
void noteDequeued() { m_dequed = time( NULL ); }
void logStats();
const QueueCallback cb() const { return m_cb; }
void setID( int id ) { m_id = id; }
int getID( void ) { return m_id; }
private:
unsigned char* m_buf;
uint8_t* m_buf;
int m_len;
AddrInfo m_addr;
QueueCallback m_cb;
time_t m_created;
time_t m_dequed;
int m_id;
};
class PartialPacket {
public:
PartialPacket(int sock)
:m_len(0)
,m_sock(sock)
,m_errno(0)
{}
bool stillGood() const ;
bool readAtMost( int len );
size_t readSoFar() const { return m_buf.size(); }
const uint8_t* data() const { return m_buf.data(); }
unsigned short m_len; /* decoded via ntohs from the first 2 bytes */
private:
vector<uint8_t> m_buf;
int m_sock;
int m_errno;
};
class UdpQueue {
@ -70,17 +94,24 @@ class UdpQueue {
static UdpQueue* get();
UdpQueue();
~UdpQueue();
void handle( const AddrInfo* addr, unsigned char* buf, int len,
bool handle( const AddrInfo* addr, QueueCallback cb );
void handle( const AddrInfo* addr, const uint8_t* buf, int len,
QueueCallback cb );
void newSocket( int socket );
void newSocket( const AddrInfo* addr );
private:
void newSocket_locked( int sock );
static void* thread_main_static( void* closure );
void* thread_main();
pthread_mutex_t m_partialsMutex;
pthread_mutex_t m_queueMutex;
pthread_cond_t m_queueCondVar;
deque<UdpThreadClosure*> m_queue;
// map<int, vector<UdpThreadClosure*> > m_bySocket;
int m_nextID;
map<int, PartialPacket*> m_partialPackets;
};
#endif

View file

@ -31,6 +31,8 @@ UDPPORT=10997
# default 5
SOCK_TIMEOUT_SECONDS=5
# How many tcp sockets at once (to prevent leaks). default: 100
MAXSOCKS=256
# And the control port is?
CTLPORT=11000
@ -54,6 +56,8 @@ SERVERNAME=eehouse.org
# name of the database. (Table names are hard-coded.)
DB_NAME=xwgames
# UDP port postgres server is listening on
DB_PORT=5433
# Initial level of logging. See xwrelay_priv.h for values. Currently
# 0 means errors only, 1 info, 2 verbose and 3 very verbose.

View file

@ -203,16 +203,30 @@ parseRelayID( const unsigned char** const inp, const unsigned char* const end,
if ( ok ) {
strncpy( buf, (char*)*inp, connNameLen );
buf[connNameLen] = '\0';
*hid = atoi( hidp+1 );
char* endptr;
*hid = strtol( hidp + 1, &endptr, 10 );
if ( '\n' == *endptr ) {
++endptr;
++hidp; // skip '/'
*hid = *hidp - '0'; // assume it's one byte, as should be in range '0'--'4'
// logf( XW_LOGERROR, "%s: read hid of %d from %s", __func__, *hid, hidp );
if ( *hid >= 0 && *hid <= 4 ) {
const char* endptr = hidp + 1;
if ( '\n' == *endptr ) {
++endptr;
}
*inp = (unsigned char*)endptr;
} else {
ok = false;
int len = end - *inp;
char buf[len+1];
memcpy( buf, *inp, len);
buf[len] = '\0';
logf( XW_LOGERROR, "%s: got bad hid %d from str \"%s\"", __func__,
*hid, buf );
}
*inp = (unsigned char*)endptr;
}
if ( !ok ) {
logf( XW_LOGERROR, "%s failed", __func__ );
logf( XW_LOGERROR, "%s failed", __func__ );
}
return ok;
}
@ -448,23 +462,32 @@ send_with_length_unsafe( const AddrInfo* addr, const unsigned char* buf,
{
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 );
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;
int socket = addr->socket();
if ( addr->isCurrent() ) {
unsigned short len = htons( bufLen );
ssize_t nSent = send( socket, &len, sizeof(len), 0 );
if ( nSent == sizeof(len) ) {
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 {
logf( XW_LOGERROR, "%s: send failed: %s (errno=%d)", __func__,
strerror(errno), errno );
}
}
} else {
logf( XW_LOGINFO, "%s: dropping packet: socket %d reused",
__func__, socket );
}
} else {
AddrInfo::ClientToken clientToken = addr->clientToken();
assert( 0 != clientToken );
clientToken = htonl(clientToken);
const struct sockaddr* saddr = addr->sockaddr();
int socket = addr->socket();
assert( g_udpsock == socket || socket == -1 );
if ( -1 == socket ) {
socket = g_udpsock;
@ -600,7 +623,7 @@ processReconnect( const unsigned char* bufp, int bufLen, const AddrInfo* addr )
cookie, srcID, addr, clientVersion, &devID,
nPlayersH, nPlayersT, gameSeed, langCode,
wantsPublic, makePublic );
success = scr.Reconnect( srcID, nPlayersH, nPlayersT, gameSeed,
success = scr.Reconnect( nPlayersH, nPlayersT, gameSeed,
&err );
// if ( !success ) {
// assert( err != XWRELAY_ERROR_NONE );
@ -695,14 +718,15 @@ forwardMessage( const unsigned char* buf, int buflen, const AddrInfo* addr )
if ( getNetShort( &bufp, end, &cookieID )
&& getNetByte( &bufp, end, &src )
&& getNetByte( &bufp, end, &dest ) ) {
logf( XW_LOGINFO, "cookieID = %d", cookieID );
&& getNetByte( &bufp, end, &dest )
&& 0 < src && 0 < dest ) {
if ( COOKIE_ID_NONE == cookieID ) {
SafeCref scr( addr );
success = scr.Forward( src, addr, dest, buf, buflen );
} else {
SafeCref scr( cookieID ); /* won't work if not allcon; will be 0 */
/* won't work if not allcon; will be 0 */
SafeCref scr( cookieID, true );
success = scr.Forward( src, addr, dest, buf, buflen );
}
}
@ -887,37 +911,6 @@ handlePipe( int sig )
logf( XW_LOGINFO, "%s", __func__ );
}
int
read_packet( int sock, unsigned char* buf, int buflen )
{
int result = -1;
ssize_t nread;
unsigned short msgLen;
nread = recv( sock, &msgLen, sizeof(msgLen), MSG_WAITALL );
if ( 0 == nread ) {
logf( XW_LOGINFO, "%s: recv => 0: remote closed", __func__ );
} else if ( nread != sizeof(msgLen) ) {
logf( XW_LOGERROR, "%s: first recv => %d: %s", __func__,
nread, strerror(errno) );
} else {
msgLen = ntohs( msgLen );
if ( msgLen >= buflen ) {
logf( XW_LOGERROR, "%s: buf too small; need %d but have %d",
__func__, msgLen, buflen );
} else {
nread = recv( sock, buf, msgLen, MSG_WAITALL );
if ( nread == msgLen ) {
result = nread;
} else {
logf( XW_LOGERROR, "%s: second recv failed: %s", __func__,
strerror(errno) );
}
}
}
return result;
} /* read_packet */
static void
pushShort( vector<unsigned char>& out, unsigned short num )
{
@ -1110,9 +1103,9 @@ handleProxyMsgs( int sock, const AddrInfo* addr, const unsigned char* bufp,
}
}
}
if ( end - bufp != 1 ) {
logf( XW_LOGERROR, "%s: buf != end: %p vs %p", __func__, bufp, end );
}
if ( end - bufp != 1 ) {
logf( XW_LOGERROR, "%s: buf != end: %p vs %p (+1)", __func__, bufp, end );
}
// assert( bufp == end ); // don't ship with this!!!
}
} // handleProxyMsgs
@ -1427,9 +1420,9 @@ udp_thread_proc( UdpThreadClosure* utc )
}
static void
handle_udp_packet( int udpsock )
read_udp_packet( int udpsock )
{
unsigned char buf[MAX_MSG_LEN];
uint8_t buf[MAX_MSG_LEN];
AddrInfo::AddrUnion saddr;
memset( &saddr, 0, sizeof(saddr) );
socklen_t fromlen = sizeof(saddr.addr_in);
@ -1449,17 +1442,17 @@ void
string_printf( string& str, const char* fmt, ... )
{
const int origsiz = str.size();
int newsiz = 100;
int addsiz = 100;
va_list ap;
for ( ; ; ) {
str.resize( origsiz + newsiz );
str.resize( origsiz + addsiz );
va_start( ap, fmt );
int len = vsnprintf( (char *)str.c_str() + origsiz, newsiz, fmt, ap );
int len = vsnprintf( (char *)str.c_str() + origsiz, addsiz, fmt, ap );
va_end( ap );
if ( len > newsiz ) { // needs more space
newsiz = len + 1;
if ( len >= addsiz ) { // needs more space
addsiz = len + 1;
} else if ( -1 == len ) {
assert(0); // should be impossible
} else {
@ -1469,6 +1462,8 @@ string_printf( string& str, const char* fmt, ... )
}
}
// Going with non-blocking instead
#if 0
static void
set_timeouts( int sock )
{
@ -1493,6 +1488,7 @@ set_timeouts( int sock )
assert( 0 );
}
}
#endif
static void
enable_keepalive( int sock )
@ -1500,7 +1496,7 @@ enable_keepalive( int sock )
int optval = 1;
if ( 0 > setsockopt( sock, SOL_SOCKET, SO_KEEPALIVE,
&optval, sizeof( optval ) ) ) {
logf( XW_LOGERROR, "setsockopt(SO_KEEPALIVE)=>%d (%s)", errno,
logf( XW_LOGERROR, "setsockopt(sock=%d, SO_KEEPALIVE)=>%d (%s)", sock, errno,
strerror(errno) );
assert( 0 );
}
@ -1918,15 +1914,17 @@ main( int argc, char** argv )
errno, strerror(errno) );
assert( 0 ); // we're leaking files or load has grown
} else {
// I've seen a bug where we accept but never service
// connections. Sockets are not closed, and so the
// number goes up. Probably need a watchdog instead,
// but this will work around it.
// I've seen a bug where we accept but never service
// connections. Sockets are not closed, and so the
// number goes up. Probably need a watchdog instead,
// but this will work around it.
assert( g_maxsocks > newSock );
/* Set timeout so send and recv won't block forever */
set_timeouts( newSock );
// set_timeouts( newSock );
int err = fcntl( newSock, F_SETFL, O_NONBLOCK );
assert( 0 == err );
enable_keepalive( newSock );
logf( XW_LOGINFO,
@ -1939,6 +1937,7 @@ main( int argc, char** argv )
perGame ? game_thread_proc
: proxy_thread_proc,
&addr );
UdpQueue::get()->newSocket( &addr );
}
--retval;
}
@ -1948,10 +1947,10 @@ main( int argc, char** argv )
// run_ctrl_thread( g_control );
--retval;
}
if ( FD_ISSET( g_udpsock, &rfds ) ) {
if ( -1 != g_udpsock && 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 );
read_udp_packet( g_udpsock );
--retval;
}
#ifdef DO_HTTP

View file

@ -53,7 +53,7 @@ cid integer
,nPerDevice INTEGER[]
,seeds INTEGER[]
,ack VARCHAR(1)[]
,nSent INTEGER DEFAULT 0
,nsents INTEGER[] DEFAULT '{0,0,0,0}'
,ctime TIMESTAMP (0) DEFAULT CURRENT_TIMESTAMP
,mtimes TIMESTAMP(0)[]
,addrs INET[]