mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2024-12-27 09:58:45 +01:00
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:
commit
d50c808f96
68 changed files with 1428 additions and 603 deletions
|
@ -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" />
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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·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'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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
}
|
|
@ -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 )
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 ) \
|
||||
|
|
|
@ -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 */
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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" );
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
|
|
@ -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) );
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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 ),
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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[]
|
||||
|
|
Loading…
Reference in a new issue