From a6d8708abce4a72223dd51836d6d6d7c0b1612a3 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 31 Aug 2017 20:07:40 -0700 Subject: [PATCH 01/66] sort unconnected games to top of list --- .../src/main/java/org/eehouse/android/xw4/DBUtils.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java index 5ac2fc34c..bc60ce11d 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java @@ -1622,8 +1622,12 @@ public class DBUtils { initDB( context ); String[] columns = { ROW_ID }; String selection = String.format( "%s=%d", DBHelper.GROUPID, groupID ); - String orderBy = String.format( "%s,%s DESC,%s", DBHelper.GAME_OVER, - DBHelper.TURN_LOCAL, DBHelper.LASTMOVE ); + // Sort unconnected games at top (turn==-1), games that are finished + // at the bottom, then games at the top by how long it's been the + // device owner's turn. + String orderBy = String.format( "%s is -1 DESC,%s,%s DESC,%s", DBHelper.TURN, + DBHelper.GAME_OVER, DBHelper.TURN_LOCAL, + DBHelper.LASTMOVE ); synchronized( s_dbHelper ) { Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns, selection, // selection From e24f7bdba85f2be40cd75855b1dae7b33d0a84b1 Mon Sep 17 00:00:00 2001 From: Bernard Massot Date: Sat, 26 Aug 2017 09:03:20 +0000 Subject: [PATCH 02/66] Translated using Weblate (French) Currently translated at 91.1% (673 of 738 strings) --- xwords4/android/res_src/values-fr/strings.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/xwords4/android/res_src/values-fr/strings.xml b/xwords4/android/res_src/values-fr/strings.xml index 27525888b..7b8bd1ac9 100644 --- a/xwords4/android/res_src/values-fr/strings.xml +++ b/xwords4/android/res_src/values-fr/strings.xml @@ -3574,4 +3574,15 @@ Si votre appareil ne peut pas envoyer de SMS de données (par ex. parce que ce n Forcer la mise en page pour tablette Forcer la mise en page pour téléphone + Autres téléchargements… + + Types d\'adresses désactivées + + Nom de contact (optionnel) : + Nom de l\'appareil (optionnel) : + + Êtes-vous sûr de vouloir effacer l\'enregistrement RelayID sélectionné ? + Êtes-vous sûr de vouloir effacer les %1$d enregistrements RelayID sélectionnés ? + + From e97dd26e6f8cf9cee31de5cbe8caf7f7f4871344 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 30 Aug 2017 07:35:15 -0700 Subject: [PATCH 03/66] fix missing "game in room NNNN" in games list Looks like sometimes the TextField was GONE --- .../src/main/java/org/eehouse/android/xw4/GameListItem.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java index efeeaa513..4b4473e49 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java @@ -325,10 +325,9 @@ public class GameListItem extends LinearLayout } ); String roleSummary = summary.summarizeRole( m_context, m_rowid ); + m_role.setVisibility( null == roleSummary ? View.GONE : View.VISIBLE ); if ( null != roleSummary ) { m_role.setText( roleSummary ); - } else { - m_role.setVisibility( View.GONE ); } update( expanded, summary.lastMoveTime, haveATurn, From 1c3b5564b51ad7c35e35c17d7a2b8576468a20ad Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 1 Sep 2017 08:12:38 -0700 Subject: [PATCH 04/66] use same red/green for expiry as for net status Slightly less glaring; slightly more consistency. --- .../java/org/eehouse/android/xw4/ConnStatusHandler.java | 5 +---- .../java/org/eehouse/android/xw4/ExpiringDelegate.java | 7 +++---- .../app/src/main/java/org/eehouse/android/xw4/XWApp.java | 3 +++ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/ConnStatusHandler.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/ConnStatusHandler.java index c2d0b9795..3a544a429 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/ConnStatusHandler.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/ConnStatusHandler.java @@ -52,9 +52,6 @@ public class ConnStatusHandler { public Handler getHandler(); } - private static final int GREEN = 0xFF00AF00; - private static final int RED = 0xFFAF0000; - private static final int BLACK = 0xFF000000; private static final int SUCCESS_IN = 0; private static final int SUCCESS_OUT = 1; private static final int SHOW_SUCCESS_INTERVAL = 1000; @@ -340,7 +337,7 @@ public class ConnStatusHandler { boolean isIn ) { enabled = enabled && null != newestSuccess( connTypes, isIn ); - s_fillPaint.setColor( enabled ? GREEN : RED ); + s_fillPaint.setColor( enabled ? XWApp.GREEN : XWApp.RED ); canvas.drawRect( rect, s_fillPaint ); } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/ExpiringDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/ExpiringDelegate.java index 72607dfb1..3987291eb 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/ExpiringDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/ExpiringDelegate.java @@ -22,7 +22,6 @@ package org.eehouse.android.xw4; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; @@ -214,13 +213,13 @@ public class ExpiringDelegate { int offset = 0; int count = s_points.length; if ( 0 < redWidth ) { - s_paint.setColor( Color.RED ); + s_paint.setColor( XWApp.RED ); canvas.drawLines( s_points, offset, count / 2, s_paint ); count /= 2; offset += count; } if ( redWidth < width ) { - s_paint.setColor( Color.GREEN ); + s_paint.setColor( XWApp.GREEN ); } canvas.drawLines( s_points, offset, count, s_paint ); } @@ -256,7 +255,7 @@ public class ExpiringDelegate { Paint paint = new Paint(); paint.setStyle(Paint.Style.FILL); - paint.setColor( Color.RED ); + paint.setColor( XWApp.RED ); canvas.drawRect( 0, 0, pct, 1, paint ); paint.setColor( Utils.TURN_COLOR ); canvas.drawRect( pct, 0, 100, 1, paint ); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java index f37658925..199b1097e 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java @@ -53,6 +53,9 @@ public class XWApp extends Application { public static final int MAX_TRAY_TILES = 7; // comtypes.h public static final int SEL_COLOR = Color.argb( 0xFF, 0x09, 0x70, 0x93 ); + public static final int GREEN = 0xFF00AF00; + public static final int RED = 0xFFAF0000; + private static UUID s_UUID = null; private static Boolean s_onEmulator = null; private static Context s_context = null; From 5ed9d2c6aa66c86ae26946560aef61dc1485ee5c Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 2 Sep 2017 11:24:58 -0700 Subject: [PATCH 05/66] remove dead code --- .../java/org/eehouse/android/xw4/BoardDelegate.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java index 78e963232..609e9958a 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java @@ -104,7 +104,6 @@ public class BoardDelegate extends DelegateBase private Button m_exchCancelButton; private SentInvitesInfo m_sentInfo; private Perms23.PermCbck m_permCbck; - private ArrayList m_pendingChats; private CommsConnTypeSet m_connTypes = null; private String[] m_missingDevs; @@ -553,8 +552,6 @@ public class BoardDelegate extends DelegateBase m_isFirstLaunch = null == savedInstanceState; getBundledData( savedInstanceState ); - m_pendingChats = new ArrayList(); - m_utils = new BoardUtilCtxt(); m_timers = new TimerRunnable[4]; // needs to be in sync with // XWTimerReason @@ -2144,7 +2141,6 @@ public class BoardDelegate extends DelegateBase if ( m_gi.serverRole != DeviceRole.SERVER_STANDALONE ) { warnIfNoTransport(); - trySendChats(); tickle( isStart ); tryInvites(); } @@ -2407,15 +2403,6 @@ public class BoardDelegate extends DelegateBase } } - private void trySendChats() - { - Iterator iter = m_pendingChats.iterator(); - while ( iter.hasNext() ) { - handleViaThread( JNICmd.CMD_SENDCHAT, iter.next() ); - } - m_pendingChats.clear(); - } - private void tryInvites() { if ( 0 < m_mySIS.nMissing && m_summary.hasRematchInfo() ) { From a10dd6a3140e39d51254328ea6c9161b8a2de017 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 2 Sep 2017 11:26:10 -0700 Subject: [PATCH 06/66] make game sort order clearer and easier to modify For the ORDER BY clause that governs how games are displayed within a group, use a static string built from a list of clauses that are then easy to move up and down. Add clause that moves games with unread chat to the top. Another commit will modify the display so it's clear why it's there. --- .../java/org/eehouse/android/xw4/DBUtils.java | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java index bc60ce11d..ec7782026 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java @@ -1616,25 +1616,34 @@ public class DBUtils { return result; } + // ORDER BY clause that governs display of games in main GamesList view + private static final String s_getGroupGamesOrderBy = + TextUtils.join(",", new String[] { + // Ended games at bottom + DBHelper.GAME_OVER, + // games with unread chat messages at top + "(" + DBHelper.HASMSGS + " & " + GameSummary.MSG_FLAGS_CHAT + ") IS NOT 0 DESC", + // Games not yet connected at top + DBHelper.TURN + " is -1 DESC", + // Games where it's a local player's turn at top + DBHelper.TURN_LOCAL + " DESC", + // finally, sort by timestamp of last-made move + DBHelper.LASTMOVE, + }); + public static long[] getGroupGames( Context context, long groupID ) { long[] result = null; initDB( context ); - String[] columns = { ROW_ID }; + String[] columns = { ROW_ID, DBHelper.HASMSGS }; String selection = String.format( "%s=%d", DBHelper.GROUPID, groupID ); - // Sort unconnected games at top (turn==-1), games that are finished - // at the bottom, then games at the top by how long it's been the - // device owner's turn. - String orderBy = String.format( "%s is -1 DESC,%s,%s DESC,%s", DBHelper.TURN, - DBHelper.GAME_OVER, DBHelper.TURN_LOCAL, - DBHelper.LASTMOVE ); synchronized( s_dbHelper ) { Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns, selection, // selection null, // args null, // groupBy null, // having - orderBy + s_getGroupGamesOrderBy ); int index = cursor.getColumnIndex( ROW_ID ); result = new long[ cursor.getCount() ]; From 261689160872658a1fc586b678746d11339d72c7 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 2 Sep 2017 12:05:58 -0700 Subject: [PATCH 07/66] remove/replace deprecated singleLine element --- xwords4/android/app/src/main/res/layout/chat.xml | 1 - xwords4/android/app/src/main/res/layout/dflt_name.xml | 2 +- xwords4/android/app/src/main/res/layout/dict_browser.xml | 2 +- xwords4/android/app/src/main/res/layout/game_config.xml | 2 +- xwords4/android/app/src/main/res/layout/game_list_item.xml | 2 +- xwords4/android/app/src/main/res/layout/get_relay.xml | 4 ++-- xwords4/android/app/src/main/res/layout/get_sms.xml | 4 ++-- xwords4/android/app/src/main/res/layout/list_item.xml | 4 ++-- xwords4/android/app/src/main/res/layout/loc_list_item.xml | 4 ++-- xwords4/android/app/src/main/res/layout/loc_main.xml | 2 +- xwords4/android/app/src/main/res/layout/player_edit.xml | 4 ++-- xwords4/android/app/src/main/res/layout/player_list_elem.xml | 4 ++-- xwords4/android/app/src/main/res/layout/rename_game.xml | 2 +- 13 files changed, 18 insertions(+), 19 deletions(-) diff --git a/xwords4/android/app/src/main/res/layout/chat.xml b/xwords4/android/app/src/main/res/layout/chat.xml index cdaa787cd..b0ac5d6eb 100644 --- a/xwords4/android/app/src/main/res/layout/chat.xml +++ b/xwords4/android/app/src/main/res/layout/chat.xml @@ -30,7 +30,6 @@ diff --git a/xwords4/android/app/src/main/res/layout/dict_browser.xml b/xwords4/android/app/src/main/res/layout/dict_browser.xml index 15ea2f71c..6e34e698b 100644 --- a/xwords4/android/app/src/main/res/layout/dict_browser.xml +++ b/xwords4/android/app/src/main/res/layout/dict_browser.xml @@ -26,7 +26,7 @@ diff --git a/xwords4/android/app/src/main/res/layout/get_relay.xml b/xwords4/android/app/src/main/res/layout/get_relay.xml index 186f92600..d1350131e 100644 --- a/xwords4/android/app/src/main/res/layout/get_relay.xml +++ b/xwords4/android/app/src/main/res/layout/get_relay.xml @@ -19,7 +19,7 @@ android:layout_marginLeft="30dip" android:layout_marginRight="30dip" android:autoText="false" - android:singleLine="true" + android:maxLines="1" android:selectAllOnFocus="true" android:textAppearance="?android:attr/textAppearanceMedium" /> @@ -37,7 +37,7 @@ android:layout_marginLeft="30dip" android:layout_marginRight="30dip" android:autoText="false" - android:singleLine="true" + android:maxLines="1" android:selectAllOnFocus="true" android:textAppearance="?android:attr/textAppearanceMedium" /> diff --git a/xwords4/android/app/src/main/res/layout/get_sms.xml b/xwords4/android/app/src/main/res/layout/get_sms.xml index 118fddcf4..65dd5910e 100644 --- a/xwords4/android/app/src/main/res/layout/get_sms.xml +++ b/xwords4/android/app/src/main/res/layout/get_sms.xml @@ -19,7 +19,7 @@ android:layout_marginLeft="30dip" android:layout_marginRight="30dip" android:autoText="false" - android:singleLine="true" + android:maxLines="1" android:selectAllOnFocus="true" android:textAppearance="?android:attr/textAppearanceMedium" /> @@ -37,7 +37,7 @@ android:layout_marginLeft="30dip" android:layout_marginRight="30dip" android:autoText="false" - android:singleLine="true" + android:maxLines="1" android:selectAllOnFocus="true" android:textAppearance="?android:attr/textAppearanceMedium" /> diff --git a/xwords4/android/app/src/main/res/layout/list_item.xml b/xwords4/android/app/src/main/res/layout/list_item.xml index 5b8b1f555..4aa520d2b 100644 --- a/xwords4/android/app/src/main/res/layout/list_item.xml +++ b/xwords4/android/app/src/main/res/layout/list_item.xml @@ -31,7 +31,7 @@ android:layout_height="wrap_content" android:layout_weight="1" android:textAppearance="?android:attr/textAppearanceMedium" - android:singleLine="true" + android:maxLines="1" /> diff --git a/xwords4/android/app/src/main/res/layout/loc_list_item.xml b/xwords4/android/app/src/main/res/layout/loc_list_item.xml index e3d1d9f8b..48326a1a6 100644 --- a/xwords4/android/app/src/main/res/layout/loc_list_item.xml +++ b/xwords4/android/app/src/main/res/layout/loc_list_item.xml @@ -9,11 +9,11 @@ diff --git a/xwords4/android/app/src/main/res/layout/loc_main.xml b/xwords4/android/app/src/main/res/layout/loc_main.xml index 8fad9f96a..f45901867 100644 --- a/xwords4/android/app/src/main/res/layout/loc_main.xml +++ b/xwords4/android/app/src/main/res/layout/loc_main.xml @@ -37,7 +37,7 @@ diff --git a/xwords4/android/app/src/main/res/layout/player_edit.xml b/xwords4/android/app/src/main/res/layout/player_edit.xml index cf5427b14..ac5dd25e8 100644 --- a/xwords4/android/app/src/main/res/layout/player_edit.xml +++ b/xwords4/android/app/src/main/res/layout/player_edit.xml @@ -55,7 +55,7 @@ android:capitalize="words" android:selectAllOnFocus="true" android:gravity="fill_horizontal" - android:singleLine="true" + android:maxLines="1" android:textAppearance="?android:attr/textAppearanceMedium" /> @@ -105,7 +105,7 @@ android:capitalize="none" android:gravity="fill_horizontal" android:password="true" - android:singleLine="true" + android:maxLines="1" android:textAppearance="?android:attr/textAppearanceMedium" /> diff --git a/xwords4/android/app/src/main/res/layout/player_list_elem.xml b/xwords4/android/app/src/main/res/layout/player_list_elem.xml index f8c865a1d..81c3c090e 100644 --- a/xwords4/android/app/src/main/res/layout/player_list_elem.xml +++ b/xwords4/android/app/src/main/res/layout/player_list_elem.xml @@ -9,13 +9,13 @@ diff --git a/xwords4/android/app/src/main/res/layout/rename_game.xml b/xwords4/android/app/src/main/res/layout/rename_game.xml index f6dca767b..84bcb62fe 100644 --- a/xwords4/android/app/src/main/res/layout/rename_game.xml +++ b/xwords4/android/app/src/main/res/layout/rename_game.xml @@ -20,7 +20,7 @@ android:layout_marginLeft="30dip" android:layout_marginRight="30dip" android:autoText="false" - android:singleLine="true" + android:maxLines="1" android:selectAllOnFocus="true" android:textAppearance="?android:attr/textAppearanceMedium" /> From 2735144515b37cef95d899342510f6e48264081e Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 2 Sep 2017 11:34:30 -0700 Subject: [PATCH 08/66] mark games with unread chat --- .../org/eehouse/android/xw4/GameListItem.java | 14 +++++++--- .../src/main/res/layout/game_list_item.xml | 26 ++++++++++++++----- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java index 4b4473e49..9c41f4fe0 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java @@ -64,7 +64,8 @@ public class GameListItem extends LinearLayout private LinearLayout m_list; private TextView m_state; private TextView m_modTime; - private ImageView m_marker; + private ImageView m_gameTypeImage; + private View m_hasChatMarker; private TextView m_role; private boolean m_expanded, m_haveTurn, m_haveTurnLocal; @@ -194,7 +195,8 @@ public class GameListItem extends LinearLayout m_list = (LinearLayout)findViewById( R.id.player_list ); m_state = (TextView)findViewById( R.id.state ); m_modTime = (TextView)findViewById( R.id.modtime ); - m_marker = (ImageView)findViewById( R.id.msg_marker ); + m_gameTypeImage = (ImageView)findViewById( R.id.game_type_marker ); + m_hasChatMarker = (View)findViewById( R.id.has_chat_marker ); m_thumb = (ImageView)findViewById( R.id.thumbnail ); m_role = (TextView)findViewById( R.id.role ); } @@ -316,14 +318,18 @@ public class GameListItem extends LinearLayout int iconID = summary.isMultiGame() ? R.drawable.multigame__gen : R.drawable.sologame__gen; - m_marker.setImageResource( iconID ); - m_marker.setOnClickListener( new View.OnClickListener() { + m_gameTypeImage.setImageResource( iconID ); + m_gameTypeImage.setOnClickListener( new View.OnClickListener() { @Override public void onClick( View view ) { toggleSelected(); } } ); + int flags = DBUtils.getMsgFlags( m_context, m_rowid ); + boolean hasChat = 0 != (flags & GameSummary.MSG_FLAGS_CHAT); + m_hasChatMarker.setVisibility( hasChat ? View.VISIBLE : View.GONE ); + String roleSummary = summary.summarizeRole( m_context, m_rowid ); m_role.setVisibility( null == roleSummary ? View.GONE : View.VISIBLE ); if ( null != roleSummary ) { diff --git a/xwords4/android/app/src/main/res/layout/game_list_item.xml b/xwords4/android/app/src/main/res/layout/game_list_item.xml index f55934df2..a2f6cdeee 100644 --- a/xwords4/android/app/src/main/res/layout/game_list_item.xml +++ b/xwords4/android/app/src/main/res/layout/game_list_item.xml @@ -31,13 +31,25 @@ android:visibility="gone" > - + + + + Date: Sun, 3 Sep 2017 10:38:54 -0700 Subject: [PATCH 09/66] cleanup don't check standalone games for chat messages --- .../java/org/eehouse/android/xw4/GameListItem.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java index 9c41f4fe0..2301c75ac 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java @@ -65,7 +65,6 @@ public class GameListItem extends LinearLayout private TextView m_state; private TextView m_modTime; private ImageView m_gameTypeImage; - private View m_hasChatMarker; private TextView m_role; private boolean m_expanded, m_haveTurn, m_haveTurnLocal; @@ -196,7 +195,6 @@ public class GameListItem extends LinearLayout m_state = (TextView)findViewById( R.id.state ); m_modTime = (TextView)findViewById( R.id.modtime ); m_gameTypeImage = (ImageView)findViewById( R.id.game_type_marker ); - m_hasChatMarker = (View)findViewById( R.id.has_chat_marker ); m_thumb = (ImageView)findViewById( R.id.thumbnail ); m_role = (TextView)findViewById( R.id.role ); } @@ -326,9 +324,13 @@ public class GameListItem extends LinearLayout } } ); - int flags = DBUtils.getMsgFlags( m_context, m_rowid ); - boolean hasChat = 0 != (flags & GameSummary.MSG_FLAGS_CHAT); - m_hasChatMarker.setVisibility( hasChat ? View.VISIBLE : View.GONE ); + boolean hasChat = summary.isMultiGame(); + if ( hasChat ) { + int flags = DBUtils.getMsgFlags( m_context, m_rowid ); + hasChat = 0 != (flags & GameSummary.MSG_FLAGS_CHAT); + } + findViewById( R.id.has_chat_marker ) + .setVisibility( hasChat ? View.VISIBLE : View.GONE ); String roleSummary = summary.summarizeRole( m_context, m_rowid ); m_role.setVisibility( null == roleSummary ? View.GONE : View.VISIBLE ); From 51ca1777e2fcb2118f794f698eb60241df02437f Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 3 Sep 2017 13:20:26 -0700 Subject: [PATCH 10/66] make the have-chat icon green --- xwords4/android/app/src/main/res/.gitignore | 1 + xwords4/android/app/src/main/res/layout/game_list_item.xml | 4 ++-- xwords4/android/scripts/images.mk | 4 ++++ xwords4/android/scripts/mkimages.sh | 5 +++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/xwords4/android/app/src/main/res/.gitignore b/xwords4/android/app/src/main/res/.gitignore index 1e3315a64..e19e5a0cb 100644 --- a/xwords4/android/app/src/main/res/.gitignore +++ b/xwords4/android/app/src/main/res/.gitignore @@ -1 +1,2 @@ values-??/strings.xml +**/*__gen.png diff --git a/xwords4/android/app/src/main/res/layout/game_list_item.xml b/xwords4/android/app/src/main/res/layout/game_list_item.xml index a2f6cdeee..77972e134 100644 --- a/xwords4/android/app/src/main/res/layout/game_list_item.xml +++ b/xwords4/android/app/src/main/res/layout/game_list_item.xml @@ -45,8 +45,8 @@ /> diff --git a/xwords4/android/scripts/images.mk b/xwords4/android/scripts/images.mk index 3df05d0a0..1ce0f3bdd 100644 --- a/xwords4/android/scripts/images.mk +++ b/xwords4/android/scripts/images.mk @@ -19,3 +19,7 @@ $(IMG_DEST)/drawable-mdpi/%__gen.png: $(IMG_SRC)/%.svg $(IMG_DEST)/drawable-hdpi/%__gen.png: $(IMG_SRC)/%.svg convert $(PARAMS) -scale 48x48 $< $@ + +# Build have-chat badge using R.color.dull_green +$(IMG_DEST)/drawable/green_chat__gen.png: $(IMG_DEST)/drawable/stat_notify_chat.png + convert -fill '#00AF00' -colorize 50% $< $@ diff --git a/xwords4/android/scripts/mkimages.sh b/xwords4/android/scripts/mkimages.sh index 96caea8ee..3169d9df8 100755 --- a/xwords4/android/scripts/mkimages.sh +++ b/xwords4/android/scripts/mkimages.sh @@ -43,3 +43,8 @@ for SVG in img_src/*.svg; do fi done done + +OTHER_IMAGES="app/src/main/res/drawable/green_chat__gen.png" +for IMAGE in $OTHER_IMAGES; do + make -f $(dirname $0)/images.mk $IMAGE >/dev/null 2>&1 +done From 406ffca333d9153433703cd79a0263f1f5c8178d Mon Sep 17 00:00:00 2001 From: naofum Date: Tue, 29 Aug 2017 23:40:52 +0000 Subject: [PATCH 11/66] Translated using Weblate (Japanese) Currently translated at 73.8% (545 of 738 strings) --- xwords4/android/res_src/values-ja/strings.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/xwords4/android/res_src/values-ja/strings.xml b/xwords4/android/res_src/values-ja/strings.xml index 8147631b4..daf200cc2 100644 --- a/xwords4/android/res_src/values-ja/strings.xml +++ b/xwords4/android/res_src/values-ja/strings.xml @@ -1297,4 +1297,15 @@ WiFi ダイレクト経由で接続可能なデバイスはありません。タブレットレイアウトを強制する 携帯電話のレイアウトを強制する + さらにダウンロード… + + 無効なアドレスの種類 + + 手動入力 + 連絡先の名前 (オプション): + デバイス名 (オプション): + + %1$d のチェックした RelayID レコードを削除してもよろしいですか? + + From f5e66463f968444830f9aa6b873851e684b4b70b Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 3 Sep 2017 14:09:56 -0700 Subject: [PATCH 12/66] up release strings for new version --- xwords4/android/app/build.gradle | 4 ++-- xwords4/android/app/src/main/assets/changes.html | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/xwords4/android/app/build.gradle b/xwords4/android/app/build.gradle index 62d5771a4..a12dc1e82 100644 --- a/xwords4/android/app/build.gradle +++ b/xwords4/android/app/build.gradle @@ -1,6 +1,6 @@ def INITIAL_CLIENT_VERS = 8 -def VERSION_CODE_BASE = 121 -def VERSION_NAME = '4.4.125' +def VERSION_CODE_BASE = 122 +def VERSION_NAME = '4.4.126' def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY") boolean forFDroid = hasProperty('forFDroid') diff --git a/xwords4/android/app/src/main/assets/changes.html b/xwords4/android/app/src/main/assets/changes.html index 87690a560..3ffe14938 100644 --- a/xwords4/android/app/src/main/assets/changes.html +++ b/xwords4/android/app/src/main/assets/changes.html @@ -13,10 +13,9 @@ -

CrossWords 4.4.125 release

+

CrossWords 4.4.126 release

-

This release fixes a problem inviting to new networked games, and - with title bars on some Samsung devices.

+

This release marks new chat messages so you can't miss them.

Please take @@ -26,10 +25,11 @@

New with this release

    -
  • Fix delays bringing up the Invite dialog for new games
  • -
  • Explicitly specify application "theme" to fix a Samsung - "upgrade" turning the titlebar white and so making menu - icons disappear
  • +
  • Add green chat icon for games with unread messages
  • +
  • Display games with unread chat at top of list
  • +
  • Put networked games that still aren't connected right below + them
  • +
  • Include latest French and Japanese translations

(The full changelog From 8971837d225b6dca5a6274feea9caad2e46f88cd Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 5 Sep 2017 19:17:02 -0700 Subject: [PATCH 13/66] fix crash on Samsung Galaxy Note 3 running 4.4 Can't repro on Nexus emulator running 4.4 nor on Samsung running 4.4.4, but the reporter says this fixes it. And from reading it appears expecting older devices to load Material themes without an AppCompat library is wrong. --- xwords4/android/app/src/main/res/values-v11/themes.xml | 4 ++++ xwords4/android/app/src/main/res/values-v21/themes.xml | 4 ++++ xwords4/android/app/src/main/res/values/styles.xml | 2 -- xwords4/android/app/src/main/res/values/themes.xml | 4 ++++ 4 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 xwords4/android/app/src/main/res/values-v11/themes.xml create mode 100644 xwords4/android/app/src/main/res/values-v21/themes.xml create mode 100644 xwords4/android/app/src/main/res/values/themes.xml diff --git a/xwords4/android/app/src/main/res/values-v11/themes.xml b/xwords4/android/app/src/main/res/values-v11/themes.xml new file mode 100644 index 000000000..0d55c6559 --- /dev/null +++ b/xwords4/android/app/src/main/res/values-v11/themes.xml @@ -0,0 +1,4 @@ + + + -

CrossWords 4.4.126 release

+

CrossWords 4.4.127 release

-

This release marks new chat messages so you can't miss them.

+

Quick fix for a crash reported via the Play Store

Please take @@ -25,13 +25,7 @@

New with this release

    -
  • Add green chat icon for games with unread messages
  • -
  • Display games with unread chat at top of list
  • -
  • Put networked games that still aren't connected right below - them
  • -
  • Fix crash on Samsung running (obsolete) Android version 4.4 - caused by the previous release
  • -
  • Include latest French and Japanese translations
  • +
  • Fix crash on Samsung Galaxy TabS 8.4 (thanks for the report!)

(The full changelog From a328221b6338b9d9df1dc041d6c25c080103134f Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 19 Sep 2017 07:46:17 -0700 Subject: [PATCH 26/66] up version codes for new release I somehow screwed up the last release, but google got the apk so requires me to change the version number. --- xwords4/android/app/build.gradle | 4 ++-- xwords4/android/app/src/main/assets/changes.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/xwords4/android/app/build.gradle b/xwords4/android/app/build.gradle index 9009b7796..65845fd1a 100644 --- a/xwords4/android/app/build.gradle +++ b/xwords4/android/app/build.gradle @@ -1,6 +1,6 @@ def INITIAL_CLIENT_VERS = 8 -def VERSION_CODE_BASE = 123 -def VERSION_NAME = '4.4.127' +def VERSION_CODE_BASE = 124 +def VERSION_NAME = '4.4.128' def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY") boolean forFDroid = hasProperty('forFDroid') diff --git a/xwords4/android/app/src/main/assets/changes.html b/xwords4/android/app/src/main/assets/changes.html index 8f961c7f2..b5827f9bb 100644 --- a/xwords4/android/app/src/main/assets/changes.html +++ b/xwords4/android/app/src/main/assets/changes.html @@ -13,7 +13,7 @@ -

CrossWords 4.4.127 release

+

CrossWords 4.4.128 release

Quick fix for a crash reported via the Play Store

From 39efbaa7f5d93a1eca764077bc7f85a5e8b2eeb3 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 19 Sep 2017 22:09:57 -0700 Subject: [PATCH 27/66] fix timer-draw NPE due to race condition use runOnUiThread().... --- .../org/eehouse/android/xw4/BoardCanvas.java | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardCanvas.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardCanvas.java index 176f6d7cc..5b0dcd7a3 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardCanvas.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardCanvas.java @@ -322,26 +322,35 @@ public class BoardCanvas extends Canvas implements DrawCtx { } } - public void drawTimer( Rect rect, int player, int secondsLeft ) + public void drawTimer( Rect rect, final int player, + int secondsLeft ) { - if ( null != m_jniThread && - (m_lastSecsLeft != secondsLeft || m_lastTimerPlayer != player) ) { - m_lastSecsLeft = secondsLeft; - m_lastTimerPlayer = player; + if ( m_lastSecsLeft != secondsLeft || m_lastTimerPlayer != player ) { + final Rect rectCopy = new Rect(rect); + final int secondsLeftCopy = secondsLeft; + m_activity.runOnUiThread( new Runnable() { + @Override + public void run() { + if ( null != m_jniThread ) { + m_lastSecsLeft = secondsLeftCopy; + m_lastTimerPlayer = player; - String negSign = secondsLeft < 0? "-":""; - secondsLeft = Math.abs( secondsLeft ); - String time = String.format( "%s%d:%02d", negSign, secondsLeft/60, - secondsLeft%60 ); + String negSign = secondsLeftCopy < 0? "-":""; + int secondsLeft = Math.abs( secondsLeftCopy ); + String time = + String.format( "%s%d:%02d", negSign, + secondsLeft/60, secondsLeft%60 ); - fillRectOther( rect, CommonPrefs.COLOR_BACKGRND ); - m_fillPaint.setColor( m_playerColors[player] ); + fillRectOther( rectCopy, CommonPrefs.COLOR_BACKGRND ); + m_fillPaint.setColor( m_playerColors[player] ); - Rect shorter = new Rect( rect ); - shorter.inset( 0, shorter.height() / 5 ); - drawCentered( time, shorter, null ); + rectCopy.inset( 0, rectCopy.height() / 5 ); + drawCentered( time, rectCopy, null ); - m_jniThread.handle( JNIThread.JNICmd.CMD_DRAW ); + m_jniThread.handle( JNIThread.JNICmd.CMD_DRAW ); + } + } + } ); } } From 592a6429d4ac57b631b3adc9b5614304af146aa7 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 20 Sep 2017 06:40:42 -0700 Subject: [PATCH 28/66] up changelog and version strings --- xwords4/android/app/build.gradle | 4 ++-- xwords4/android/app/src/main/assets/changes.html | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/xwords4/android/app/build.gradle b/xwords4/android/app/build.gradle index 65845fd1a..bb635bd45 100644 --- a/xwords4/android/app/build.gradle +++ b/xwords4/android/app/build.gradle @@ -1,6 +1,6 @@ def INITIAL_CLIENT_VERS = 8 -def VERSION_CODE_BASE = 124 -def VERSION_NAME = '4.4.128' +def VERSION_CODE_BASE = 125 +def VERSION_NAME = '4.4.129' def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY") boolean forFDroid = hasProperty('forFDroid') diff --git a/xwords4/android/app/src/main/assets/changes.html b/xwords4/android/app/src/main/assets/changes.html index b5827f9bb..71661ee86 100644 --- a/xwords4/android/app/src/main/assets/changes.html +++ b/xwords4/android/app/src/main/assets/changes.html @@ -13,9 +13,9 @@ -

CrossWords 4.4.128 release

+

CrossWords 4.4.129 release

-

Quick fix for a crash reported via the Play Store

+

Quick fix for a another crash reported via the Play Store

Please take @@ -25,7 +25,7 @@

New with this release

    -
  • Fix crash on Samsung Galaxy TabS 8.4 (thanks for the report!)
  • +
  • Fix crash that only showed up in games using timers

(The full changelog From 618ee89add6d2b18e125f41641f1428d5ffea9ee Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 20 Sep 2017 07:26:26 -0700 Subject: [PATCH 29/66] fix crash drawing game timer It's outside the normal begin_/end_draw flow and so cairo wasn't set up as expected. --- xwords4/linux/gtkdraw.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/xwords4/linux/gtkdraw.c b/xwords4/linux/gtkdraw.c index e32c7c293..aeab1f824 100644 --- a/xwords4/linux/gtkdraw.c +++ b/xwords4/linux/gtkdraw.c @@ -112,6 +112,12 @@ destroyCairo( GtkDrawCtx* dctx ) dctx->_cairo = NULL; } +static XP_Bool +haveCairo( const GtkDrawCtx* dctx ) +{ + return !!dctx->_cairo; +} + static cairo_t* getCairo( const GtkDrawCtx* dctx ) { @@ -1231,15 +1237,20 @@ gtk_draw_drawTimer( DrawCtx* p_dctx, const XP_Rect* rInner, XP_U16 playerNum, XP_S16 secondsLeft ) { GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; - XP_UCHAR buf[10]; + XP_Bool hadCairo = haveCairo( dctx ); + if ( hadCairo || initCairo( dctx ) ) { + XP_UCHAR buf[10]; - gtkFormatTimerText( buf, VSIZE(buf), secondsLeft ); + gtkFormatTimerText( buf, VSIZE(buf), secondsLeft ); -/* gdk_gc_set_clip_rectangle( dctx->drawGC, (GdkRectangle*)rInner ); */ - gtkEraseRect( dctx, rInner ); - draw_string_at( dctx, NULL, buf, rInner->height-1, - rInner, XP_GTK_JUST_CENTER, - &dctx->playerColors[playerNum], NULL ); + gtkEraseRect( dctx, rInner ); + draw_string_at( dctx, NULL, buf, rInner->height-1, + rInner, XP_GTK_JUST_CENTER, + &dctx->playerColors[playerNum], NULL ); + if ( !hadCairo ) { + destroyCairo( dctx ); + } + } } /* gtk_draw_drawTimer */ #ifdef XWFEATURE_MINIWIN From 5dc9327d31afa5789143c22e56a9565fb39424f9 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 22 Sep 2017 06:46:19 -0700 Subject: [PATCH 30/66] record current git-diff in assets --- xwords4/android/app/build.gradle | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/xwords4/android/app/build.gradle b/xwords4/android/app/build.gradle index bb635bd45..2046c7e2e 100644 --- a/xwords4/android/app/build.gradle +++ b/xwords4/android/app/build.gradle @@ -256,7 +256,13 @@ task makeBuildAssets() { def assetsDir = android.sourceSets.main.assets.srcDirs.toArray()[0] String path = new File(assetsDir, 'build-info.txt').getAbsolutePath() File file = new File(path); - file.write("git: ${GITREV}\n"); + String out = "git: ${GITREV}\n" + + String diff = "git diff".execute().text.trim() + if (diff) { + out += "\n" + diff + } + file.write(out) } gradle.projectsEvaluated { From 9bdf43a89e0337de8aacad87ce1d950c7f7c1620 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 22 Sep 2017 06:55:02 -0700 Subject: [PATCH 31/66] remove duplicated BuildConfig entry --- xwords4/android/app/build.gradle | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/xwords4/android/app/build.gradle b/xwords4/android/app/build.gradle index 2046c7e2e..00f5f22f1 100644 --- a/xwords4/android/app/build.gradle +++ b/xwords4/android/app/build.gradle @@ -38,7 +38,7 @@ android { // renameArtifact(variant) // variant.buildConfigField "String", "FIELD_NAME", "\"my String\"" def GCM_SENDER_ID = System.getenv("GCM_SENDER_ID") - variant.buildConfigField "String", "SENDER_ID", "\"$GCM_SENDER_ID\"" + variant.buildConfigField "String", "GCM_SENDER_ID", "\"$GCM_SENDER_ID\"" variant.buildConfigField "String", "FABRIC_API_KEY", "\"$FABRIC_API_KEY\"" resValue "string", "git_rev", "$GITREV" @@ -51,9 +51,6 @@ android { // FIX ME variant.buildConfigField "String", "STRINGS_HASH", "\"00000\"" - def senderID = System.getenv("GCM_SENDER_ID") - variant.buildConfigField "String", "GCM_SENDER_ID", "\"$senderID\"" - variant.buildConfigField "short", "CLIENT_VERS_RELAY", "$INITIAL_CLIENT_VERS" variant.buildConfigField "boolean", "FOR_FDROID", "$forFDroid" From 096aa3bd0f8c173ae928347ddc17bf91d1026298 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 24 Sep 2017 14:30:59 -0700 Subject: [PATCH 32/66] make GCM_SENDER_ID empty for xw4d variant build This seems to fix that app, when built by me where GCM_SENDER_ID's set in the environment, being a battery hog. Apparently google's code doesn't handle being passed the wrong senderID very well. --- xwords4/android/app/build.gradle | 7 +++-- .../eehouse/android/xw4/GCMIntentService.java | 31 +++++++++++-------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/xwords4/android/app/build.gradle b/xwords4/android/app/build.gradle index 00f5f22f1..6cdb233a1 100644 --- a/xwords4/android/app/build.gradle +++ b/xwords4/android/app/build.gradle @@ -2,6 +2,7 @@ def INITIAL_CLIENT_VERS = 8 def VERSION_CODE_BASE = 125 def VERSION_NAME = '4.4.129' def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY") +def GCM_SENDER_ID = System.getenv("GCM_SENDER_ID") boolean forFDroid = hasProperty('forFDroid') @@ -37,8 +38,6 @@ android { applicationVariants.all { variant -> // renameArtifact(variant) // variant.buildConfigField "String", "FIELD_NAME", "\"my String\"" - def GCM_SENDER_ID = System.getenv("GCM_SENDER_ID") - variant.buildConfigField "String", "GCM_SENDER_ID", "\"$GCM_SENDER_ID\"" variant.buildConfigField "String", "FABRIC_API_KEY", "\"$FABRIC_API_KEY\"" resValue "string", "git_rev", "$GITREV" @@ -67,6 +66,8 @@ android { resValue "string", "invite_prefix", "/and/" buildConfigField "boolean", "WIDIR_ENABLED", "false" buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false" + + buildConfigField "String", "GCM_SENDER_ID", "\"$GCM_SENDER_ID\"" } xw4d { dimension "variant" @@ -78,6 +79,8 @@ android { resValue "string", "invite_prefix", "/anddbg/" buildConfigField "boolean", "WIDIR_ENABLED", "true" buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "true" + + buildConfigField "String", "GCM_SENDER_ID", "\"\"" } // WARNING: "all" breaks things. Seems to be a keyword. Need diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GCMIntentService.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GCMIntentService.java index e70beac2e..7175e56d9 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GCMIntentService.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GCMIntentService.java @@ -29,12 +29,15 @@ import com.google.android.gcm.GCMRegistrar; import org.json.JSONArray; +import junit.framework.Assert; + public class GCMIntentService extends GCMBaseIntentService { private static final String TAG = GCMIntentService.class.getSimpleName(); public GCMIntentService() { super( BuildConfig.GCM_SENDER_ID ); + Assert.assertTrue( BuildConfig.GCM_SENDER_ID.length() > 0 ); } @Override @@ -120,20 +123,22 @@ public class GCMIntentService extends GCMBaseIntentService { public static void init( Application app ) { - int sdkVersion = Integer.valueOf( android.os.Build.VERSION.SDK ); - if ( 8 <= sdkVersion && 0 < BuildConfig.GCM_SENDER_ID.length() ) { - try { - GCMRegistrar.checkDevice( app ); - // GCMRegistrar.checkManifest( app ); - String regId = DevID.getGCMDevID( app ); - if ( regId.equals("") ) { - GCMRegistrar.register( app, BuildConfig.GCM_SENDER_ID ); + if ( 0 < BuildConfig.GCM_SENDER_ID.length() ) { + int sdkVersion = Integer.valueOf( android.os.Build.VERSION.SDK ); + if ( 8 <= sdkVersion ) { + try { + GCMRegistrar.checkDevice( app ); + // GCMRegistrar.checkManifest( app ); + String regId = DevID.getGCMDevID( app ); + if ( regId.equals("") ) { + GCMRegistrar.register( app, BuildConfig.GCM_SENDER_ID ); + } + } catch ( UnsupportedOperationException uoe ) { + Log.w( TAG, "Device can't do GCM." ); + } catch ( Exception whatever ) { + // funky devices could do anything + Log.ex( TAG, whatever ); } - } catch ( UnsupportedOperationException uoe ) { - Log.w( TAG, "Device can't do GCM." ); - } catch ( Exception whatever ) { - // funky devices could do anything - Log.ex( TAG, whatever ); } } } From fd7f564e11abc566e6d9bb2d3fcc5c6a7eb59342 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 24 Sep 2017 14:32:58 -0700 Subject: [PATCH 33/66] add debug menu to copy git info to clipboard Makes it easier to find out what the diffs were. --- xwords4/android/app/build.gradle | 10 ++++-- .../android/xw4/GamesListDelegate.java | 5 +++ .../java/org/eehouse/android/xw4/Utils.java | 32 +++++++++++++++++++ .../app/src/main/res/menu/games_list_menu.xml | 3 ++ .../app/src/main/res/values/strings.xml | 1 + 5 files changed, 48 insertions(+), 3 deletions(-) diff --git a/xwords4/android/app/build.gradle b/xwords4/android/app/build.gradle index 6cdb233a1..62e4504c7 100644 --- a/xwords4/android/app/build.gradle +++ b/xwords4/android/app/build.gradle @@ -3,6 +3,7 @@ def VERSION_CODE_BASE = 125 def VERSION_NAME = '4.4.129' def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY") def GCM_SENDER_ID = System.getenv("GCM_SENDER_ID") +def BUILD_INFO_NAME = "build-info.txt" boolean forFDroid = hasProperty('forFDroid') @@ -57,6 +58,10 @@ android { flavorDimensions "variant"//, "abi" productFlavors { + all { + buildConfigField "String", "BUILD_INFO_NAME", "\"${BUILD_INFO_NAME}\"" + } + xw4 { dimension "variant" applicationId "org.eehouse.android.xw4" @@ -254,15 +259,14 @@ afterEvaluate { task makeBuildAssets() { def assetsDir = android.sourceSets.main.assets.srcDirs.toArray()[0] - String path = new File(assetsDir, 'build-info.txt').getAbsolutePath() - File file = new File(path); + String path = new File(assetsDir, BUILD_INFO_NAME).getAbsolutePath() String out = "git: ${GITREV}\n" String diff = "git diff".execute().text.trim() if (diff) { out += "\n" + diff } - file.write(out) + new File(path).write(out) } gradle.projectsEvaluated { diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java index e98aecd9f..caf6ca581 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java @@ -564,6 +564,7 @@ public class GamesListDelegate extends ListDelegateBase private static final int[] DEBUG_ITEMS = { // R.id.games_menu_loaddb, R.id.games_menu_storedb, + R.id.games_menu_writegit, }; private static final int[] NOSEL_ITEMS = { R.id.games_menu_newgroup, @@ -1597,6 +1598,10 @@ public class GamesListDelegate extends ListDelegateBase Action.STORAGE_CONFIRMED, itemID ); break; + case R.id.games_menu_writegit: + Utils.gitInfoToClip( m_activity ); + break; + default: handled = handleSelGamesItem( itemID, selRowIDs ) || handleSelGroupsItem( itemID, getSelGroupIDs() ); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java index e76107259..de49a2c2f 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java @@ -32,7 +32,9 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.res.AssetManager; import android.content.res.Configuration; +import android.text.ClipboardManager; import android.database.Cursor; import android.media.Ringtone; @@ -55,9 +57,12 @@ import android.widget.Toast; import org.json.JSONException; import org.json.JSONObject; +import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; @@ -187,6 +192,33 @@ public class Utils { context.startActivity( Intent.createChooser( intent, chooserMsg ) ); } + static void gitInfoToClip( Context context ) + { + StringBuilder sb; + try { + InputStream is = context.getAssets().open( BuildConfig.BUILD_INFO_NAME, + AssetManager.ACCESS_BUFFER ); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + sb = new StringBuilder(); + for ( ; ; ) { + String line = reader.readLine(); + if ( null == line ) { + break; + } + sb.append( line ).append( "\n" ); + } + reader.close(); + } catch ( Exception ex ) { + sb = null; + } + + if ( null != sb ) { + ClipboardManager clipboard = (ClipboardManager) + context.getSystemService(Context.CLIPBOARD_SERVICE); + clipboard.setText( sb.toString() ); + } + } + public static void postNotification( Context context, Intent intent, int titleID, int bodyID, int id ) { diff --git a/xwords4/android/app/src/main/res/menu/games_list_menu.xml b/xwords4/android/app/src/main/res/menu/games_list_menu.xml index fb964160a..078a860ad 100644 --- a/xwords4/android/app/src/main/res/menu/games_list_menu.xml +++ b/xwords4/android/app/src/main/res/menu/games_list_menu.xml @@ -120,5 +120,8 @@ + diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index 9d592eb22..d7831a755 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -2491,6 +2491,7 @@ %1$s/%2$s Write games to SD card Load games from SD card + Copy git info to clipboard Accept duplicate invites Fake locale for translation Accept invitations more than once From 6fa01eb971e96f4fc389b37725f53d2c63139a84 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 25 Sep 2017 06:17:44 -0700 Subject: [PATCH 34/66] move GCM-related permissions into new manifest Create an AndroidManifest.xml specific for the xw4 variant, and move the GCM-related permissions (including WakeLock) into it. --- xwords4/android/app/src/main/AndroidManifest.xml | 6 ------ xwords4/android/app/src/xw4/AndroidManifest.xml | 13 +++++++++++++ 2 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 xwords4/android/app/src/xw4/AndroidManifest.xml diff --git a/xwords4/android/app/src/main/AndroidManifest.xml b/xwords4/android/app/src/main/AndroidManifest.xml index bc49a4b99..789de40e9 100644 --- a/xwords4/android/app/src/main/AndroidManifest.xml +++ b/xwords4/android/app/src/main/AndroidManifest.xml @@ -34,13 +34,7 @@ /> - - - - - + + + + + + + + + From 604119f99b1563e9ea52a055f4b1dd0811508776 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 26 Sep 2017 07:01:55 -0700 Subject: [PATCH 35/66] stub out GCMIntentService for d variant Somehow the d variant was crashing without the WAKELOCK permission, the OS having invokes something GCM-related. This should ensure nothing GCM-related can ever happen. --- .../java/org/eehouse/android/xw4/XWApp.java | 1 - .../eehouse/android/xw4/GCMIntentService.java | 0 .../eehouse/android/xw4/GCMIntentService.java | 37 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) rename xwords4/android/app/src/{main => xw4}/java/org/eehouse/android/xw4/GCMIntentService.java (100%) create mode 100644 xwords4/android/app/src/xw4d/java/org/eehouse/android/xw4/GCMIntentService.java diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java index 199b1097e..dbf740b99 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java @@ -36,7 +36,6 @@ public class XWApp extends Application { private static final String TAG = XWApp.class.getSimpleName(); public static final boolean BTSUPPORTED = true; - public static final boolean GCMSUPPORTED = true; public static final boolean ATTACH_SUPPORTED = false; public static final boolean LOG_LIFECYLE = false; public static final boolean DEBUG_EXP_TIMERS = false; diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GCMIntentService.java b/xwords4/android/app/src/xw4/java/org/eehouse/android/xw4/GCMIntentService.java similarity index 100% rename from xwords4/android/app/src/main/java/org/eehouse/android/xw4/GCMIntentService.java rename to xwords4/android/app/src/xw4/java/org/eehouse/android/xw4/GCMIntentService.java diff --git a/xwords4/android/app/src/xw4d/java/org/eehouse/android/xw4/GCMIntentService.java b/xwords4/android/app/src/xw4d/java/org/eehouse/android/xw4/GCMIntentService.java new file mode 100644 index 000000000..52ed99206 --- /dev/null +++ b/xwords4/android/app/src/xw4d/java/org/eehouse/android/xw4/GCMIntentService.java @@ -0,0 +1,37 @@ +/* -*- compile-command: "find-and-gradle.sh -PuseCrashlytics insXw4dDeb"; -*- */ +/* + * Copyright 2017 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +package org.eehouse.android.xw4; + +import android.app.Application; + +/** + * The ancient GCMIntentService I copied from sample code seems to have + * trouble (burns battery using the WAKELOCK, specifically) when used with an + * app that doesn't have a registration ID. So let's not use that code. + */ + +public class GCMIntentService { + private static final String TAG = GCMIntentService.class.getSimpleName(); + + public static void init( Application app ) + { + Log.d( TAG, "doing nothing" ); + } +} From 0aab21ed5eb87674eb52fd6e25d522b8ad396260 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 26 Sep 2017 07:06:58 -0700 Subject: [PATCH 36/66] don't email when travis builds succeed --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 243eaa361..9677518ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,5 +35,5 @@ notifications: email: recipients: - xwords@eehouse.org - on_success: always + on_success: never on_failure: always From 7b691dfd2f7b97b830d912484cde77250934a204 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 26 Sep 2017 07:34:54 -0700 Subject: [PATCH 37/66] offer to hide buttons when invoked from action bar --- .../org/eehouse/android/xw4/GamesListDelegate.java | 4 ++-- xwords4/android/app/src/main/res/values/strings.xml | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java index caf6ca581..90a1dc2b1 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java @@ -1540,10 +1540,10 @@ public class GamesListDelegate extends ListDelegateBase GameUtils.resendAllIf( m_activity, null, true, true ); break; case R.id.games_menu_newgame_solo: - handleNewGame( true ); + handleNewGameButton( true ); break; case R.id.games_menu_newgame_net: - handleNewGame( false ); + handleNewGameButton( false ); break; case R.id.games_menu_newgroup: diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index d7831a755..4ba195973 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -2419,11 +2419,11 @@ Hide buttons - These two buttons do - the same thing as the first two items in this window\'s Action Bar - (or menu). If you like you can hide the buttons to make more games - visible.\n\n(If you later want to unhide them go to the Appearance - section of App settings). + The two buttons at the + bottom of this screen and the first two items in its Action Bar + (or menu) do the same thing. If you like you can hide the buttons + to make more games visible.\n\n(If you later want to unhide the + buttons go to the Appearance section of App settings). Waiting for players From d3b9a9851814a0722ebe632c6b77cf6982f159cf Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 27 Sep 2017 20:39:58 -0700 Subject: [PATCH 38/66] fix to serve the oldest release newer than current Removing hard-coded list of release .apks in favor of picking them from the file system based on version code reported by aapt. --- xwords4/android/scripts/info.py | 94 ++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 32 deletions(-) diff --git a/xwords4/android/scripts/info.py b/xwords4/android/scripts/info.py index df74bb667..35006d9d7 100755 --- a/xwords4/android/scripts/info.py +++ b/xwords4/android/scripts/info.py @@ -66,20 +66,7 @@ k_filebase = "/var/www/html/" k_apkDir = "xw4/android/" k_shelfFile = k_filebase + 'xw4/info_shelf_2' k_urlbase = "http://eehouse.org" -k_versions = { 'org.eehouse.android.xw4': { - 'version' : 91, - k_AVERS : 91, - k_URL : k_apkDir + 'XWords4-release_' + k_REL_REV + '.apk', - }, - } -# k_versions_dbg = { 'org.eehouse.android.xw4': { -# 'version' : 74, -# k_AVERS : 74, -# k_GVERS : k_DBG_REV, -# k_URL : k_apkDir + 'XWords4-release_' + k_DBG_REV + '.apk', -# }, -# } s_shelf = None g_langs = {'English' : 'en', @@ -223,6 +210,34 @@ def getOrderedApks( path, appID, debug ): result = sorted(apkToCode.keys(), reverse=True, key=lambda file: (apkToCode[file], apkToMtime[file])) return result +# Given a version, find the apk that has the next highest version +def getNextAfter(path, appID, curVers, debug): + # print 'getNextAfter(', path, ')' + apks = getOrderedApks(path, appID, debug) + + map = {} + max = 0 + for apk in apks: + versionCode = getAAPTInfo(apk)['versionCode'] + if versionCode > curVers: + map[versionCode] = apk + if max < versionCode: max = versionCode + + # print map + + result = None + if map: + print 'looking between', curVers+1, 'and', max + for nextVersion in range(curVers+1, max+1): + if nextVersion in map: + result = map[nextVersion] + break + + if result: + print nextVersion, ':', result + return result + +# Returns '' for xw4, for anything else def getVariantDir( name ): result = '' splits = string.split( name, '.' ) @@ -271,10 +286,10 @@ def dictVersion( req, name, lang, md5sum ): closeShelf() return json.dumps( result ) -def getApp( params, name ): +def getApp( params, name = None, debug = False): result = None - if k_NAME in params: - name = params[k_NAME] + if k_DEBUG in params: debug = params[k_DEBUG] + if k_NAME in params: name = params[k_NAME] if name: variantDir = getVariantDir( name ) # If we're a dev device, always push the latest @@ -303,18 +318,21 @@ def getApp( params, name ): result = {k_URL: url} logging.debug( result ) - elif k_GVERS in params: - gvers = params[k_GVERS] + elif k_AVERS in params: + vers = params[k_AVERS] if k_INSTALLER in params: installer = params[k_INSTALLER] else: installer = '' logging.debug( "name: %s; installer: %s; gvers: %s" - % (name, installer, gvers) ) - if name in k_versions: - if k_GVERS in versForName and not gvers == versForName[k_GVERS]: - result = {k_URL: k_urlbase + '/' + versForName[k_URL]} - else: - logging.debug(name + " is up-to-date") + % (name, installer, vers) ) + print "name: %s; installer: %s; vers: %s" % (name, installer, vers) + dir = k_filebase + k_apkDir + 'rel/' + apk = getNextAfter( dir, name, vers, debug ) + if apk: + apk = apk[len(k_filebase):] # strip fs path + result = {k_URL: k_urlbase + '/' + apk} + else: + logging.debug(name + " is up-to-date") else: logging.debug( 'Error: bad name ' + name ) else: @@ -564,7 +582,7 @@ def clearShelf(): def usage(msg=None): if msg: print "ERROR:", msg print "usage:", sys.argv[0], '--get-sums [lang/dict]*' - print ' | --test-get-app app avers gvers' + print ' | --get-app --appID --vers --gvers [--debug]' print ' | --test-get-dicts name lang curSum' print ' | --list-apks [--path ] [--debug] --appID org.something' print ' | --list-dicts' @@ -574,8 +592,9 @@ def usage(msg=None): def main(): argc = len(sys.argv) - if 1 >= argc: usage(); + if 1 >= argc: usage('too few args') arg = sys.argv[1] + args = sys.argv[2:] if arg == '--clear-shelf': clearShelf() elif arg == '--list-dicts': @@ -589,12 +608,24 @@ def main(): print arg, md5Checksums(dictSums, arg) s_shelf[k_SUMS] = dictSums closeShelf() - elif arg == '--test-get-app': - if not 4 == argc: usage() - params = { k_NAME: sys.argv[2], - k_GVERS: sys.argv[3], + elif arg == '--get-app': + appID = None + vers = 0 + debug = False + while len(args): + arg = args.pop(0) + if arg == '--appID': appID = args.pop(0) + elif arg == '--vers': vers = int(args.pop(0)) + elif arg == '--debug': debug = True + else: usage('unexpected arg: ' + arg) + if not appID: usage('--appID required') + elif not vers: usage('--vers required') + params = { k_NAME: appID, + k_AVERS: vers, + k_DEBUG: debug, + k_DEVOK: False, # FIX ME } - print getApp( params, sys.argv[2] ) + print getApp( params ) elif arg == '--test-get-dicts': if not 5 == argc: usage() params = { k_NAME: sys.argv[2], @@ -607,7 +638,6 @@ def main(): path = "" debug = False appID = '' - args = sys.argv[2:] while len(args): arg = args.pop(0) if arg == '--appID': appID = args.pop(0) From 79e852a9eb0ba7b846f118bba44e5c776377fb37 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 28 Sep 2017 06:50:16 -0700 Subject: [PATCH 39/66] change ID of duplicate menu Board and Games List were using same menuid which meant that even when chosen from Board's menu it would up getting handled by GamesList (in dual-pane mode.) --- .../src/main/java/org/eehouse/android/xw4/BoardDelegate.java | 4 ++-- xwords4/android/app/src/main/res/menu/board_menu.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java index 609e9958a..35052ec90 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java @@ -840,7 +840,7 @@ public class BoardDelegate extends DelegateBase Utils.setItemVisible( menu, R.id.board_menu_game_invites, enable ); enable = XWPrefs.getStudyEnabled( m_activity ); - Utils.setItemVisible( menu, R.id.games_menu_study, enable ); + Utils.setItemVisible( menu, R.id.board_menu_study, enable ); return true; } // onPrepareOptionsMenu @@ -910,7 +910,7 @@ public class BoardDelegate extends DelegateBase case R.id.board_menu_tray: cmd = JNICmd.CMD_TOGGLE_TRAY; break; - case R.id.games_menu_study: + case R.id.board_menu_study: StudyListDelegate.launchOrAlert( getDelegator(), m_gi.dictLang, this ); break; case R.id.board_menu_game_netstats: diff --git a/xwords4/android/app/src/main/res/menu/board_menu.xml b/xwords4/android/app/src/main/res/menu/board_menu.xml index 35e3997ef..f49b85f19 100644 --- a/xwords4/android/app/src/main/res/menu/board_menu.xml +++ b/xwords4/android/app/src/main/res/menu/board_menu.xml @@ -57,7 +57,7 @@ - From a3bf94200497ab5caa023db8f52ab9b196aca390 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 28 Sep 2017 06:51:24 -0700 Subject: [PATCH 40/66] fix assertion by not handling Action Was wrong to assert that unexpected Actions never arrived. --- .../main/java/org/eehouse/android/xw4/StudyListDelegate.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/StudyListDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/StudyListDelegate.java index eed42cdee..db51df1f4 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/StudyListDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/StudyListDelegate.java @@ -50,6 +50,7 @@ public class StudyListDelegate extends ListDelegateBase implements OnItemSelectedListener, SelectableItem, View.OnLongClickListener, View.OnClickListener, DBUtils.StudyListListener { + private static final String TAG = StudyListDelegate.class.getSimpleName(); protected static final int NO_LANG = -1; @@ -220,7 +221,8 @@ public class StudyListDelegate extends ListDelegateBase showToast( msg ); break; default: - Assert.assertFalse( BuildConfig.DEBUG ); + Log.d( TAG, "not handling: %s", action ); + handled = false; break; } return handled; From bbffe4caf137621dcc193c1bb990e93ca8a5d2a6 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 28 Sep 2017 06:51:59 -0700 Subject: [PATCH 41/66] Add assert prior to removing dead (?) code There should no longer be a need for curThis(), but let's confim by asserting for a while. --- .../app/src/main/java/org/eehouse/android/xw4/DelegateBase.java | 1 + 1 file changed, 1 insertion(+) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DelegateBase.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DelegateBase.java index 5b9f4da1b..144c61b91 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DelegateBase.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DelegateBase.java @@ -163,6 +163,7 @@ public class DelegateBase implements DlgClickNotify, } if ( this != result ) { Log.d( TAG, "%s.curThis() => " + result, this.toString() ); + Assert.fail(); } return result; } From c9a86a23e05351f10e86fb06690a5b3be32478eb Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 28 Sep 2017 20:02:05 -0700 Subject: [PATCH 42/66] remove GCM-related receiver from shared manifest Duh. The .java file was removed, but the declaration that all apps can handle an intent (that requires WakeLock they no longer all have) was not. Should fix crashes I'm seeing. --- xwords4/android/app/src/main/AndroidManifest.xml | 11 ----------- xwords4/android/app/src/xw4/AndroidManifest.xml | 13 +++++++++++++ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/xwords4/android/app/src/main/AndroidManifest.xml b/xwords4/android/app/src/main/AndroidManifest.xml index 789de40e9..6fa3ddb0f 100644 --- a/xwords4/android/app/src/main/AndroidManifest.xml +++ b/xwords4/android/app/src/main/AndroidManifest.xml @@ -202,16 +202,5 @@ - - - - - - - - - - diff --git a/xwords4/android/app/src/xw4/AndroidManifest.xml b/xwords4/android/app/src/xw4/AndroidManifest.xml index d4935c9d1..aff0f77bc 100644 --- a/xwords4/android/app/src/xw4/AndroidManifest.xml +++ b/xwords4/android/app/src/xw4/AndroidManifest.xml @@ -10,4 +10,17 @@ + + + + + + + + + + + + From 2cabf2332caac61d7f5034071cfbfb766d1f5eba Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 7 Oct 2017 08:03:18 -0700 Subject: [PATCH 43/66] fix compiler-found (but trivial) error debian's new compiler rocks. Effected only test code I never use, but still nice to fix. --- xwords4/linux/linuxmain.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index a79cc4224..e3ff095e6 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -1577,7 +1577,7 @@ parsePair( const char* optarg, XP_U16* min, XP_U16* max ) } else { int intmin, intmax; if ( 2 == sscanf( optarg, "%d:%d", &intmin, &intmax ) ) { - if ( intmin <= intmin ) { + if ( intmin <= intmax ) { *min = intmin; *max = intmax; success = true; From 3e8839636e4f62046f8ebf649cd0e5baf0f761ae Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 7 Oct 2017 10:14:27 -0700 Subject: [PATCH 44/66] preserve hid when recreating games When a device reconnects and there's no record for its game (e.g. because an ISP's screwed up and the db and its host are lost), recreate but keep the device's hid (position in the arrays indicating mostly whether it's the inviter or invitee.) Tested by running the linux scripts and deleting the games table mid-way through, but not yet tested with android devices. --- xwords4/relay/crefmgr.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/xwords4/relay/crefmgr.cpp b/xwords4/relay/crefmgr.cpp index 38a3b8fc6..c08a0d2c9 100644 --- a/xwords4/relay/crefmgr.cpp +++ b/xwords4/relay/crefmgr.cpp @@ -649,10 +649,14 @@ SafeCref::SafeCref( const char* connName, const char* cookie, HostID hid, nPlayersS, gameSeed, langCode, wantsPublic || makePublic, &isDead ); - /* If the reconnect doesn't check out, treat it as a connect */ + /* If the reconnect doesn't check out, treat it as a connect. But + preserve the existing hid. If the DB was deleted it's important + that devices keep their places (hids) */ if ( NULL == cinfo ) { - logf( XW_LOGINFO, "%s: taking a second crack", __func__ ); - m_hid = HOST_ID_NONE; + logf( XW_LOGINFO, "%s: taking a second crack; (cur hid: %d)", + __func__, hid ); + assert( m_hid == hid ); + // m_hid = HOST_ID_NONE; /* wrong; but why was I doing it? */ cinfo = m_mgr->getMakeCookieRef( cookie, nPlayersH, nPlayersS, langCode, gameSeed, clientIndx, wantsPublic, makePublic, &m_seenSeed ); From 36aef059c1132f2e6be77fffda86f108b93262bd Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 7 Oct 2017 10:47:44 -0700 Subject: [PATCH 45/66] remove assertion: deviceID can change Hit this nuking the devices table mid-game. Games recover with the assertion removed (and storing the new/non-matching deviceID) --- xwords4/linux/cursesmain.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index 4548f00ab..ea02def79 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -1721,11 +1721,10 @@ cursesDevIDReceived( void* closure, const XP_UCHAR* devID, /* If we already have one, make sure it's the same! Else store. */ gchar buf[64]; - XP_Bool have = db_fetch( pDb, KEY_RDEVID, buf, sizeof(buf) ); + XP_Bool have = db_fetch( pDb, KEY_RDEVID, buf, sizeof(buf) ) + && 0 == strcmp( buf, devID ); if ( !have ) { db_store( pDb, KEY_RDEVID, devID ); - } else { - XP_ASSERT( 0 == strcmp( buf, devID ) ); } (void)g_timeout_add_seconds( maxInterval, keepalive_timer, globals ); } else { From 4717308c4d900d4f82edefbc3ada53153682e41d Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 10 Oct 2017 19:50:03 -0700 Subject: [PATCH 46/66] make new installs easier Add make target and script option to install debs --- xwords4/relay/Makefile | 18 ++++++++++++++++++ xwords4/relay/xwrelay.sh | 9 ++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/xwords4/relay/Makefile b/xwords4/relay/Makefile index 7915bf203..af2bbae6f 100644 --- a/xwords4/relay/Makefile +++ b/xwords4/relay/Makefile @@ -70,6 +70,24 @@ endif memdebug all: xwrelay rq +REQUIRED_DEBS = libpq-dev \ + +.PHONY: debcheck debs_install + +debs_install: + sudo apt-get install $(REQUIRED_DEBS) + +debcheck: + @if which dpkg; then \ + for DEB in $(REQUIRED_DEBS); do \ + if ! dpkg -l $$DEB >/dev/null 2>&1; then \ + echo "$$DEB not installed"; \ + echo "try running 'make debs_install'"; \ + break; \ + fi \ + done; \ + fi + # Manual config in order to place -lpq after the .obj files as # required by something Ubuntu did upgrading natty to oneiric xwrelay: $(OBJ) diff --git a/xwords4/relay/xwrelay.sh b/xwords4/relay/xwrelay.sh index 4564816ef..dc9e6b7c3 100755 --- a/xwords4/relay/xwrelay.sh +++ b/xwords4/relay/xwrelay.sh @@ -14,7 +14,7 @@ LOGFILE=/tmp/xwrelay_log_$$.txt date > $LOGFILE usage() { - echo "usage: $0 start | stop | restart | mkdb" + echo "usage: $0 start | stop | restart | mkdb | debs_install" } make_db() { @@ -114,6 +114,10 @@ do_start() { fi } +install_debs() { + sudo apt-get install postgresql-client postgresql +} + case $1 in stop) @@ -149,6 +153,9 @@ case $1 in make_db ;; + debs_install) + install_debs + ;; *) usage exit 0 From 0a774625a1a94dda8097294a94347dbbdcde3768 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 29 Sep 2017 05:42:13 -0700 Subject: [PATCH 47/66] cleanup --- xwords4/android/scripts/info.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/xwords4/android/scripts/info.py b/xwords4/android/scripts/info.py index 35006d9d7..936a4dd32 100755 --- a/xwords4/android/scripts/info.py +++ b/xwords4/android/scripts/info.py @@ -114,7 +114,7 @@ def md5Checksums( sums, filePath ): if filePath in sums: result = sums[filePath] else: - logging.debug( "opening %s" % (k_filebase + "and_wordlists/" + filePath)) + # logging.debug( "opening %s" % (k_filebase + "and_wordlists/" + filePath)) try: file = open( k_filebase + "and_wordlists/" + filePath, 'rb' ) md5 = hashlib.md5() @@ -145,7 +145,7 @@ def openShelf(): if not k_SUMS in s_shelf: s_shelf[k_SUMS] = {} if not k_COUNT in s_shelf: s_shelf[k_COUNT] = 0 s_shelf[k_COUNT] += 1 - logging.debug( "Count now %d" % s_shelf[k_COUNT] ) + # logging.debug( "Count now %d" % s_shelf[k_COUNT] ) def closeShelf(): global s_shelf @@ -560,15 +560,13 @@ def getUpdates( req, params ): result[k_DICTS] = dictsResult # Let's not upgrade strings at the same time as we're upgrading the app - if appResult: - logging.debug( 'skipping xlation upgrade because app being updated' ) - elif k_XLATEINFO in asJson and k_NAME in asJson and k_STRINGSHASH in asJson: - xlateResult = getXlate( asJson[k_XLATEINFO], asJson[k_NAME], asJson[k_STRINGSHASH] ) - if xlateResult: - logging.debug( xlateResult ) - result[k_XLATEINFO] = xlateResult; - else: - logging.debug( "NOT FOUND xlate info" ) + # if appResult: + # logging.debug( 'skipping xlation upgrade because app being updated' ) + # elif k_XLATEINFO in asJson and k_NAME in asJson and k_STRINGSHASH in asJson: + # xlateResult = getXlate( asJson[k_XLATEINFO], asJson[k_NAME], asJson[k_STRINGSHASH] ) + # if xlateResult: + # logging.debug( xlateResult ) + # result[k_XLATEINFO] = xlateResult; result = json.dumps( result ) # logging.debug( result ) From 37ecd81c04d4fb676a0dde4e8f5dcb592093ab7f Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 2 Oct 2017 06:42:12 -0700 Subject: [PATCH 48/66] change param names about to introduce a second. This will make things clearer. No code change here though. --- xwords4/relay/dbmgr.cpp | 22 +++++++++++----------- xwords4/relay/dbmgr.h | 5 +++-- xwords4/relay/xwrelay.cpp | 22 +++++++++++----------- xwords4/relay/xwrelay_priv.h | 4 ++-- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index 8c40f9310..95625a9b6 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -1015,10 +1015,10 @@ DBMgr::CountStoredMessages( DevIDRelay relayID ) } void -DBMgr::StoreMessage( DevIDRelay devID, const uint8_t* const buf, +DBMgr::StoreMessage( DevIDRelay destDevID, const uint8_t* const buf, int len ) { - clearHasNoMessages( devID ); + clearHasNoMessages( destDevID ); size_t newLen; const char* fmt = "INSERT INTO " MSGS_TABLE " " @@ -1027,13 +1027,13 @@ DBMgr::StoreMessage( DevIDRelay devID, const uint8_t* const buf, StrWPF query; if ( m_useB64 ) { gchar* b64 = g_base64_encode( buf, len ); - query.catf( fmt, "msg64", devID, "", b64, len ); + query.catf( fmt, "msg64", destDevID, "", b64, len ); g_free( b64 ); } else { uint8_t* bytes = PQescapeByteaConn( getThreadConn(), buf, len, &newLen ); assert( NULL != bytes ); - query.catf( fmt, "msg", devID, "E", bytes, len ); + query.catf( fmt, "msg", destDevID, "E", bytes, len ); PQfreemem( bytes ); } @@ -1042,15 +1042,15 @@ DBMgr::StoreMessage( DevIDRelay devID, const uint8_t* const buf, } void -DBMgr::StoreMessage( const char* const connName, int hid, +DBMgr::StoreMessage( const char* const connName, int destHid, const uint8_t* buf, int len ) { - clearHasNoMessages( connName, hid ); + clearHasNoMessages( connName, destHid ); - DevIDRelay devID = getDevID( connName, hid ); + DevIDRelay devID = getDevID( connName, destHid ); if ( DEVID_NONE == devID ) { logf( XW_LOGERROR, "%s: warning: devid not found for connName=%s, " - "hid=%d", __func__, connName, hid ); + "hid=%d", __func__, connName, destHid ); } else { clearHasNoMessages( devID ); } @@ -1066,7 +1066,7 @@ DBMgr::StoreMessage( const char* const connName, int hid, StrWPF query; if ( m_useB64 ) { gchar* b64 = g_base64_encode( buf, len ); - query.catf( fmt, "msg64", connName, hid, devID, hid, connName, + query.catf( fmt, "msg64", connName, destHid, devID, destHid, connName, "", b64, len ); query.catf( " WHERE NOT EXISTS (SELECT 1 FROM " MSGS_TABLE @@ -1074,14 +1074,14 @@ DBMgr::StoreMessage( const char* const connName, int hid, #ifdef HAVE_STIME " AND stime='epoch'" #endif - " );", connName, hid, b64 ); + " );", connName, destHid, b64 ); g_free( b64 ); } else { uint8_t* bytes = PQescapeByteaConn( getThreadConn(), buf, len, &newLen ); assert( NULL != bytes ); - query.catf( fmt, "msg", connName, hid, devID, hid, connName, + query.catf( fmt, "msg", connName, destHid, devID, destHid, connName, "E", bytes, len ); PQfreemem( bytes ); } diff --git a/xwords4/relay/dbmgr.h b/xwords4/relay/dbmgr.h index 839f4f3c9..690ca5c39 100644 --- a/xwords4/relay/dbmgr.h +++ b/xwords4/relay/dbmgr.h @@ -137,8 +137,9 @@ class DBMgr { /* message storage -- different DB */ int CountStoredMessages( const char* const connName ); int CountStoredMessages( DevIDRelay relayID ); - void StoreMessage( DevIDRelay relayID, const uint8_t* const buf, int len ); - void StoreMessage( const char* const connName, int hid, + void StoreMessage( DevIDRelay destRelayID, const uint8_t* const buf, + int len ); + void StoreMessage( const char* const connName, int destHid, const uint8_t* const buf, int len ); void GetStoredMessages( DevIDRelay relayID, vector& msgs ); void GetStoredMessages( const char* const connName, HostID hid, diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index d22ea8738..666bee3f3 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -760,15 +760,15 @@ send_havemsgs( const AddrInfo* addr ) class MsgClosure { public: - MsgClosure( DevIDRelay devid, const vector* packet, + MsgClosure( DevIDRelay dest, const vector* packet, OnMsgAckProc proc, void* procClosure ) { - m_devid = devid; + m_destDevID = dest; m_packet = *packet; m_proc = proc; m_procClosure = procClosure; } - DevIDRelay m_devid; + DevIDRelay m_destDevID; vector m_packet; OnMsgAckProc m_proc; void* m_procClosure; @@ -779,21 +779,21 @@ onPostedMsgAcked( bool acked, uint32_t packetID, void* data ) { MsgClosure* mc = (MsgClosure*)data; if ( !acked ) { - DBMgr::Get()->StoreMessage( mc->m_devid, mc->m_packet.data(), + DBMgr::Get()->StoreMessage( mc->m_destDevID, mc->m_packet.data(), mc->m_packet.size() ); } if ( NULL != mc->m_proc ) { - (*mc->m_proc)( acked, mc->m_devid, packetID, mc->m_procClosure ); + (*mc->m_proc)( acked, mc->m_destDevID, packetID, mc->m_procClosure ); } delete mc; } static bool -post_or_store( DevIDRelay devid, vector& packet, uint32_t packetID, +post_or_store( DevIDRelay destDevID, vector& packet, uint32_t packetID, OnMsgAckProc proc, void* procClosure ) { - const AddrInfo::AddrUnion* addru = DevMgr::Get()->get( devid ); + const AddrInfo::AddrUnion* addru = DevMgr::Get()->get( destDevID ); bool canSendNow = !!addru; bool sent = false; @@ -805,20 +805,20 @@ post_or_store( DevIDRelay devid, vector& packet, uint32_t packetID, sent = 0 < send_packet_via_udp_impl( packet, sock, dest_addr ); if ( sent ) { - MsgClosure* mc = new MsgClosure( devid, &packet, + MsgClosure* mc = new MsgClosure( destDevID, &packet, proc, procClosure ); UDPAckTrack::setOnAck( onPostedMsgAcked, packetID, (void*)mc ); } } } if ( !sent ) { - DBMgr::Get()->StoreMessage( devid, packet.data(), packet.size() ); + DBMgr::Get()->StoreMessage( destDevID, packet.data(), packet.size() ); } return sent; } bool -post_message( DevIDRelay devid, const char* message, OnMsgAckProc proc, +post_message( DevIDRelay destDevID, const char* message, OnMsgAckProc proc, void* procClosure ) { vector packet; @@ -830,7 +830,7 @@ post_message( DevIDRelay devid, const char* message, OnMsgAckProc proc, assemble_packet( packet, &packetID, XWPDEV_ALERT, lenbuf, lenlen, message, len, NULL ); - return post_or_store( devid, packet, packetID, proc, procClosure ); + return post_or_store( destDevID, packet, packetID, proc, procClosure ); } void diff --git a/xwords4/relay/xwrelay_priv.h b/xwords4/relay/xwrelay_priv.h index 652ae366d..ee85f98c7 100644 --- a/xwords4/relay/xwrelay_priv.h +++ b/xwords4/relay/xwrelay_priv.h @@ -52,8 +52,8 @@ void send_havemsgs( const AddrInfo* addr ); typedef void (*OnMsgAckProc)( bool acked, DevIDRelay devid, uint32_t packetID, void* data ); -bool post_message( DevIDRelay devid, const char* message, OnMsgAckProc proc, - void* data ); +bool post_message( DevIDRelay destDevID, const char* message, + OnMsgAckProc proc, void* data ); void post_upgrade( DevIDRelay devid ); time_t uptime(void); From c7f0090e15e9c105e3da46c5083526f716b951a4 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 10 Oct 2017 20:36:36 -0700 Subject: [PATCH 49/66] fix script that makes the db I've had it wrong all these years. Thanks Dave! --- xwords4/relay/xwrelay.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xwords4/relay/xwrelay.sh b/xwords4/relay/xwrelay.sh index dc9e6b7c3..1665c603f 100755 --- a/xwords4/relay/xwrelay.sh +++ b/xwords4/relay/xwrelay.sh @@ -28,7 +28,7 @@ make_db() { exit 1 fi createdb $DBNAME - cat | psql $DBNAME --file - < Date: Wed, 11 Oct 2017 07:39:09 -0700 Subject: [PATCH 50/66] try to fix travis for new host --- .travis.yml | 13 +++++++------ id_rsa_uploader.enc | Bin 0 -> 1680 bytes 2 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 id_rsa_uploader.enc diff --git a/.travis.yml b/.travis.yml index 9677518ac..0dab7d096 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ before_script: - export TERM=dumb - curl -L http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin -O - chmod u+x android-ndk-r10e-linux-x86_64.bin -- ./android-ndk-r10e-linux-x86_64.bin > /dev/null +- "./android-ndk-r10e-linux-x86_64.bin > /dev/null" - rm android-ndk-r10e-linux-x86_64.bin - export ANDROID_NDK_HOME=`pwd`/android-ndk-r10e - export LOCAL_ANDROID_NDK_HOME="$ANDROID_NDK_HOME" @@ -23,17 +23,18 @@ before_script: - export PATH=$PATH:${ANDROID_NDK_HOME} - cd xwords4/android/ before_install: -- openssl aes-256-cbc -K $encrypted_b9c66bb79572_key -iv $encrypted_b9c66bb79572_iv - -in xwords4/android/id_rsa_uploader.enc -out /tmp/id_rsa_uploader -d +- openssl aes-256-cbc -K $encrypted_8436f2891714_key -iv $encrypted_8436f2891714_iv + -in id_rsa_uploader.enc -out /tmp/id_rsa_uploader -d - chmod 600 \/tmp\/id_rsa_uploader - sudo apt-get -qq update - sudo apt-get install -y python-lxml imagemagick script: -- ./gradlew -PuseCrashlytics assXw4dDeb -- scp -o "StrictHostKeyChecking no" -i /tmp/id_rsa_uploader -d app/build/outputs/apk/*.apk uploader@eehouse.org:XW4D_UPLOAD +- "./gradlew -PuseCrashlytics assXw4dDeb" +- scp -o "StrictHostKeyChecking no" -i /tmp/id_rsa_uploader -d app/build/outputs/apk/*.apk + uploader@eehouse.org:XW4D_UPLOAD notifications: email: recipients: - xwords@eehouse.org - on_success: never + on_success: always on_failure: always diff --git a/id_rsa_uploader.enc b/id_rsa_uploader.enc new file mode 100644 index 0000000000000000000000000000000000000000..16e5dd866b419c5903e78855fdbef6266efdc9d3 GIT binary patch literal 1680 zcmV;B25j;~$oq3eXg-=w$d?VIHs2|c2es~e~oBL-dD3BI^ zY^VGqEC^B8>Eg!UaC8CH#HzO~yRbb$ikhhU!|22tz!b}fF&B-!gn<`J+SkMW{xkX9 zN+POGRE@cT4ZsSa;xN*|&Jv66GG}5pd?C@7RsUnbhC@kAx7=-$*l%k07+W=zVNcW7 z<$Cp*SIM05J?jwE`h0T;`ZxjUCTR+arbj)_C5;x-tCO|V$HXi)6kz}ln={Uwu0PLl zS!-aI{9V(NqgHhCg5~S)guDo0U8cUlREZ!4$?JWB>Msd*maNR&*Oq+LnHJBtmF7sb zxmhSjfKrGWA^(`rqk8l$*+yr;KxsmKQ;DGYZ0GjU-jDo43t}lMrX$l{<}mG&Ki|)Qd;M?)`cU(Cd_!N{Y@7xgGUrb{5D_H_MVGP-J9iD3skr&R_koX`ItT9i zl~&kTAmS!0^}>}L?%iXr|0ae4^4+jTyePe3fi@Z&F3QrdK(C-eL{))OItzO7$wWR9e{Zx!}jdmeb-wro=LYR|mc z@X}WK+KaJ$FTTSGo~=BP^}5&sk7Au3a{?Q0D7p3P>=lMl{;o)!!p{e~7=U<)p9pg+ zSVa+CijPSll3jiF|D&*o(9NCjO7l{4%+$BWmaZ zI-re+$kTHov)y@FPf)3EaQ_ay`Bat7@~=}~XMsii{%Oj{UnpsU?G+*>Cr+~pPGWFW z4U1VCSCS9Zu69t^dXk)&1M?@grJGgQTz^DPvY(5EfOvQix*|F-N}t18DyO=mjk4;t zA<##C_BfbX3%ZZC6qM1pgrlcGrrPC64pQd}lIT|z=^%~X6SHO{0??`(-d~MtI2q)T z@B4Dp#mB3Scv0>Ez1WxigS*RFgdn*D&AG$~SCMW@!EMQraOn3VW@Fhb3NXZ%0PD}~ z(oA^CAc6X|vbo<_Z7O1Bvzi)MlYiQ2?>fb*h6p!$)h@C3@b9mbxpJ$W4Wc2XUM{eP z$tVi&!O(}yxt#SQ$s&Mtjeqh*n57lZNz?7@!KAj>loT7Kuwhnn-MN2{xTk0y)`oX? z$u&t+DtBX(h~*^4{XL|LTG)!|>!WW!&Epjb{?H>y6dK+bo-UaVC!Yx#3sP30W*JU4 zcXLX9U9Ow~{akw#jpHMsS|nM5LaFj=%=nckek_2?;@D7 zM9q8HueyA3+qEoqFa=f-wgQCh>jXtrPsj&~yS;~|*8%IlzdRi?xHYK50;Klz6FyYM zdDYK>hzEnypMF0u3zi|A1d7zrK{2Qi*7M%WI~Gha%&YA5WRb@%&^7~C@ z^_Z19DCO^Johav}=nV!@S$GL}_+n1{aKz_VI?-$(V3D$ZM^Ri}{ipk!r`K%Awm9ghV}X&xxm1A)zkJq&M>8ZEQB6Cp=K!P5q(Z&1d{g$r(3Yuk-ANLoI&ur(cfz*1 zfo7nzBqrXD^xie%$av3&BerWVKD9ip%N?RT0R{+-E}tlqXxSNdLZ3148fRTcc7fJ? aeNmCZc2}EwMv8Ml>p10eTr+}<#=3lee@?9c literal 0 HcmV?d00001 From 1e38069b9406f1f427e9932969ec402a0a390b4e Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 12 Oct 2017 21:14:07 -0700 Subject: [PATCH 51/66] update script seen by those without the app installed --- xwords4/android/scripts/and_index.php | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/xwords4/android/scripts/and_index.php b/xwords4/android/scripts/and_index.php index 7d0a53cc1..affecb246 100644 --- a/xwords4/android/scripts/and_index.php +++ b/xwords4/android/scripts/and_index.php @@ -10,7 +10,7 @@ function printHead() { - Crosswords Invite redirect + CrossWords Invite redirect

@@ -51,32 +51,31 @@ function printAndroid() { print <<

You'll have come here after clicking a link in an email or - text inviting you to a Crosswords game. But you should not be seeing + text inviting you to a CrossWords game. But you should not be seeing this page.

If you got this page on your device, it means either

    -
  • The copy of Crosswords you have is NOT beta 56 or newer (dating from about Dec. 1, 2012).
  • -
  • OR
  • -
  • that your copy of Crosswords is new enough BUT that - when you clicked on the link and were asked to choose between a - browser and Crosswords you chose the browser.
  • +
  • You don't have CrossWords installed
  • +
  • OR
  • +
  • that when you clicked on the link and were asked to choose between a + browser and CrossWords you chose the browser.

-

In the first case, install the latest Crosswords, +

In the first case, install the latest CrossWords, either via the Google Play store or -(sideloading) via +(sideloading) via Sourceforge.net. After the install is finished go back to the invite email (or text) and tap the link again.

In the second case, hit your browser's back button, click the link in your invite email (or text) again, and this time let -Crosswords handle it.

+CrossWords handle it.

(If you get tired of having to having to make that choice, Android -will allow you to make Crosswords the default. If you do that -Crosswords will be given control of all URLs that start with +will allow you to make CrossWords the default. If you do that +CrossWords will be given control of all URLs that start with "http://eehouse.org/and/" -- not all URLs of any type.)

Have fun. And as always, let From 927c4f12a04ca402dee7f70805a0e0fef81970ed Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 14 Oct 2017 12:51:51 -0700 Subject: [PATCH 52/66] make tap on thumbnail select/deselect Working around there being a border around the game-type image area. With this change long-tapping works only on the right 2/3 of the region. There might be a fix, but it's still better than there being a hole (the border) in the thing where behavior's different. --- .../org/eehouse/android/xw4/GameListItem.java | 49 +++++++++++-------- .../src/main/res/layout/game_list_item.xml | 5 +- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java index 2301c75ac..f3cd41e91 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java @@ -90,16 +90,6 @@ public class GameListItem extends LinearLayout m_lastMoveTime = 0; m_loadingCount = 0; m_dsdel = new DrawSelDelegate( this ); - - setOnClickListener( new View.OnClickListener() { - @Override - public void onClick( View v ) { - // if selected, just un-select - if ( null != m_summary ) { - m_cb.itemClicked( GameListItem.this, m_summary ); - } - } - } ); } public GameSummary getSummary() @@ -174,13 +164,32 @@ public class GameListItem extends LinearLayout } // View.OnClickListener interface - public void onClick( View view ) { - m_expanded = !m_expanded; - DBUtils.setExpanded( m_rowid, m_expanded ); + public void onClick( View view ) + { + int id = view.getId(); + switch ( id ) { + case R.id.expander: + m_expanded = !m_expanded; + DBUtils.setExpanded( m_rowid, m_expanded ); - makeThumbnailIf( m_expanded ); + makeThumbnailIf( m_expanded ); - showHide(); + showHide(); + break; + + case R.id.view_loaded: + toggleSelected(); + break; + + case R.id.right_side: + if ( null != m_summary ) { + m_cb.itemClicked( GameListItem.this, m_summary ); + } + break; + default: + Assert.assertFalse(BuildConfig.DEBUG); + break; + } } private void findViews() @@ -191,12 +200,15 @@ public class GameListItem extends LinearLayout m_expandButton.setOnClickListener( this ); m_viewUnloaded = (TextView)findViewById( R.id.view_unloaded ); m_viewLoaded = findViewById( R.id.view_loaded ); + m_viewLoaded.setOnClickListener( this ); m_list = (LinearLayout)findViewById( R.id.player_list ); m_state = (TextView)findViewById( R.id.state ); m_modTime = (TextView)findViewById( R.id.modtime ); m_gameTypeImage = (ImageView)findViewById( R.id.game_type_marker ); m_thumb = (ImageView)findViewById( R.id.thumbnail ); m_role = (TextView)findViewById( R.id.role ); + + findViewById( R.id.right_side ).setOnClickListener( this ); } private void setLoaded( boolean loaded ) @@ -317,12 +329,6 @@ public class GameListItem extends LinearLayout int iconID = summary.isMultiGame() ? R.drawable.multigame__gen : R.drawable.sologame__gen; m_gameTypeImage.setImageResource( iconID ); - m_gameTypeImage.setOnClickListener( new View.OnClickListener() { - @Override - public void onClick( View view ) { - toggleSelected(); - } - } ); boolean hasChat = summary.isMultiGame(); if ( hasChat ) { @@ -427,6 +433,7 @@ public class GameListItem extends LinearLayout // } // GameListAdapter.ClickHandler interface + @Override public void longClicked() { toggleSelected(); diff --git a/xwords4/android/app/src/main/res/layout/game_list_item.xml b/xwords4/android/app/src/main/res/layout/game_list_item.xml index 77972e134..2591b7afe 100644 --- a/xwords4/android/app/src/main/res/layout/game_list_item.xml +++ b/xwords4/android/app/src/main/res/layout/game_list_item.xml @@ -61,9 +61,12 @@ - From 6b0fe35c8f288792dad90cce0b95d951cb816362 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 16 Oct 2017 22:12:35 -0700 Subject: [PATCH 53/66] use readFully() rather than read() readFully() blocks; read() can return with less than you expect. --- .../org/eehouse/android/xw4/BTService.java | 32 ++++++++----------- .../org/eehouse/android/xw4/BiDiSockWrap.java | 6 ++-- .../org/eehouse/android/xw4/NetUtils.java | 2 +- .../eehouse/android/xw4/RefreshNamesTask.java | 2 +- .../org/eehouse/android/xw4/RelayService.java | 12 +++---- .../org/eehouse/android/xw4/SMSService.java | 4 +-- 6 files changed, 24 insertions(+), 34 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTService.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTService.java index 70df610ca..1fb0a9c3f 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTService.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTService.java @@ -550,7 +550,7 @@ public class BTService extends XWService { } else { short len = is.readShort(); byte[] nliData = new byte[len]; - is.read( nliData ); + is.readFully( nliData ); nli = XwJNI.nliFromStream( nliData ); } @@ -573,26 +573,20 @@ public class BTService extends XWService { int gameID = dis.readInt(); switch ( cmd ) { case MESG_SEND: - short len = dis.readShort(); - byte[] buffer = new byte[len]; - int nRead = dis.read( buffer, 0, len ); - if ( nRead == len ) { - BluetoothDevice host = socket.getRemoteDevice(); - addAddr( host ); + byte[] buffer = new byte[dis.readShort()]; + dis.readFully( buffer ); + BluetoothDevice host = socket.getRemoteDevice(); + addAddr( host ); - CommsAddrRec addr = new CommsAddrRec( host.getName(), - host.getAddress() ); - ReceiveResult rslt - = BTService.this.receiveMessage( BTService.this, - gameID, m_btMsgSink, - buffer, addr ); + CommsAddrRec addr = new CommsAddrRec( host.getName(), + host.getAddress() ); + ReceiveResult rslt + = BTService.this.receiveMessage( BTService.this, + gameID, m_btMsgSink, + buffer, addr ); - result = rslt == ReceiveResult.GAME_GONE ? - BTCmd.MESG_GAMEGONE : BTCmd.MESG_ACCPT; - } else { - Log.e( TAG, "receiveMessage(): read only %d of %d bytes", - nRead, len ); - } + result = rslt == ReceiveResult.GAME_GONE ? + BTCmd.MESG_GAMEGONE : BTCmd.MESG_ACCPT; break; case MESG_GAMEGONE: postEvent( MultiEvent.MESSAGE_NOGAME, gameID ); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BiDiSockWrap.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BiDiSockWrap.java index 9b97432d5..e2744c9f5 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BiDiSockWrap.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BiDiSockWrap.java @@ -188,10 +188,8 @@ public class BiDiSockWrap { DataInputStream inStream = new DataInputStream( mSocket.getInputStream() ); while ( mRunThreads ) { - short len = inStream.readShort(); - Log.d( TAG, "got len: %d", len ); - byte[] packet = new byte[len]; - inStream.read( packet ); + byte[] packet = new byte[inStream.readShort()]; + inStream.readFully( packet ); mIface.gotPacket( BiDiSockWrap.this, packet ); } } catch( IOException ioe ) { diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NetUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NetUtils.java index 62ff478da..78c43b68e 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NetUtils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NetUtils.java @@ -184,7 +184,7 @@ public class NetUtils { short len = dis.readShort(); if ( len > 0 ) { byte[] packet = new byte[len]; - dis.read( packet ); + dis.readFully( packet ); msgs[ii][jj] = packet; } } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RefreshNamesTask.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RefreshNamesTask.java index f94f04725..9844be6d8 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RefreshNamesTask.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RefreshNamesTask.java @@ -94,7 +94,7 @@ public class RefreshNamesTask extends AsyncTask { // Can't figure out how to read a null-terminated string // from DataInputStream so parse it myself. byte[] bytes = new byte[len]; - dis.read( bytes ); + dis.readFully( bytes ); int index = -1; for ( int ii = 0; ii < nRooms; ++ii ) { diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java index 740aa945d..89c24ebb4 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java @@ -736,7 +736,7 @@ public class RelayService extends XWService case XWPDEV_MSG: int token = dis.readInt(); byte[] msg = new byte[dis.available()]; - dis.read( msg ); + dis.readFully( msg ); postData( this, token, msg ); // game-related packets only count @@ -756,9 +756,8 @@ public class RelayService extends XWService resetBackoff = true; intent = getIntentTo( this, MsgCmds.GOT_INVITE ); int srcDevID = dis.readInt(); - short len = dis.readShort(); - byte[] nliData = new byte[len]; - dis.read( nliData ); + byte[] nliData = new byte[dis.readShort()]; + dis.readFully( nliData ); NetLaunchInfo nli = XwJNI.nliFromStream( nliData ); intent.putExtra( INVITE_FROM, srcDevID ); String asStr = nli.toString(); @@ -995,9 +994,8 @@ public class RelayService extends XWService private String getVLIString( DataInputStream dis ) throws java.io.IOException { - int len = vli2un( dis ); - byte[] tmp = new byte[len]; - dis.read( tmp ); + byte[] tmp = new byte[vli2un( dis )]; + dis.readFully( tmp ); String result = new String( tmp ); return result; } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/SMSService.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/SMSService.java index 38c1bef61..c5a226eec 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/SMSService.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/SMSService.java @@ -522,7 +522,7 @@ public class SMSService extends XWService { case DATA: int gameID = dis.readInt(); byte[] rest = new byte[dis.available()]; - dis.read( rest ); + dis.readFully( rest ); if ( feedMessage( gameID, rest, new CommsAddrRec( phone ) ) ) { SMSResendReceiver.resetTimer( this ); } @@ -618,7 +618,7 @@ public class SMSService extends XWService { } else { SMS_CMD cmd = SMS_CMD.values()[dis.readByte()]; byte[] rest = new byte[dis.available()]; - dis.read( rest ); + dis.readFully( rest ); receive( cmd, rest, senderPhone ); success = true; } From da3f6db9e44a8f46f29efdc441c4a92b47f015ef Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 11 Nov 2017 08:10:48 -0800 Subject: [PATCH 54/66] fix failure to notify relay of half-game deletion Wasn't detecting as a relay game something registered on relay but not yet joined by another device, a common case! --- .../main/java/org/eehouse/android/xw4/GameUtils.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java index dba4c57de..92c476792 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java @@ -1196,7 +1196,7 @@ public class GameUtils { for ( CommsConnType typ : conTypes ) { switch ( typ ) { case COMMS_CONN_RELAY: - tellRelayDied( context, summary, informNow ); + // see below break; case COMMS_CONN_BT: BTService.gameDied( context, addr.bt_btAddr, gameID ); @@ -1210,6 +1210,14 @@ public class GameUtils { } } } + + // comms doesn't have a relay address for us until the game's + // in play (all devices registered, at least.) To enable + // deleting on relay half-games that we created but nobody + // joined, special-case this one. + if ( summary.inRelayGame() ) { + tellRelayDied( context, summary, informNow ); + } gamePtr.release(); } From 39deeeb089099447bb72ddad301371b26dd372cf Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 11 Nov 2017 15:43:07 -0800 Subject: [PATCH 55/66] fix tap down lower not opening game Making the right_side elem match its parent height prevents the lower-right region of game list items from falling through and triggering a toggle-selection event. --- xwords4/android/app/src/main/res/layout/game_list_item.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/android/app/src/main/res/layout/game_list_item.xml b/xwords4/android/app/src/main/res/layout/game_list_item.xml index 2591b7afe..49e924837 100644 --- a/xwords4/android/app/src/main/res/layout/game_list_item.xml +++ b/xwords4/android/app/src/main/res/layout/game_list_item.xml @@ -64,7 +64,7 @@ From f072c68bf9a4f556b982064710e7b97069f103fb Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 12 Nov 2017 20:25:29 -0800 Subject: [PATCH 56/66] bring in changes from relay_via_http branch It's time to make them live so client development can use a live relay, and all the old tests pass, so why not. --- xwords4/relay/Makefile | 4 +- xwords4/relay/cref.cpp | 34 +++---- xwords4/relay/cref.h | 3 +- xwords4/relay/crefmgr.cpp | 63 ++++++++++++- xwords4/relay/crefmgr.h | 6 +- xwords4/relay/dbmgr.cpp | 135 ++++++++++++++++++---------- xwords4/relay/dbmgr.h | 15 ++-- xwords4/relay/scripts/showinplay.sh | 7 +- xwords4/relay/xwrelay.cpp | 84 ++++++++++------- 9 files changed, 231 insertions(+), 120 deletions(-) diff --git a/xwords4/relay/Makefile b/xwords4/relay/Makefile index af2bbae6f..06fc29fa8 100644 --- a/xwords4/relay/Makefile +++ b/xwords4/relay/Makefile @@ -42,7 +42,7 @@ SRC = \ # STATIC ?= -static GITINFO = gitversion.txt -HASH=$(shell git describe) +HASH=$(shell git rev-parse --verify HEAD) OBJ = $(patsubst %.cpp,obj/%.o,$(SRC)) #LDFLAGS += -pthread -g -lmcheck $(STATIC) @@ -70,7 +70,7 @@ endif memdebug all: xwrelay rq -REQUIRED_DEBS = libpq-dev \ +REQUIRED_DEBS = libpq-dev g++ libglib2.0-dev postgresql \ .PHONY: debcheck debs_install diff --git a/xwords4/relay/cref.cpp b/xwords4/relay/cref.cpp index 86c8c8e8f..07dfa354e 100644 --- a/xwords4/relay/cref.cpp +++ b/xwords4/relay/cref.cpp @@ -875,13 +875,13 @@ putNetShort( uint8_t** bufpp, unsigned short s ) *bufpp += sizeof(s); } -void +int CookieRef::store_message( HostID dest, const uint8_t* buf, unsigned int len ) { logf( XW_LOGVERBOSE0, "%s: storing msg size %d for dest %d", __func__, len, dest ); - DBMgr::Get()->StoreMessage( ConnName(), dest, buf, len ); + return DBMgr::Get()->StoreMessage( ConnName(), dest, buf, len ); } void @@ -1044,6 +1044,7 @@ CookieRef::postCheckAllHere() void CookieRef::postDropDevice( HostID hostID ) { + logf( XW_LOGINFO, "%s(hostID=%d)", __func__, hostID ); CRefEvent evt( XWE_ACKTIMEOUT ); evt.u.ack.srcID = hostID; m_eventQueue.push_back( evt ); @@ -1192,21 +1193,16 @@ CookieRef::sendAnyStored( const CRefEvent* evt ) } typedef struct _StoreData { - string connName; - HostID dest; - uint8_t* buf; - int buflen; + int msgID; } StoreData; void CookieRef::storeNoAck( bool acked, uint32_t packetID, void* data ) { StoreData* sdata = (StoreData*)data; - if ( !acked ) { - DBMgr::Get()->StoreMessage( sdata->connName.c_str(), sdata->dest, - sdata->buf, sdata->buflen ); + if ( acked ) { + DBMgr::Get()->RemoveStoredMessages( &sdata->msgID, 1 ); } - free( sdata->buf ); delete sdata; } @@ -1237,17 +1233,13 @@ CookieRef::forward_or_store( const CRefEvent* evt ) } uint32_t packetID = 0; + int msgID = store_message( dest, buf, buflen ); if ( (NULL == destAddr) || !send_with_length( destAddr, dest, buf, buflen, true, &packetID ) ) { - store_message( dest, buf, buflen ); - } else if ( 0 != packetID ) { // sent via UDP + } else if ( 0 != msgID && 0 != packetID ) { // sent via UDP StoreData* data = new StoreData; - data->connName = m_connName; - data->dest = dest; - data->buf = (uint8_t*)malloc( buflen ); - memcpy( data->buf, buf, buflen ); - data->buflen = buflen; + data->msgID = msgID; UDPAckTrack::setOnAck( storeNoAck, packetID, data ); } @@ -1376,20 +1368,16 @@ CookieRef::sendAllHere( bool initial ) through the vector each time. */ HostID dest; for ( dest = 1; dest <= m_nPlayersSought; ++dest ) { - bool sent = false; *idLoc = dest; /* write in this target's hostId */ { RWReadLock rrl( &m_socketsRWLock ); HostRec* hr = m_sockets[dest-1]; if ( !!hr ) { - sent = send_with_length( &hr->m_addr, dest, buf, - bufp-buf, true ); + (void)send_with_length( &hr->m_addr, dest, buf, bufp-buf, true ); } } - if ( !sent ) { - store_message( dest, buf, bufp-buf ); - } + (void)store_message( dest, buf, bufp-buf ); } } /* sendAllHere */ diff --git a/xwords4/relay/cref.h b/xwords4/relay/cref.h index 508a4485c..61a677976 100644 --- a/xwords4/relay/cref.h +++ b/xwords4/relay/cref.h @@ -275,8 +275,7 @@ class CookieRef { bool notInUse(void) { return m_cid == 0; } - void store_message( HostID dest, const uint8_t* buf, - unsigned int len ); + int store_message( HostID dest, const uint8_t* buf, unsigned int len ); void send_stored_messages( HostID dest, const AddrInfo* addr ); void printSeeds( const char* caller ); diff --git a/xwords4/relay/crefmgr.cpp b/xwords4/relay/crefmgr.cpp index c08a0d2c9..882f0eb27 100644 --- a/xwords4/relay/crefmgr.cpp +++ b/xwords4/relay/crefmgr.cpp @@ -337,7 +337,7 @@ CRefMgr::getMakeCookieRef( const char* connName, const char* cookie, } /* getMakeCookieRef */ CidInfo* -CRefMgr::getMakeCookieRef( const char* const connName, bool* isDead ) +CRefMgr::getMakeCookieRef( const char* const connName, HostID hid, bool* isDead ) { CookieRef* cref = NULL; CidInfo* cinfo = NULL; @@ -347,7 +347,7 @@ CRefMgr::getMakeCookieRef( const char* const connName, bool* isDead ) int nAlreadyHere = 0; for ( ; ; ) { /* for: see comment above */ - CookieID cid = m_db->FindGame( connName, curCookie, sizeof(curCookie), + CookieID cid = m_db->FindGame( connName, hid, curCookie, sizeof(curCookie), &curLangCode, &nPlayersT, &nAlreadyHere, isDead ); if ( 0 != cid ) { /* already open */ @@ -375,6 +375,48 @@ CRefMgr::getMakeCookieRef( const char* const connName, bool* isDead ) return cinfo; } +CidInfo* +CRefMgr::getMakeCookieRef( const AddrInfo::ClientToken clientToken, HostID srcID ) +{ + CookieRef* cref = NULL; + CidInfo* cinfo = NULL; + char curCookie[MAX_INVITE_LEN+1]; + int curLangCode; + int nPlayersT = 0; + int nAlreadyHere = 0; + + for ( ; ; ) { /* for: see comment above */ + char connName[MAX_CONNNAME_LEN+1] = {0}; + CookieID cid = m_db->FindGame( clientToken, srcID, + connName, sizeof(connName), + curCookie, sizeof(curCookie), + &curLangCode, &nPlayersT, &nAlreadyHere ); + // &seed ); + if ( 0 != cid ) { /* already open */ + cinfo = m_cidlock->Claim( cid ); + if ( NULL == cinfo->GetRef() ) { + m_cidlock->Relinquish( cinfo, true ); + continue; + } + } else if ( nPlayersT == 0 ) { /* wasn't in the DB */ + /* do nothing; insufficient info to fake it */ + } else { + cinfo = m_cidlock->Claim(); + if ( !m_db->AddCID( connName, cinfo->GetCid() ) ) { + m_cidlock->Relinquish( cinfo, true ); + continue; + } + logf( XW_LOGINFO, "%s(): added cid???", __func__ ); + cref = AddNew( curCookie, connName, cinfo->GetCid(), curLangCode, + nPlayersT, nAlreadyHere ); + cinfo->SetRef( cref ); + } + break; + } + logf( XW_LOGINFO, "%s() => %p", __func__, cinfo ); + return cinfo; +} + void CRefMgr::RemoveSocketRefs( const AddrInfo* addr ) { @@ -672,13 +714,13 @@ SafeCref::SafeCref( const char* connName, const char* cookie, HostID hid, } /* ConnName case -- must exist (unless DB record's been removed */ -SafeCref::SafeCref( const char* const connName ) +SafeCref::SafeCref( const char* const connName, HostID hid ) : m_cinfo( NULL ) , m_mgr( CRefMgr::Get() ) , m_isValid( false ) { bool isDead = false; - CidInfo* cinfo = m_mgr->getMakeCookieRef( connName, &isDead ); + CidInfo* cinfo = m_mgr->getMakeCookieRef( connName, hid, &isDead ); if ( NULL != cinfo && NULL != cinfo->GetRef() ) { assert( cinfo->GetCid() == cinfo->GetRef()->GetCid() ); m_locked = cinfo->GetRef()->Lock(); @@ -722,6 +764,19 @@ SafeCref::SafeCref( const AddrInfo* addr ) } } +SafeCref::SafeCref( const AddrInfo::ClientToken clientToken, HostID srcID ) + : m_cinfo( NULL ) + , m_mgr( CRefMgr::Get() ) + , m_isValid( false ) +{ + CidInfo* cinfo = m_mgr->getMakeCookieRef( clientToken, srcID ); + if ( NULL != cinfo && NULL != cinfo->GetRef() ) { + m_locked = cinfo->GetRef()->Lock(); + m_cinfo = cinfo; + m_isValid = true; + } +} + SafeCref::~SafeCref() { if ( m_cinfo != NULL ) { diff --git a/xwords4/relay/crefmgr.h b/xwords4/relay/crefmgr.h index 315bce9c8..7694759dd 100644 --- a/xwords4/relay/crefmgr.h +++ b/xwords4/relay/crefmgr.h @@ -128,7 +128,8 @@ class CRefMgr { int nPlayersS, int seed, int langCode, bool isPublic, bool* isDead ); - CidInfo* getMakeCookieRef( const char* const connName, bool* isDead ); + CidInfo* getMakeCookieRef( const char* const connName, HostID hid, bool* isDead ); + CidInfo* getMakeCookieRef( const AddrInfo::ClientToken clientToken, HostID srcID ); CidInfo* getCookieRef( CookieID cid, bool failOk = false ); CidInfo* getCookieRef( const AddrInfo* addr ); @@ -179,9 +180,10 @@ class SafeCref { const AddrInfo* addr, int clientVersion, DevID* devID, int nPlayersH, int nPlayersS, unsigned short gameSeed, int clientIndx, int langCode, bool wantsPublic, bool makePublic ); - SafeCref( const char* const connName ); + SafeCref( const char* const connName, HostID hid ); SafeCref( CookieID cid, bool failOk = false ); SafeCref( const AddrInfo* addr ); + SafeCref( const AddrInfo::ClientToken clientToken, HostID srcID ); /* SafeCref( CookieRef* cref ); */ ~SafeCref(); diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index 95625a9b6..99492a87a 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -70,20 +70,6 @@ DBMgr::DBMgr() pthread_mutex_init( &m_haveNoMessagesMutex, NULL ); - /* Now figure out what the largest cid currently is. There must be a way - to get postgres to do this for me.... */ - /* const char* query = "SELECT cid FROM games ORDER BY cid DESC LIMIT 1"; */ - /* PGresult* result = PQexec( m_pgconn, query ); */ - /* if ( 0 == PQntuples( result ) ) { */ - /* m_nextCID = 1; */ - /* } else { */ - /* char* value = PQgetvalue( result, 0, 0 ); */ - /* m_nextCID = 1 + atoi( value ); */ - /* } */ - /* PQclear(result); */ - /* logf( XW_LOGINFO, "%s: m_nextCID=%d", __func__, m_nextCID ); */ - - // I've seen rand returning the same series several times.... srand( time( NULL ) ); } @@ -107,7 +93,7 @@ DBMgr::AddNew( const char* cookie, const char* connName, CookieID cid, qb.appendQueryf( "INSERT INTO " GAMES_TABLE " (cid, room, connName, nTotal, lang, pub)" " VALUES( $$, $$, $$, $$, $$, $$ )" ) - .appendParam(cid) + .appendParam(cid) .appendParam(cookie) .appendParam(connName) .appendParam(nPlayersT) @@ -136,7 +122,7 @@ DBMgr::FindGameFor( const char* connName, char* cookieBuf, int bufLen, { bool found = false; - const char* fmt = "SELECT cid, room, lang, nPerDevice, dead FROM " + const char* fmt = "SELECT cid, room, lang, dead FROM " GAMES_TABLE " WHERE connName = '%s' AND nTotal = %d " "AND %d = seeds[%d] AND 'A' = ack[%d] " ; @@ -148,10 +134,11 @@ DBMgr::FindGameFor( const char* connName, char* cookieBuf, int bufLen, 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]; + int col = 0; + *cidp = atoi( PQgetvalue( result, 0, col++ ) ); + snprintf( cookieBuf, bufLen, "%s", PQgetvalue( result, 0, col++ ) ); + *langP = atoi( PQgetvalue( result, 0, col++ ) ); + *isDead = 't' == PQgetvalue( result, 0, col++ )[0]; } PQclear( result ); @@ -160,28 +147,29 @@ DBMgr::FindGameFor( const char* connName, char* cookieBuf, int bufLen, } /* FindGameFor */ CookieID -DBMgr::FindGame( const char* connName, char* cookieBuf, int bufLen, +DBMgr::FindGame( const char* connName, HostID hid, char* roomBuf, int roomBufLen, int* langP, int* nPlayersTP, int* nPlayersHP, bool* isDead ) { CookieID cid = 0; - const char* fmt = "SELECT cid, room, lang, nTotal, nPerDevice, dead FROM " + const char* fmt = "SELECT cid, room, lang, nTotal, nPerDevice[%d], dead FROM " GAMES_TABLE " WHERE connName = '%s'" // " LIMIT 1" ; StrWPF query; - query.catf( fmt, connName ); + query.catf( fmt, hid, 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 ) ); - *langP = atoi( PQgetvalue( result, 0, 2 ) ); - *nPlayersTP = atoi( PQgetvalue( result, 0, 3 ) ); - *nPlayersHP = atoi( PQgetvalue( result, 0, 4 ) ); - *isDead = 't' == PQgetvalue( result, 0, 5 )[0]; + int col = 0; + cid = atoi( PQgetvalue( result, 0, col++ ) ); + snprintf( roomBuf, roomBufLen, "%s", PQgetvalue( result, 0, col++ ) ); + *langP = atoi( PQgetvalue( result, 0, col++ ) ); + *nPlayersTP = atoi( PQgetvalue( result, 0, col++ ) ); + *nPlayersHP = atoi( PQgetvalue( result, 0, col++ ) ); + *isDead = 't' == PQgetvalue( result, 0, col++ )[0]; } PQclear( result ); @@ -189,6 +177,40 @@ DBMgr::FindGame( const char* connName, char* cookieBuf, int bufLen, return cid; } /* FindGame */ +CookieID +DBMgr::FindGame( const AddrInfo::ClientToken clientToken, HostID hid, + char* connNameBuf, int connNameBufLen, + char* roomBuf, int roomBufLen, + int* langP, int* nPlayersTP, int* nPlayersHP ) +{ + CookieID cid = 0; + const char* fmt = "SELECT cid, room, lang, nTotal, nPerDevice[%d], connname FROM " + GAMES_TABLE " WHERE tokens[%d] = %d and NOT dead"; + // " LIMIT 1" + ; + StrWPF query; + query.catf( fmt, hid, hid, clientToken ); + logf( XW_LOGINFO, "query: %s", query.c_str() ); + + PGresult* result = PQexec( getThreadConn(), query.c_str() ); + if ( 1 == PQntuples( result ) ) { + int col = 0; + cid = atoi( PQgetvalue( result, 0, col++ ) ); + // room + snprintf( roomBuf, roomBufLen, "%s", PQgetvalue( result, 0, col++ ) ); + // lang + *langP = atoi( PQgetvalue( result, 0, col++ ) ); + *nPlayersTP = atoi( PQgetvalue( result, 0, col++ ) ); + *nPlayersHP = atoi( PQgetvalue( result, 0, col++ ) ); + snprintf( connNameBuf, connNameBufLen, "%s", PQgetvalue( result, 0, col++ ) ); + } + PQclear( result ); + + logf( XW_LOGINFO, "%s(ct=%d,hid=%d) => %d (connname=%s)", __func__, clientToken, + hid, cid, connNameBuf ); + return cid; +} + bool DBMgr::FindPlayer( DevIDRelay relayID, AddrInfo::ClientToken token, string& connName, HostID* hidp, unsigned short* seed ) @@ -294,11 +316,13 @@ DBMgr::SeenSeed( const char* cookie, unsigned short seed, NULL, NULL, 0 ); bool found = 1 == PQntuples( result ); if ( found ) { - *cid = atoi( PQgetvalue( result, 0, 0 ) ); - *nPlayersHP = here_less_seed( PQgetvalue( result, 0, 2 ), - atoi( PQgetvalue( result, 0, 3 ) ), - seed ); - snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) ); + int col = 0; + *cid = atoi( PQgetvalue( result, 0, col++ ) ); + snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, col++ ) ); + + const char* seeds = PQgetvalue( result, 0, col++ ); + int perDeviceSum = atoi( PQgetvalue( result, 0, col++ ) ); + *nPlayersHP = here_less_seed( seeds, perDeviceSum, seed ); } PQclear( result ); logf( XW_LOGINFO, "%s(%4X)=>%s", __func__, seed, found?"true":"false" ); @@ -333,9 +357,10 @@ DBMgr::FindOpen( const char* cookie, int lang, int nPlayersT, int nPlayersH, NULL, NULL, 0 ); CookieID cid = 0; if ( 1 == PQntuples( result ) ) { - cid = atoi( PQgetvalue( result, 0, 0 ) ); - snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) ); - *nPlayersHP = atoi( PQgetvalue( result, 0, 2 ) ); + int col = 0; + cid = atoi( PQgetvalue( result, 0, col++ ) ); + snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, col++ ) ); + *nPlayersHP = atoi( PQgetvalue( result, 0, col++ ) ); /* cid may be 0, but should use game anyway */ } PQclear( result ); @@ -699,9 +724,11 @@ DBMgr::RecordSent( const int* msgIDs, int nMsgIDs ) if ( PGRES_TUPLES_OK == PQresultStatus( result ) ) { int ntuples = PQntuples( result ); for ( int ii = 0; ii < ntuples; ++ii ) { - RecordSent( PQgetvalue( result, ii, 0 ), - atoi( PQgetvalue( result, ii, 1 ) ), - atoi( PQgetvalue( result, ii, 2 ) ) ); + int col = 0; + const char* const connName = PQgetvalue( result, ii, col++ ); + HostID hid = atoi( PQgetvalue( result, ii, col++ ) ); + int nBytes = atoi( PQgetvalue( result, ii, col++ ) ); + RecordSent( connName, hid, nBytes ); } } PQclear( result ); @@ -1014,15 +1041,16 @@ DBMgr::CountStoredMessages( DevIDRelay relayID ) return getCountWhere( MSGS_TABLE, test ); } -void +int DBMgr::StoreMessage( DevIDRelay destDevID, const uint8_t* const buf, int len ) { + int msgID = 0; clearHasNoMessages( destDevID ); size_t newLen; const char* fmt = "INSERT INTO " MSGS_TABLE " " - "(devid, %s, msglen) VALUES(%d, %s'%s', %d)"; + "(devid, %s, msglen) VALUES(%d, %s'%s', %d) RETURNING id"; StrWPF query; if ( m_useB64 ) { @@ -1038,13 +1066,20 @@ DBMgr::StoreMessage( DevIDRelay destDevID, const uint8_t* const buf, } logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); - execSql( query ); + + PGresult* result = PQexec( getThreadConn(), query.c_str() ); + if ( 1 == PQntuples( result ) ) { + msgID = atoi( PQgetvalue( result, 0, 0 ) ); + } + PQclear( result ); + return msgID; } -void +int DBMgr::StoreMessage( const char* const connName, int destHid, const uint8_t* buf, int len ) { + int msgID = 0; clearHasNoMessages( connName, destHid ); DevIDRelay devID = getDevID( connName, destHid ); @@ -1074,7 +1109,7 @@ DBMgr::StoreMessage( const char* const connName, int destHid, #ifdef HAVE_STIME " AND stime='epoch'" #endif - " );", connName, destHid, b64 ); + " )", connName, destHid, b64 ); g_free( b64 ); } else { uint8_t* bytes = PQescapeByteaConn( getThreadConn(), buf, @@ -1085,9 +1120,17 @@ DBMgr::StoreMessage( const char* const connName, int destHid, "E", bytes, len ); PQfreemem( bytes ); } + query.catf(" RETURNING id;"); logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); - execSql( query ); + PGresult* result = PQexec( getThreadConn(), query.c_str() ); + if ( 1 == PQntuples( result ) ) { + msgID = atoi( PQgetvalue( result, 0, 0 ) ); + } else { + logf( XW_LOGINFO, "Not stored; duplicate?" ); + } + PQclear( result ); + return msgID; } void diff --git a/xwords4/relay/dbmgr.h b/xwords4/relay/dbmgr.h index 690ca5c39..d23622c7e 100644 --- a/xwords4/relay/dbmgr.h +++ b/xwords4/relay/dbmgr.h @@ -75,9 +75,13 @@ class DBMgr { bool FindRelayIDFor( const char* connName, HostID hid, unsigned short seed, const DevID* host, DevIDRelay* devID ); - CookieID FindGame( const char* connName, char* cookieBuf, int bufLen, + CookieID FindGame( const char* connName, HostID hid, char* cookieBuf, int bufLen, int* langP, int* nPlayersTP, int* nPlayersHP, bool* isDead ); + CookieID FindGame( const AddrInfo::ClientToken clientToken, HostID hid, + char* connNameBuf, int connNameBufLen, + char* cookieBuf, int cookieBufLen, + int* langP, int* nPlayersTP, int* nPlayersHP ); bool FindGameFor( const char* connName, char* cookieBuf, int bufLen, unsigned short seed, HostID hid, @@ -137,10 +141,10 @@ class DBMgr { /* message storage -- different DB */ int CountStoredMessages( const char* const connName ); int CountStoredMessages( DevIDRelay relayID ); - void StoreMessage( DevIDRelay destRelayID, const uint8_t* const buf, - int len ); - void StoreMessage( const char* const connName, int destHid, - const uint8_t* const buf, int len ); + int StoreMessage( DevIDRelay destRelayID, const uint8_t* const buf, + int len ); + int StoreMessage( const char* const connName, int destHid, + const uint8_t* const buf, int len ); void GetStoredMessages( DevIDRelay relayID, vector& msgs ); void GetStoredMessages( const char* const connName, HostID hid, vector& msgs ); @@ -171,6 +175,7 @@ class DBMgr { int clientVersion, const char* const model, const char* const osVers, DevIDRelay relayID ); + PGconn* getThreadConn( void ); void clearThreadConn(); diff --git a/xwords4/relay/scripts/showinplay.sh b/xwords4/relay/scripts/showinplay.sh index f4c7eeec7..8a593ea02 100755 --- a/xwords4/relay/scripts/showinplay.sh +++ b/xwords4/relay/scripts/showinplay.sh @@ -54,13 +54,14 @@ echo "; relay pid[s]: $(pidof xwrelay)" echo "Row count:" $(psql -t xwgames -c "select count(*) FROM games $QUERY;") # Games -echo "SELECT dead as d,connname,cid,room,lang as lg,clntVers as cv ,ntotal as t,nperdevice as nPerDev,nsents as snts, seeds,devids,tokens,ack, mtimes "\ +echo "SELECT dead as d,connname,cid,room,lang as lg,clntVers as cv ,ntotal as t,nperdevice as npd,nsents as snts, seeds,devids,tokens,ack, mtimes "\ "FROM games $QUERY ORDER BY NOT dead, ctime DESC LIMIT $LIMIT;" \ | psql xwgames # Messages -echo "SELECT * "\ - "FROM msgs WHERE connname IN (SELECT connname from games $QUERY) "\ +echo "Unack'd msgs count:" $(psql -t xwgames -c "select count(*) FROM msgs where stime = 'epoch' AND connname IN (SELECT connname from games $QUERY);") +echo "SELECT id,connName,hid as h,token,ctime,stime,devid,msg64 "\ + "FROM msgs WHERE stime = 'epoch' AND connname IN (SELECT connname from games $QUERY) "\ "ORDER BY ctime DESC, connname LIMIT $LIMIT;" \ | psql xwgames diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index 666bee3f3..def2b44f1 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -550,18 +550,18 @@ assemble_packet( vector& packet, uint32_t* packetIDP, XWRelayReg cmd, } #ifdef LOG_UDP_PACKETS - gsize size = 0; - gint state = 0; - gint save = 0; - gchar out[1024]; - for ( unsigned int ii = 0; ii < iocount; ++ii ) { - size += g_base64_encode_step( (const guchar*)vec[ii].iov_base, - vec[ii].iov_len, - FALSE, &out[size], &state, &save ); - } - size += g_base64_encode_close( FALSE, &out[size], &state, &save ); - assert( size < sizeof(out) ); - out[size] = '\0'; + // gsize size = 0; + // gint state = 0; + // gint save = 0; + // gchar out[1024]; + // for ( unsigned int ii = 0; ii < iocount; ++ii ) { + // size += g_base64_encode_step( (const guchar*)vec[ii].iov_base, + // vec[ii].iov_len, + // FALSE, &out[size], &state, &save ); + // } + // size += g_base64_encode_close( FALSE, &out[size], &state, &save ); + // assert( size < sizeof(out) ); + // out[size] = '\0'; #endif } @@ -640,8 +640,10 @@ send_via_udp_impl( int sock, const struct sockaddr* dest_addr, #ifdef LOG_UDP_PACKETS gchar* b64 = g_base64_encode( (uint8_t*)dest_addr, sizeof(*dest_addr) ); + gchar* out = g_base64_encode( packet.data(), packet.size() ); logf( XW_LOGINFO, "%s()=>%d; addr='%s'; msg='%s'", __func__, nSent, b64, out ); + g_free( out ); g_free( b64 ); #else logf( XW_LOGINFO, "%s()=>%d", __func__, nSent ); @@ -761,13 +763,17 @@ send_havemsgs( const AddrInfo* addr ) class MsgClosure { public: MsgClosure( DevIDRelay dest, const vector* packet, - OnMsgAckProc proc, void* procClosure ) + int msgID, OnMsgAckProc proc, void* procClosure ) { + assert(m_msgID != 0); m_destDevID = dest; m_packet = *packet; m_proc = proc; m_procClosure = procClosure; + m_msgID = msgID; } + int getMsgID() { return m_msgID; } + int m_msgID; DevIDRelay m_destDevID; vector m_packet; OnMsgAckProc m_proc; @@ -778,9 +784,14 @@ static void onPostedMsgAcked( bool acked, uint32_t packetID, void* data ) { MsgClosure* mc = (MsgClosure*)data; - if ( !acked ) { - DBMgr::Get()->StoreMessage( mc->m_destDevID, mc->m_packet.data(), - mc->m_packet.size() ); + int msgID = mc->getMsgID(); + if ( acked ) { + DBMgr::Get()->RemoveStoredMessages( &msgID, 1 ); + } else { + assert( msgID != 0 ); + // So we only store after ack fails? Change that!!! + // DBMgr::Get()->StoreMessage( mc->m_destDevID, mc->m_packet.data(), + // mc->m_packet.size() ); } if ( NULL != mc->m_proc ) { (*mc->m_proc)( acked, mc->m_destDevID, packetID, mc->m_procClosure ); @@ -793,6 +804,8 @@ static bool post_or_store( DevIDRelay destDevID, vector& packet, uint32_t packetID, OnMsgAckProc proc, void* procClosure ) { + int msgID = DBMgr::Get()->StoreMessage( destDevID, packet.data(), packet.size() ); + const AddrInfo::AddrUnion* addru = DevMgr::Get()->get( destDevID ); bool canSendNow = !!addru; @@ -804,16 +817,13 @@ post_or_store( DevIDRelay destDevID, vector& packet, uint32_t packetID, if ( get_addr_info_if( &addr, &sock, &dest_addr ) ) { sent = 0 < send_packet_via_udp_impl( packet, sock, dest_addr ); - if ( sent ) { - MsgClosure* mc = new MsgClosure( destDevID, &packet, + if ( sent && msgID != 0 ) { + MsgClosure* mc = new MsgClosure( destDevID, &packet, msgID, proc, procClosure ); UDPAckTrack::setOnAck( onPostedMsgAcked, packetID, (void*)mc ); } } } - if ( !sent ) { - DBMgr::Get()->StoreMessage( destDevID, packet.data(), packet.size() ); - } return sent; } @@ -988,13 +998,13 @@ processReconnect( const uint8_t* bufp, int bufLen, const AddrInfo* addr ) } /* processReconnect */ static bool -processAck( const uint8_t* bufp, int bufLen, const AddrInfo* addr ) +processAck( const uint8_t* bufp, int bufLen, AddrInfo::ClientToken clientToken ) { bool success = false; const uint8_t* end = bufp + bufLen; HostID srcID; if ( getNetByte( &bufp, end, &srcID ) ) { - SafeCref scr( addr ); + SafeCref scr( clientToken, srcID ); success = scr.HandleAck( srcID ); } return success; @@ -1084,7 +1094,8 @@ forwardMessage( const uint8_t* buf, int buflen, const AddrInfo* addr ) } /* forwardMessage */ static bool -processMessage( const uint8_t* buf, int bufLen, const AddrInfo* addr ) +processMessage( const uint8_t* buf, int bufLen, const AddrInfo* addr, + AddrInfo::ClientToken clientToken ) { bool success = false; /* default is failure */ XWRELAY_Cmd cmd = *buf; @@ -1099,7 +1110,11 @@ processMessage( const uint8_t* buf, int bufLen, const AddrInfo* addr ) success = processReconnect( buf+1, bufLen-1, addr ); break; case XWRELAY_ACK: - success = processAck( buf+1, bufLen-1, addr ); + if ( clientToken != 0 ) { + success = processAck( buf+1, bufLen-1, clientToken ); + } else { + logf( XW_LOGERROR, "%s(): null client token", __func__ ); + } break; case XWRELAY_GAME_DISCONNECT: success = processDisconnect( buf+1, bufLen-1, addr ); @@ -1334,6 +1349,9 @@ handleMsgsMsg( const AddrInfo* addr, bool sendFull, logf( XW_LOGVERBOSE0, "%s: wrote %d bytes", __func__, nwritten ); if ( sendFull && nwritten >= 0 && (size_t)nwritten == out.size() ) { dbmgr->RecordSent( &msgIDs[0], msgIDs.size() ); + // This is wrong: should be removed when ACK returns and not + // before. But for some reason if I make that change apps wind up + // stalling. dbmgr->RemoveStoredMessages( msgIDs ); } } @@ -1438,7 +1456,7 @@ handleProxyMsgs( int sock, const AddrInfo* addr, const uint8_t* bufp, } unsigned short nMsgs; if ( getNetShort( &bufp, end, &nMsgs ) ) { - SafeCref scr( connName ); + SafeCref scr( connName, hid ); while ( scr.IsValid() && nMsgs-- > 0 ) { unsigned short len; if ( getNetShort( &bufp, end, &len ) ) { @@ -1460,7 +1478,7 @@ handleProxyMsgs( int sock, const AddrInfo* addr, const uint8_t* bufp, static void game_thread_proc( UdpThreadClosure* utc ) { - if ( !processMessage( utc->buf(), utc->len(), utc->addr() ) ) { + if ( !processMessage( utc->buf(), utc->len(), utc->addr(), 0 ) ) { XWThreadPool::GetTPool()->CloseSocket( utc->addr() ); } } @@ -1528,7 +1546,7 @@ proxy_thread_proc( UdpThreadClosure* utc ) sizeof( connName ), &hid ) ) { break; } - SafeCref scr( connName ); + SafeCref scr( connName, hid ); scr.DeviceGone( hid, seed ); } } @@ -1748,7 +1766,7 @@ handle_udp_packet( UdpThreadClosure* utc ) clientToken = ntohl( clientToken ); if ( AddrInfo::NULL_TOKEN != clientToken ) { AddrInfo addr( g_udpsock, clientToken, utc->saddr() ); - (void)processMessage( ptr, end - ptr, &addr ); + (void)processMessage( ptr, end - ptr, &addr, clientToken ); } else { logf( XW_LOGERROR, "%s: dropping packet with token of 0", __func__ ); @@ -1766,7 +1784,7 @@ handle_udp_packet( UdpThreadClosure* utc ) logf( XW_LOGERROR, "parse failed!!!" ); break; } - SafeCref scr( connName ); + SafeCref scr( connName, hid ); if ( scr.IsValid() ) { AddrInfo addr( g_udpsock, clientToken, utc->saddr() ); handlePutMessage( scr, hid, &addr, end - ptr, &ptr, end ); @@ -1833,7 +1851,7 @@ handle_udp_packet( UdpThreadClosure* utc ) string connName; if ( DBMgr::Get()->FindPlayer( devID.asRelayID(), clientToken, connName, &hid, &seed ) ) { - SafeCref scr( connName.c_str() ); + SafeCref scr( connName.c_str(), hid ); scr.DeviceGone( hid, seed ); } } @@ -1980,7 +1998,7 @@ maint_str_loop( int udpsock, const char* str ) } // maint_str_loop static uint32_t -getIPAddr( void ) +getUDPIPAddr( void ) { uint32_t result = INADDR_ANY; char iface[16] = {0}; @@ -2215,7 +2233,7 @@ main( int argc, char** argv ) struct sockaddr_in saddr; g_udpsock = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); saddr.sin_family = PF_INET; - saddr.sin_addr.s_addr = getIPAddr(); + saddr.sin_addr.s_addr = getUDPIPAddr(); saddr.sin_port = htons(udpport); int err = bind( g_udpsock, (struct sockaddr*)&saddr, sizeof(saddr) ); if ( 0 == err ) { From b77ea0aaadd6e3bac006dd9ad07b989dc2179881 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 12 Nov 2017 20:29:15 -0800 Subject: [PATCH 57/66] copied from relay_via_http branch (was at: ./xwords4/android/scripts/relay.py) This is the web API clients can use to talk to the relay. --- xwords4/relay/scripts/relay.py | 250 +++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100755 xwords4/relay/scripts/relay.py diff --git a/xwords4/relay/scripts/relay.py b/xwords4/relay/scripts/relay.py new file mode 100755 index 000000000..7ca6c2fa6 --- /dev/null +++ b/xwords4/relay/scripts/relay.py @@ -0,0 +1,250 @@ +#!/usr/bin/python + +import base64, json, mod_python, socket, struct, sys +import psycopg2, random + +PROTOCOL_VERSION = 0 +PRX_DEVICE_GONE = 3 +PRX_GET_MSGS = 4 + +# try: +# from mod_python import apache +# apacheAvailable = True +# except ImportError: +# apacheAvailable = False + +# Joining a game. Basic idea is you have stuff to match on (room, +# number in game, language) and when somebody wants to join you add to +# an existing matching game if there's space otherwise create a new +# one. Problems are the unreliablity of transport: if you give a space +# and the device doesn't get the message you can't hold it forever. So +# device provides a seed that holds the space. If it asks again for a +# space with the same seed it gets the same space. If it never asks +# again (app deleted, say), the space needs eventually to be given to +# somebody else. I think that's done by adding a timestamp array and +# treating the space as available if TIME has expired. Need to think +# about this: what if app fails to ACK for TIME, then returns with +# seed to find it given away. Let's do a 30 minute reservation for +# now? [Note: much of this is PENDING] + +def join(req, devID, room, seed, hid = 0, lang = 1, nInGame = 2, nHere = 1, inviteID = None): + assert hid <= 4 + seed = int(seed) + assert seed != 0 + nInGame = int(nInGame) + nHere = int(nHere) + assert nHere <= nInGame + assert nInGame <= 4 + + devID = int(devID, 16) + + connname = None + logs = [] # for debugging + # logs.append('vers: ' + platform.python_version()) + + con = psycopg2.connect(database='xwgames') + cur = con.cursor() + # cur.execute('LOCK TABLE games IN ACCESS EXCLUSIVE MODE') + + # First see if there's a game with a space for me. Must match on + # room, lang and size. Must have room OR must have already given a + # spot for a seed equal to mine, in which case I get it + # back. Assumption is I didn't ack in time. + + query = "SELECT connname, seeds, nperdevice FROM games " + query += "WHERE lang = %s AND nTotal = %s AND room = %s " + query += "AND (njoined + %s <= ntotal OR %s = ANY(seeds)) " + query += "LIMIT 1" + cur.execute( query, (lang, nInGame, room, nHere, seed)) + for row in cur: + (connname, seeds, nperdevice) = row + print('found', connname, seeds, nperdevice) + break # should be only one! + + # If we've found a match, we either need to UPDATE or, if the + # seeds match, remind the caller of where he belongs. If a hid's + # been specified, we honor it by updating if the slot's available; + # otherwise a new game has to be created. + if connname: + if seed in seeds and nHere == nperdevice[seeds.index(seed)]: + hid = seeds.index(seed) + 1 + print('resusing seed case; outta here!') + else: + if hid == 0: + # Any gaps? Assign it + if None in seeds: + hid = seeds.index(None) + 1 + else: + hid = len(seeds) + 1 + print('set hid to', hid, 'based on ', seeds) + else: + print('hid already', hid) + query = "UPDATE games SET njoined = njoined + %s, " + query += "devids[%d] = %%s, " % hid + query += "seeds[%d] = %%s, " % hid + query += "jtimes[%d] = 'now', " % hid + query += "nperdevice[%d] = %%s " % hid + query += "WHERE connname = %s " + print(query) + params = (nHere, devID, seed, nHere, connname) + cur.execute(query, params) + + # If nothing was found, add a new game and add me. Honor my hid + # preference if specified + if not connname: + # This requires python3, which likely requires mod_wsgi + # ts = datetime.datetime.utcnow().timestamp() + # connname = '%s:%d:1' % (xwconfig.k_HOSTNAME, int(ts * 1000)) + connname = '%s:%d:1' % (xwconfig.k_HOSTNAME, random.randint(0, 10000000000)) + useHid = hid == 0 and 1 or hid + print('not found case; inserting using hid:', useHid) + query = "INSERT INTO games (connname, room, lang, ntotal, njoined, " + \ + "devids[%d], seeds[%d], jtimes[%d], nperdevice[%d]) " % (4 * (useHid,)) + query += "VALUES (%s, %s, %s, %s, %s, %s, %s, 'now', %s) " + query += "RETURNING connname, array_length(seeds,1); " + cur.execute(query, (connname, room, lang, nInGame, nHere, devID, seed, nHere)) + for row in cur: + connname, gothid = row + break + if hid == 0: hid = gothid + + con.commit() + con.close() + + result = {'connname': connname, 'hid' : hid, 'log' : ':'.join(logs)} + + return json.dumps(result) + +def kill(req, params): + print(params) + params = json.loads(params) + count = len(params) + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('127.0.0.1', 10998)) + + header = struct.Struct('!BBh') + strLens = 0 + for ii in range(count): + strLens += len(params[ii]['relayID']) + 1 + size = header.size + (2*count) + strLens + sock.send(struct.Struct('!h').pack(size)) + sock.send(header.pack(PROTOCOL_VERSION, PRX_DEVICE_GONE, count)) + + for ii in range(count): + elem = params[ii] + asBytes = bytes(elem['relayID']) + sock.send(struct.Struct('!H%dsc' % (len(asBytes))).pack(elem['seed'], asBytes, '\n')) + sock.close() + + result = {'err': 0} + return json.dumps(result) + +# winds up in handle_udp_packet() in xwrelay.cpp +def post(req, params): + err = 'none' + params = json.loads(params) + data = params['data'] + timeoutSecs = 'timeoutSecs' in params and params['timeoutSecs'] or 1.0 + binData = [base64.b64decode(datum) for datum in data] + + udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + udpSock.settimeout(float(timeoutSecs)) # seconds + addr = ("127.0.0.1", 10997) + for binDatum in binData: + udpSock.sendto(binDatum, addr) + + responses = [] + while True: + try: + data, server = udpSock.recvfrom(1024) + responses.append(base64.b64encode(data)) + except socket.timeout: + #If data is not received back from server, print it has timed out + err = 'timeout' + break + + result = {'err' : err, 'data' : responses} + return json.dumps(result) + +def query(req, params): + print('params', params) + params = json.loads(params) + ids = params['ids'] + timeoutSecs = 'timeoutSecs' in params and float(params['timeoutSecs']) or 2.0 + + idsLen = 0 + for id in ids: idsLen += len(id) + + tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + tcpSock.settimeout(timeoutSecs) + tcpSock.connect(('127.0.0.1', 10998)) + + lenShort = 2 + idsLen + len(ids) + 2 + print(lenShort, PROTOCOL_VERSION, PRX_GET_MSGS, len(ids)) + header = struct.Struct('!hBBh') + assert header.size == 6 + tcpSock.send(header.pack(lenShort, PROTOCOL_VERSION, PRX_GET_MSGS, len(ids))) + + for id in ids: tcpSock.send(id + '\n') + + msgsLists = {} + try: + shortUnpacker = struct.Struct('!H') + resLen, = shortUnpacker.unpack(tcpSock.recv(shortUnpacker.size)) # not getting all bytes + nameCount, = shortUnpacker.unpack(tcpSock.recv(shortUnpacker.size)) + resLen -= shortUnpacker.size + print('resLen:', resLen, 'nameCount:', nameCount) + if nameCount == len(ids) and resLen > 0: + print('nameCount', nameCount) + for ii in range(nameCount): + perGame = [] + countsThisGame, = shortUnpacker.unpack(tcpSock.recv(shortUnpacker.size)) # problem + print('countsThisGame:', countsThisGame) + for jj in range(countsThisGame): + msgLen, = shortUnpacker.unpack(tcpSock.recv(shortUnpacker.size)) + print('msgLen:', msgLen) + msgs = [] + if msgLen > 0: + msg = tcpSock.recv(msgLen) + print('msg len:', len(msg)) + msg = base64.b64encode(msg) + msgs.append(msg) + perGame.append(msgs) + msgsLists[ids[ii]] = perGame + except: + None + + return json.dumps(msgsLists) + +def main(): + result = None + if len(sys.argv) > 1: + cmd = sys.argv[1] + args = sys.argv[2:] + if cmd == 'query' and len(args) > 0: + result = query(None, json.dumps({'ids':args})) + elif cmd == 'post': + # Params = { 'data' : 'V2VkIE9jdCAxOCAwNjowNDo0OCBQRFQgMjAxNwo=' } + # params = json.dumps(params) + # print(post(None, params)) + pass + elif cmd == 'join': + if len(args) == 6: + result = join(None, 1, args[0], int(args[1]), int(args[2]), int(args[3]), int(args[4]), int(args[5])) + elif cmd == 'kill': + result = kill( None, json.dumps([{'relayID': args[0], 'seed':int(args[1])}]) ) + + if result: + print '->', result + else: + print 'USAGE: query [connname/hid]*' + print ' join ' + print ' query [connname/hid]*' + # print ' post ' + print ' kill ' + print ' join ' + +############################################################################## +if __name__ == '__main__': + main() From c2eff7d3f2fdfb067756cc1142259608c42428d7 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 12 Nov 2017 22:31:48 -0800 Subject: [PATCH 58/66] add "archive" option to dialog on opening done game Create the group if needed. Name's not user-editable at this point. Should be a preference, and that preference should be changed if user renames it. --- .../eehouse/android/xw4/BoardDelegate.java | 25 +++++++++++++++ .../java/org/eehouse/android/xw4/DBUtils.java | 32 +++++++++++++++++-- .../android/xw4/GamesListDelegate.java | 5 +-- .../app/src/main/res/values/strings.xml | 2 ++ 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java index 35052ec90..71f60acf4 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java @@ -204,6 +204,21 @@ public class BoardDelegate extends DelegateBase } }; ab.setNegativeButton( R.string.button_rematch, lstnr ); + + // If we're not already in the "archive" group, offer to move + final String archiveName = LocUtils + .getString( m_activity, R.string.group_name_archive ); + final long archiveGroup = DBUtils.getGroup( m_activity, archiveName ); + long curGroup = DBUtils.getGroupForGame( m_activity, m_rowid ); + if ( curGroup != archiveGroup ) { + lstnr = new OnClickListener() { + public void onClick( DialogInterface dlg, + int whichButton ) { + archiveAndClose( archiveName, archiveGroup ); + } + }; + ab.setNeutralButton( R.string.button_archive, lstnr ); + } } else if ( DlgID.DLG_CONNSTAT == dlgID && BuildConfig.DEBUG && null != m_connTypes && (m_connTypes.contains( CommsConnType.COMMS_CONN_RELAY ) @@ -2575,6 +2590,16 @@ public class BoardDelegate extends DelegateBase return wordsArray; } + private void archiveAndClose( String archiveName, long groupID ) + { + if ( DBUtils.GROUPID_UNSPEC == groupID ) { + groupID = DBUtils.addGroup( m_activity, archiveName ); + } + DBUtils.moveGame( m_activity, m_rowid, groupID ); + waitCloseGame( false ); + finish(); + } + // For now, supported if standalone or either BT or SMS used for transport private boolean rematchSupported( boolean showMulti ) { diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java index ec7782026..9f287b36b 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java @@ -81,7 +81,9 @@ public class DBUtils { private static long s_cachedRowID = ROWID_NOTFOUND; private static byte[] s_cachedBytes = null; - public static enum GameChangeType { GAME_CHANGED, GAME_CREATED, GAME_DELETED }; + public static enum GameChangeType { GAME_CHANGED, GAME_CREATED, + GAME_DELETED, GAME_MOVED, + }; public static interface DBChangeListener { public void gameSaved( long rowid, GameChangeType change ); @@ -1701,6 +1703,29 @@ public class DBUtils { return result; } + public static long getGroup( Context context, String name ) + { + long result = GROUPID_UNSPEC; + String[] columns = { ROW_ID }; + String selection = DBHelper.GROUPNAME + " = ?"; + String[] selArgs = { name }; + + initDB( context ); + synchronized( s_dbHelper ) { + Cursor cursor = s_db.query( DBHelper.TABLE_NAME_GROUPS, columns, + selection, selArgs, + null, // groupBy + null, // having + null // orderby + ); + if ( cursor.moveToNext() ) { + result = cursor.getLong( cursor.getColumnIndex( ROW_ID ) ); + } + cursor.close(); + } + return result; + } + public static long addGroup( Context context, String name ) { long rowid = GROUPID_UNSPEC; @@ -1759,13 +1784,14 @@ public class DBUtils { } // Change group id of a game - public static void moveGame( Context context, long gameid, long groupID ) + public static void moveGame( Context context, long rowid, long groupID ) { Assert.assertTrue( GROUPID_UNSPEC != groupID ); ContentValues values = new ContentValues(); values.put( DBHelper.GROUPID, groupID ); - updateRow( context, DBHelper.TABLE_NAME_SUM, gameid, values ); + updateRow( context, DBHelper.TABLE_NAME_SUM, rowid, values ); invalGroupsCache(); + notifyListeners( rowid, GameChangeType.GAME_MOVED ); } private static String getChatHistoryStr( Context context, long rowid ) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java index 90a1dc2b1..06300270b 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java @@ -1060,8 +1060,6 @@ public class GamesListDelegate extends ListDelegateBase invalidateOptionsMenuIf(); setTitle(); } - - mkListAdapter(); } public void invalidateOptionsMenuIf() @@ -1133,6 +1131,9 @@ public class GamesListDelegate extends ListDelegateBase mkListAdapter(); setSelGame( rowid ); break; + case GAME_MOVED: + mkListAdapter(); + break; default: Assert.fail(); break; diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index 4ba195973..c8342bb3a 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -2158,6 +2158,8 @@ game with the same players and parameters as the one that just ended. --> Rematch + Archive\u200C + Archive Reconnect From 607567cd35f85c93129e995f0524b355754d20da Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 12 Nov 2017 22:50:45 -0800 Subject: [PATCH 59/66] don't allow duplicate group names If group already exists, warn rather than create another with the same name. --- .../eehouse/android/xw4/GamesListDelegate.java | 15 ++++++++++++--- .../android/app/src/main/res/values/strings.xml | 2 ++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java index 06300270b..41ffeeaea 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java @@ -755,9 +755,18 @@ public class GamesListDelegate extends ListDelegateBase lstnr = new OnClickListener() { public void onClick( DialogInterface dlg, int item ) { String name = namer.getName(); - DBUtils.addGroup( m_activity, name ); - mkListAdapter(); - showNewGroupIf(); + long hasName = DBUtils.getGroup( m_activity, name ); + if ( DBUtils.GROUPID_UNSPEC == hasName ) { + DBUtils.addGroup( m_activity, name ); + mkListAdapter(); + showNewGroupIf(); + } else { + String msg = LocUtils + .getString( m_activity, + R.string.duplicate_group_name_fmt, + name ); + makeOkOnlyBuilder( msg ).show(); + } } }; lstnr2 = new OnClickListener() { diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index c8342bb3a..4a5a7eb29 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -2161,6 +2161,8 @@ Archive\u200C Archive + The group \"%1$s\" already exists. + Reconnect Square rack tiles From 9d02237f19b7e0486a16961bc0f1ffc2b98bb406 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 16 Nov 2017 07:13:10 -0800 Subject: [PATCH 60/66] add assertion to catch too many players --- xwords4/linux/linuxmain.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index e3ff095e6..124321ea0 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -2284,6 +2284,7 @@ main( int argc, char** argv ) break; case CMD_PLAYERNAME: index = mainParams.pgi.nPlayers++; + XP_ASSERT( index < MAX_NUM_PLAYERS ); ++mainParams.nLocalPlayers; mainParams.pgi.players[index].robotIQ = 0; /* means human */ mainParams.pgi.players[index].isLocal = XP_TRUE; @@ -2292,6 +2293,7 @@ main( int argc, char** argv ) break; case CMD_REMOTEPLAYER: index = mainParams.pgi.nPlayers++; + XP_ASSERT( index < MAX_NUM_PLAYERS ); mainParams.pgi.players[index].isLocal = XP_FALSE; ++mainParams.info.serverInfo.nRemotePlayers; break; @@ -2302,6 +2304,7 @@ main( int argc, char** argv ) case CMD_ROBOTNAME: ++robotCount; index = mainParams.pgi.nPlayers++; + XP_ASSERT( index < MAX_NUM_PLAYERS ); ++mainParams.nLocalPlayers; mainParams.pgi.players[index].robotIQ = 1; /* real smart by default */ mainParams.pgi.players[index].isLocal = XP_TRUE; From f45dcb36b1b09a260bc7a612b706a7de9d62cce9 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 15 Nov 2017 08:34:04 -0800 Subject: [PATCH 61/66] rewrite of shell script in python3 translate the most-used features of my too-big-for-bash script into python3, which is clearly much better suited. Tried to keep the structure and variable names intact so that diff has a chance of showing something, but when a class replaces a bunch of arrays that were being kept in sync there's only so much you can to. Currently doesn't support stuff like app upgrades and switching from tcp to udp, but those should be relatively easy to bring over from the .sh when/if I need them. --- xwords4/linux/scripts/discon_ok2.py | 986 ++++++++++++++++++++++++++++ 1 file changed, 986 insertions(+) create mode 100755 xwords4/linux/scripts/discon_ok2.py diff --git a/xwords4/linux/scripts/discon_ok2.py b/xwords4/linux/scripts/discon_ok2.py new file mode 100755 index 000000000..82bfa6c33 --- /dev/null +++ b/xwords4/linux/scripts/discon_ok2.py @@ -0,0 +1,986 @@ +#!/usr/bin/env python3 + +import re, os, sys, getopt, shutil, threading, requests, json, glob +import argparse, datetime, random, subprocess, time + +# LOGDIR=./$(basename $0)_logs +# APP_NEW="" +# DO_CLEAN="" +# APP_NEW_PARAMS="" +# NGAMES = 1 +g_UDP_PCT_START = 100 +# UDP_PCT_INCR=10 +# UPGRADE_ODDS="" +# NROOMS="" +# HOST="" +# PORT="" +# TIMEOUT="" +# SAVE_GOOD="" +# MINDEVS="" +# MAXDEVS="" +# ONEPER="" +# RESIGN_PCT=0 +g_DROP_N=0 +# MINRUN=2 # seconds +# ONE_PER_ROOM="" # don't run more than one device at a time per room +# USE_GTK="" +# UNDO_PCT=0 +# ALL_VIA_RQ=${ALL_VIA_RQ:-FALSE} +# SEED="" +# BOARD_SIZES_OLD=(15) +# BOARD_SIZES_NEW=(15) +g_NAMES = [None, 'Brynn', 'Ariela', 'Kati', 'Eric'] +# SEND_CHAT='' +# CORE_COUNT=$(ls core.* 2>/dev/null | wc -l) +# DUP_PACKETS='' +# HTTP_PCT=0 + +# declare -A PIDS +# declare -A APPS +# declare -A NEW_ARGS +# declare -a ARGS +# declare -A ARGS_DEVID +# declare -A ROOMS +# declare -A FILES +# declare -A LOGS +# declare -A MINEND +# ROOM_PIDS = {} +# declare -a APPS_OLD=() +# declare -a DICTS= # wants to be =() too? +# 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 WHERE room LIKE 'ROOM_%';" | psql -q -t xwgames +# echo "DELETE FROM msgs WHERE NOT devid in (SELECT unnest(devids) from games);" | psql -q -t xwgames +# } + +# function connName() { +# LOG=$1 +# grep -a 'got_connect_cmd: connName' $LOG | \ +# tail -n 1 | \ +# sed 's,^.*connName: \"\(.*\)\" (reconnect=.)$,\1,' +# } + +# function check_room() { +# ROOM=$1 +# if [ -z ${CHECKED_ROOMS[$ROOM]:-""} ]; then +# NUM=$(echo "SELECT COUNT(*) FROM games "\ +# "WHERE NOT dead "\ +# "AND ntotal!=sum_array(nperdevice) "\ +# "AND ntotal != -sum_array(nperdevice) "\ +# "AND room='$ROOM'" | +# psql -q -t xwgames) +# NUM=$((NUM+0)) +# if [ "$NUM" -gt 0 ]; then +# echo "$ROOM in the DB has unconsummated games. Remove them." +# exit 1 +# else +# CHECKED_ROOMS[$ROOM]=1 +# fi +# fi +# } + +# print_cmdline() { +# local COUNTER=$1 +# local LOG=${LOGS[$COUNTER]} +# echo -n "New cmdline: " >> $LOG +# echo "${APPS[$COUNTER]} ${NEW_ARGS[$COUNTER]} ${ARGS[$COUNTER]}" >> $LOG +# } + +def pick_ndevs(args): + RNUM = random.randint(0, 99) + if RNUM > 90 and args.MAXDEVS >= 4: + NDEVS = 4 + elif RNUM > 75 and args.MAXDEVS >= 3: + NDEVS = 3 + else: + NDEVS = 2 + if NDEVS < args.MINDEVS: + NDEVS = args.MINDEVS + return NDEVS + +# # Given a device count, figure out how many local players per device. +# # "1 1" would be a two-device game with 1 each. "1 2 1" a +# # three-device game with four players total +def figure_locals(args, NDEVS): + NPLAYERS = pick_ndevs(args) + if NPLAYERS < NDEVS: NPLAYERS = NDEVS + + EXTRAS = 0 + if not args.ONEPER: + EXTRAS = NPLAYERS - NDEVS + + LOCALS = [] + for IGNORE in range(NDEVS): + COUNT = 1 + if EXTRAS > 0: + EXTRA = random.randint(0, EXTRAS) + if EXTRA > 0: + COUNT += EXTRA + EXTRAS -= EXTRA + LOCALS.append(COUNT) + assert 0 < sum(LOCALS) <= 4 + return LOCALS + +def player_params(args, NLOCALS, NPLAYERS, NAME_INDX): + assert 0 < NPLAYERS <= 4 + NREMOTES = NPLAYERS - NLOCALS + PARAMS = [] + while NLOCALS > 0 or NREMOTES > 0: + if 0 == random.randint(0, 2) and 0 < NLOCALS: + PARAMS += ['--robot', g_NAMES[NAME_INDX], '--robot-iq', str(random.randint(1,100))] + NLOCALS -= 1 + NAME_INDX += 1 + elif 0 < NREMOTES: + PARAMS += ['--remote-player'] + NREMOTES -= 1 + return PARAMS + +def logReaderStub(dev): dev.logReaderMain() + +class Device(): + sConnnameMap = {} + sHasLDevIDMap = {} + sConnNamePat = re.compile('.*got_connect_cmd: connName: "([^"]+)".*$') + sGameOverPat = re.compile('.*\[unused tiles\].*') + sTilesLeftPat = re.compile('.*pool_removeTiles: (\d+) tiles left in pool') + sRelayIDPat = re.compile('.*UPDATE games.*seed=(\d+),.*relayid=\'([^\']+)\'.*') + + def __init__(self, args, indx, app, params, room, db, log, nInGame): + self.indx = indx + self.args = args + self.pid = 0 + self.gameOver = False + self.app = app + self.params = params + self.room = room + self.db = db + self.logPath = log + self.nInGame = nInGame + # runtime stuff; init now + self.proc = None + self.connname = None + self.devID = '' + self.launchCount = 0 + self.allDone = False # when true, can be killed + self.nTilesLeft = -1 # negative means don't know + self.relayID = None + self.relaySeed = 0 + + with open(self.logPath, "w") as log: + log.write('New cmdline: ' + self.app + ' ' + (' '.join([str(p) for p in self.params]))) + log.write(os.linesep) + + def logReaderMain(self): + assert self and self.proc + stdout, stderr = self.proc.communicate() + # print('logReaderMain called; opening:', self.logPath, 'flag:', flag) + nLines = 0 + with open(self.logPath, 'a') as log: + for line in stderr.splitlines(): + nLines += 1 + log.write(line + os.linesep) + + # check for connname + if not self.connname: + match = Device.sConnNamePat.match(line) + if match: + self.connname = match.group(1) + if not self.connname in Device.sConnnameMap: + Device.sConnnameMap[self.connname] = set() + Device.sConnnameMap[self.connname].add(self) + + # check for game over + if not self.gameOver: + match = Device.sGameOverPat.match(line) + if match: self.gameOver = True + + # Check every line for tiles left + match = Device.sTilesLeftPat.match(line) + if match: self.nTilesLeft = int(match.group(1)) + + if not self.relayID: + match = Device.sRelayIDPat.match(line) + if match: + self.relaySeed = int(match.group(1)) + self.relayID = match.group(2) + + # print('logReaderMain done, wrote lines:', nLines, 'to', self.logPath); + + def launch(self): + args = [self.app] + [str(p) for p in self.params] + if self.devID: args.extend( ' '.split(self.devID)) + self.launchCount += 1 + # self.logStream = open(self.logPath, flag) + self.proc = subprocess.Popen(args, stdout = subprocess.DEVNULL, + stderr = subprocess.PIPE, universal_newlines = True) + self.pid = self.proc.pid + self.minEnd = datetime.datetime.now() + datetime.timedelta(seconds = self.args.MINRUN) + + # Now start a thread to read stdio + self.reader = threading.Thread(target = logReaderStub, args=(self,)) + self.reader.isDaemon = True + self.reader.start() + + def running(self): + return self.proc and not self.proc.poll() + + def minTimeExpired(self): + assert self.proc + return self.minEnd < datetime.datetime.now() + + def kill(self): + if self.proc.poll() is None: + self.proc.terminate() + self.proc.wait() + assert self.proc.poll() is not None + + self.reader.join() + self.reader = None + else: + print('NOT killing') + self.proc = None + self.check_game() + + def moveFiles(self): + assert not self.running() + shutil.move(self.logPath, self.args.LOGDIR + '/done') + shutil.move(self.db, self.args.LOGDIR + '/done') + + def send_dead(self): + JSON = json.dumps([{'relayID': self.relayID, 'seed': self.relaySeed}]) + url = 'http://%s/xw4/relay.py/kill' % (self.args.HOST) + req = requests.get(url, params = {'params' : JSON}) + + def getTilesCount(self): + result = None + if self.nTilesLeft != -1: + result = '%.2d:%.2d' % (self.indx, self.nTilesLeft) + return result + + def update_ldevid(self): + if not self.app in Device.sHasLDevIDMap: + hasLDevID = False + proc = subprocess.Popen([self.app, '--help'], stderr=subprocess.PIPE) + # output, err, = proc.communicate() + for line in proc.stderr.readlines(): + if b'--ldevid' in line: + hasLDevID = True + break + print('found --ldevid:', hasLDevID); + Device.sHasLDevIDMap[self.app] = hasLDevID + + if Device.sHasLDevIDMap[self.app]: + RNUM = random.randint(0, 99) + if not self.devID: + if RNUM < 30: + self.devID = '--ldevid LINUX_TEST_%.5d_' % (self.indx) + elif RNUM < 10: + self.devID += 'x' + + def check_game(self): + if self.gameOver and not self.allDone: + allDone = False + if len(Device.sConnnameMap[self.connname]) == self.nInGame: + allDone = True + for dev in Device.sConnnameMap[self.connname]: + if dev == self: continue + if not dev.gameOver: + allDone = False + break + + if allDone: + for dev in Device.sConnnameMap[self.connname]: + dev.allDone = True + + # print('Closing', self.connname, datetime.datetime.now()) + # for dev in Device.sConnnameMap[self.connname]: + # dev.kill() +# # kill_from_logs $OTHERS $KEY +# for ID in $OTHERS $KEY; do +# echo -n "${ID}:${LOGS[$ID]}, " +# kill_from_log ${LOGS[$ID]} || /bin/true +# send_dead $ID +# close_device $ID $DONEDIR "game over" +# done +# echo "" +# # XWRELAY_ERROR_DELETED may be old +# elif grep -aq 'relay_error_curses(XWRELAY_ERROR_DELETED)' $LOG; then +# echo "deleting $LOG $(connName $LOG) b/c another resigned" +# kill_from_log $LOG || /bin/true +# close_device $KEY $DEADDIR "other resigned" +# elif grep -aq 'relay_error_curses(XWRELAY_ERROR_DEADGAME)' $LOG; then +# echo "deleting $LOG $(connName $LOG) b/c another resigned" +# kill_from_log $LOG || /bin/true +# close_device $KEY $DEADDIR "other resigned" +# else +# maybe_resign $KEY +# fi +# } + + +def build_cmds(args): + devs = [] + COUNTER = 0 + PLAT_PARMS = [] + if not args.USE_GTK: + PLAT_PARMS += ['--curses', '--close-stdin'] + + for GAME in range(1, args.NGAMES + 1): + ROOM = 'ROOM_%.3d' % (GAME % args.NROOMS) + # check_room $ROOM + NDEVS = pick_ndevs(args) + LOCALS = figure_locals(args, NDEVS) # as array + NPLAYERS = sum(LOCALS) + assert(len(LOCALS) == NDEVS) + DICT = args.DICTS[GAME % len(args.DICTS)] + # make one in three games public + PUBLIC = [] + if random.randint(0, 3) == 0: PUBLIC = ['--make-public', '--join-public'] + DEV = 0 + for NLOCALS in LOCALS: + DEV += 1 + FILE="%s/GAME_%d_%d.sql3" % (args.LOGDIR, GAME, DEV) + LOG='%s/%d_%d_LOG.txt' % (args.LOGDIR, GAME, DEV) + # os.system("rm -f $LOG") # clear the log + + # APPS[$COUNTER]="$APP_NEW" + # NEW_ARGS[$COUNTER]="$APP_NEW_PARAMS" + BOARD_SIZE = ['--board-size', '15'] + # if [ 0 -lt ${#APPS_OLD[@]} ]; then + # # 50% chance of starting out with old app + # NAPPS=$((1+${#APPS_OLD[*]})) + # if [ 0 -lt $((RANDOM%$NAPPS)) ]; then + # APPS[$COUNTER]=${APPS_OLD[$((RANDOM%${#APPS_OLD[*]}))]} + # BOARD_SIZE="--board-size ${BOARD_SIZES_OLD[$((RANDOM%${#BOARD_SIZES_OLD[*]}))]}" + # NEW_ARGS[$COUNTER]="" + # fi + # fi + + PARAMS = player_params(args, NLOCALS, NPLAYERS, DEV) + PARAMS += PLAT_PARMS + PARAMS += BOARD_SIZE + ['--room', ROOM, '--trade-pct', args.TRADE_PCT, '--sort-tiles'] + if args.UNDO_PCT > 0: + PARAMS += ['--undo-pct', args.UNDO_PCT] + PARAMS += [ '--game-dict', DICT, '--relay-port', args.PORT, '--host', args.HOST] + PARAMS += ['--slow-robot', '1:3', '--skip-confirm'] + PARAMS += ['--db', FILE] + if random.randint(0,100) % 100 < g_UDP_PCT_START: + PARAMS += ['--use-udp'] + + PARAMS += ['--drop-nth-packet', g_DROP_N] + if random.randint(0, 100) < args.HTTP_PCT: + PARAMS += ['--use-http'] + + PARAMS += ['--split-packets', '2'] + if args.SEND_CHAT: + PARAMS += ['--send-chat', args.SEND_CHAT] + + if args.DUP_PACKETS: + PARAMS += ['--dup-packets'] + # PARAMS += ['--my-port', '1024'] + # PARAMS += ['--savefail-pct', 10] + + # With the --seed param passed, games with more than 2 + # devices don't get going. No idea why. This param is NOT + # passed in the old bash version of this script, so fixing + # it isn't a priority. + # PARAMS += ['--seed', args.SEED] + PARAMS += PUBLIC + if DEV > 1: + PARAMS += ['--force-channel', DEV - 1] + else: + PARAMS += ['--server'] + + # print('PARAMS:', PARAMS) + + dev = Device(args, COUNTER, args.APP_NEW, PARAMS, ROOM, FILE, LOG, len(LOCALS)) + dev.update_ldevid() + devs.append(dev) + + COUNTER += 1 + return devs + +# read_resume_cmds() { +# COUNTER=0 +# for LOG in $(ls $LOGDIR/*.txt); do +# echo "need to parse cmd and deal with changes" +# exit 1 +# CMD=$(head -n 1 $LOG) + +# ARGS[$COUNTER]=$CMD +# LOGS[$COUNTER]=$LOG +# PIDS[$COUNTER]=0 + +# set $CMD +# while [ $# -gt 0 ]; do +# case $1 in +# --file) +# FILES[$COUNTER]=$2 +# shift +# ;; +# --room) +# ROOMS[$COUNTER]=$2 +# shift +# ;; +# esac +# shift +# done +# COUNTER=$((COUNTER+1)) +# done +# ROOM_PIDS[$ROOM]=0 +# } + +# launch() { +# KEY=$1 +# LOG=${LOGS[$KEY]} +# APP="${APPS[$KEY]}" +# if [ -z "$APP" ]; then +# echo "error: no app set" +# exit 1 +# fi +# PARAMS="${NEW_ARGS[$KEY]} ${ARGS[$KEY]} ${ARGS_DEVID[$KEY]}" +# exec $APP $PARAMS >/dev/null 2>>$LOG +# } + +# # launch_via_rq() { +# # KEY=$1 +# # RELAYID=$2 +# # PIPE=${PIPES[$KEY]} +# # ../relay/rq -f $RELAYID -o $PIPE & +# # CMD="${CMDS[$KEY]}" +# # exec $CMD >/dev/null 2>>$LOG +# # } + +# send_dead() { +# ID=$1 +# DB=${FILES[$ID]} +# while :; do +# [ -f $DB ] || break # it's gone +# RES=$(echo 'select relayid, seed from games limit 1;' | sqlite3 -separator ' ' $DB || /bin/true) +# [ -n "$RES" ] && break +# sleep 0.2 +# done +# RELAYID=$(echo $RES | awk '{print $1}') +# SEED=$(echo $RES | awk '{print $2}') +# JSON="[{\"relayID\":\"$RELAYID\", \"seed\":$SEED}]" +# curl -G --data-urlencode params="$JSON" http://$HOST/xw4/relay.py/kill >/dev/null 2>&1 +# } + +# close_device() { +# ID=$1 +# MVTO=$2 +# REASON="$3" +# PID=${PIDS[$ID]} +# if [ $PID -ne 0 ]; then +# kill ${PIDS[$ID]} 2>/dev/null +# wait ${PIDS[$ID]} +# ROOM=${ROOMS[$ID]} +# [ ${ROOM_PIDS[$ROOM]} -eq $PID ] && ROOM_PIDS[$ROOM]=0 +# fi +# unset PIDS[$ID] +# unset ARGS[$ID] +# echo "closing game: $REASON" >> ${LOGS[$ID]} +# if [ -n "$MVTO" ]; then +# [ -f "${FILES[$ID]}" ] && mv ${FILES[$ID]} $MVTO +# mv ${LOGS[$ID]} $MVTO +# else +# rm -f ${FILES[$ID]} +# rm -f ${LOGS[$ID]} +# fi +# unset FILES[$ID] +# unset LOGS[$ID] +# unset ROOMS[$ID] +# unset APPS[$ID] +# unset ARGS_DEVID[$ID] + +# COUNT=${#ARGS[*]} +# echo "$COUNT devices left playing..." +# } + +# OBITS="" + +# kill_from_log() { +# LOG=$1 +# RELAYID=$(./scripts/relayID.sh --long $LOG) +# if [ -n "$RELAYID" ]; then +# OBITS="$OBITS -d $RELAYID" +# if [ 0 -eq $(($RANDOM%2)) ]; then +# ../relay/rq -a $HOST $OBITS 2>/dev/null || /bin/true +# OBITS="" +# fi +# return 0 # success +# fi +# echo "unable to send kill command for $LOG" +# return 1 +# } + +# maybe_resign() { +# if [ "$RESIGN_PCT" -gt 0 ]; then +# KEY=$1 +# LOG=${LOGS[$KEY]} +# if grep -aq XWRELAY_ALLHERE $LOG; then +# if [ $((${RANDOM}%100)) -lt $RESIGN_PCT ]; then +# echo "making $LOG $(connName $LOG) resign..." +# kill_from_log $LOG && close_device $KEY $DEADDIR "resignation forced" || /bin/true +# fi +# fi +# fi +# } + +# try_upgrade() { +# KEY=$1 +# if [ 0 -lt ${#APPS_OLD[@]} ]; then +# if [ $APP_NEW != "${APPS[$KEY]}" ]; then +# # one in five chance of upgrading +# if [ 0 -eq $((RANDOM % UPGRADE_ODDS)) ]; then +# APPS[$KEY]=$APP_NEW +# NEW_ARGS[$KEY]="$APP_NEW_PARAMS" +# print_cmdline $KEY +# fi +# fi +# fi +# } + +# try_upgrade_upd() { +# KEY=$1 +# CMD=${ARGS[$KEY]} +# if [ "${CMD/--use-udp/}" = "${CMD}" ]; then +# if [ $((RANDOM % 100)) -lt $UDP_PCT_INCR ]; then +# ARGS[$KEY]="$CMD --use-udp" +# echo -n "$(date +%r): " +# echo "upgrading key $KEY to use UDP" +# fi +# fi +# } + +# check_game() { +# KEY=$1 +# LOG=${LOGS[$KEY]} +# CONNNAME="$(connName $LOG)" +# OTHERS="" +# if [ -n "$CONNNAME" ]; then +# if grep -aq '\[unused tiles\]' $LOG ; then +# for INDX in ${!LOGS[*]}; do +# [ $INDX -eq $KEY ] && continue +# ALOG=${LOGS[$INDX]} +# CONNNAME2="$(connName $ALOG)" +# if [ "$CONNNAME2" = "$CONNNAME" ]; then +# if ! grep -aq '\[unused tiles\]' $ALOG; then +# OTHERS="" +# break +# fi +# OTHERS="$OTHERS $INDX" +# fi +# done +# fi +# fi + +# if [ -n "$OTHERS" ]; then +# echo -n "Closing $CONNNAME [$(date)]: " +# # kill_from_logs $OTHERS $KEY +# for ID in $OTHERS $KEY; do +# echo -n "${ID}:${LOGS[$ID]}, " +# kill_from_log ${LOGS[$ID]} || /bin/true +# send_dead $ID +# close_device $ID $DONEDIR "game over" +# done +# echo "" +# # XWRELAY_ERROR_DELETED may be old +# elif grep -aq 'relay_error_curses(XWRELAY_ERROR_DELETED)' $LOG; then +# echo "deleting $LOG $(connName $LOG) b/c another resigned" +# kill_from_log $LOG || /bin/true +# close_device $KEY $DEADDIR "other resigned" +# elif grep -aq 'relay_error_curses(XWRELAY_ERROR_DEADGAME)' $LOG; then +# echo "deleting $LOG $(connName $LOG) b/c another resigned" +# kill_from_log $LOG || /bin/true +# close_device $KEY $DEADDIR "other resigned" +# else +# maybe_resign $KEY +# fi +# } + +# increment_drop() { +# KEY=$1 +# CMD=${ARGS[$KEY]} +# if [ "$CMD" != "${CMD/drop-nth-packet//}" ]; then +# DROP_N=$(echo $CMD | sed 's,^.*drop-nth-packet \(-*[0-9]*\) .*$,\1,') +# if [ $DROP_N -gt 0 ]; then +# NEXT_N=$((DROP_N+1)) +# ARGS[$KEY]=$(echo $CMD | sed "s,^\(.*drop-nth-packet \)$DROP_N\(.*\)$,\1$NEXT_N\2,") +# fi +# fi +# } + +def summarizeTileCounts(devs): + nDevs = len(devs) + strs = [dev.getTilesCount() for dev in devs] + strs = [s for s in strs if s] + nWithTiles = len(strs) + print('%s %d/%d %s' % (datetime.datetime.now().strftime("%H:%M:%S"), nDevs, nWithTiles, ' '.join(strs))) + +def countCores(): + return len(glob.glob1('/tmp',"core*")) + +def run_cmds(args, devs): + nCores = countCores() + endTime = datetime.datetime.now() + datetime.timedelta(seconds = args.TIMEOUT) + LOOPCOUNT = 0 + + while len(devs) > 0: + if countCores() > nCores: + print('core file count increased; exiting') + break + if datetime.datetime.now() > endTime: + print('outta time; outta here') + break + + LOOPCOUNT += 1 + if 0 == LOOPCOUNT % 20: summarizeTileCounts(devs) + + dev = random.choice(devs) + if not dev.running(): + if dev.allDone: + dev.moveFiles() + dev.send_dead() + devs.remove(dev) + else: +# if [ -n "$ONE_PER_ROOM" -a 0 -ne ${ROOM_PIDS[$ROOM]} ]; then +# continue +# fi +# try_upgrade $KEY +# try_upgrade_upd $KEY + dev.launch() +# PID=$! +# # renice doesn't work on one of my machines... +# renice -n 1 -p $PID >/dev/null 2>&1 || /bin/true +# PIDS[$KEY]=$PID +# ROOM_PIDS[$ROOM]=$PID +# MINEND[$KEY]=$(($NOW + $MINRUN)) + elif not dev.minTimeExpired(): + # print('sleeping...') + time.sleep(2) + else: + dev.kill() + # if g_DROP_N >= 0: dev.increment_drop() + # update_ldevid $KEY + + + # if we get here via a break, kill any remaining games + if devs: + print('stopping %d remaining games' % (len(devs))) + for dev in devs: + if dev.running(): dev.kill() + +# run_via_rq() { +# # launch then kill all games to give chance to hook up +# for KEY in ${!ARGS[*]}; do +# echo "launching $KEY" +# launch $KEY & +# PID=$! +# sleep 1 +# kill $PID +# wait $PID +# # add_pipe $KEY +# done + +# echo "now running via rq" +# # then run them +# while :; do +# COUNT=${#ARGS[*]} +# [ 0 -ge $COUNT ] && break + +# INDX=$(($RANDOM%COUNT)) +# KEYS=( ${!ARGS[*]} ) +# KEY=${KEYS[$INDX]} +# CMD=${ARGS[$KEY]} + +# RELAYID=$(./scripts/relayID.sh --short ${LOGS[$KEY]}) +# MSG_COUNT=$(../relay/rq -a $HOST -m $RELAYID 2>/dev/null | sed 's,^.*-- ,,') +# if [ $MSG_COUNT -gt 0 ]; then +# launch $KEY & +# PID=$! +# sleep 2 +# kill $PID || /bin/true +# wait $PID +# fi +# [ "$DROP_N" -ge 0 ] && increment_drop $KEY +# check_game $KEY +# done +# } # run_via_rq + +# function getArg() { +# [ 1 -lt "$#" ] || usage "$1 requires an argument" +# echo $2 +# } + +def mkParser(): + parser = argparse.ArgumentParser() + parser.add_argument('--send-chat', dest = 'SEND_CHAT', type = str, default = None, + help = 'the message to send') + + parser.add_argument('--app-new', dest = 'APP_NEW', default = './obj_linux_memdbg/xwords', + help = 'the app we\'ll use') + parser.add_argument('--num-games', dest = 'NGAMES', type = int, default = 1, help = 'number of games') + parser.add_argument('--num-rooms', dest = 'NROOMS', type = int, default = 0, + help = 'number of roooms (default to --num-games)') + parser.add_argument('--no-timeout', dest = 'TIMEOUT', default = False, action = 'store_true', + help = 'run forever (default proportional to number of games') + parser.add_argument('--log-root', dest='LOGROOT', default = '.', help = 'where logfiles go') + parser.add_argument('--dup-packets', dest = 'DUP_PACKETS', default = False, help = 'send all packet twice') + parser.add_argument('--use-gtk', dest = 'USE_GTK', default = False, action = 'store_true', + help = 'run games using gtk instead of ncurses') + # # + # # echo " [--clean-start] \\" >&2 + parser.add_argument('--game-dict', dest = 'DICTS', action = 'append', default = []) + # # echo " [--help] \\" >&2 + parser.add_argument('--host', dest = 'HOST', default = 'localhost', + help = 'relay hostname') + # # echo " [--max-devs ] \\" >&2 + parser.add_argument('--min-devs', dest = 'MINDEVS', type = int, default = 2, + help = 'No game will have fewer devices than this') + parser.add_argument('--max-devs', dest = 'MAXDEVS', type = int, default = 4, + help = 'No game will have more devices than this') + parser.add_argument('--min-run', dest = 'MINRUN', type = int, default = 2, + help = 'Keep each run alive at least this many seconds') + # # echo " [--new-app &2 + # # echo " [--new-app-args [arg*]] # passed only to new app \\" >&2 + # # echo " [--num-rooms ] \\" >&2 + # # echo " [--old-app &2 + parser.add_argument('--one-per', dest = 'ONEPER', default = False, + action = 'store_true', help = 'force one player per device') + parser.add_argument('--port', dest = 'PORT', default = 10997, type = int, \ + help = 'Port relay\'s on') + parser.add_argument('--resign-pct', dest = 'RESIGN_PCT', default = 0, type = int, \ + help = 'Odds of resigning [0..100]') + # # echo " [--no-timeout] # run until all games done \\" >&2 + parser.add_argument('--seed', type = int, dest = 'SEED', + default = random.randint(1, 1000000000)) + # # echo " [--send-chat \\" >&2 + # # echo " [--udp-incr ] \\" >&2 + # # echo " [--udp-start ] # default: $UDP_PCT_START \\" >&2 + # # echo " [--undo-pct ] \\" >&2 + parser.add_argument('--http-pct', dest = 'HTTP_PCT', default = 0, type = int, + help = 'pct of games to be using web api') + + parser.add_argument('--undo-pct', dest = 'UNDO_PCT', default = 0, type = int) + parser.add_argument('--trade-pct', dest = 'TRADE_PCT', default = 0, type = int) + + return parser + +# ####################################################### +# ##################### MAIN begins ##################### +# ####################################################### + +def parseArgs(): + args = mkParser().parse_args() + assignDefaults(args) + print(args) + return args + # print(options) + +# while [ "$#" -gt 0 ]; do +# case $1 in +# --udp-start) +# UDP_PCT_START=$(getArg $*) +# shift +# ;; +# --udp-incr) +# UDP_PCT_INCR=$(getArg $*) +# shift +# ;; +# --clean-start) +# DO_CLEAN=1 +# ;; +# --num-games) +# NGAMES=$(getArg $*) +# shift +# ;; +# --num-rooms) +# NROOMS=$(getArg $*) +# shift +# ;; +# --old-app) +# APPS_OLD[${#APPS_OLD[@]}]=$(getArg $*) +# shift +# ;; +# --log-root) +# [ -d $2 ] || usage "$1: no such directory $2" +# LOGDIR=$2/$(basename $0)_logs +# shift +# ;; +# --dup-packets) + # DUP_PACKETS=1 +# ;; +# --new-app) +# APP_NEW=$(getArg $*) +# shift +# ;; +# --new-app-args) +# APP_NEW_PARAMS="${2}" +# echo "got $APP_NEW_PARAMS" +# shift +# ;; +# --game-dict) +# DICTS[${#DICTS[@]}]=$(getArg $*) +# shift +# ;; +# --min-devs) +# MINDEVS=$(getArg $*) +# shift +# ;; +# --max-devs) +# MAXDEVS=$(getArg $*) +# shift +# ;; +# --min-run) +# MINRUN=$(getArg $*) +# [ $MINRUN -ge 2 -a $MINRUN -le 60 ] || usage "$1: n must be 2 <= n <= 60" +# shift +# ;; +# --one-per) +# ONEPER=TRUE +# ;; +# --host) +# HOST=$(getArg $*) +# shift +# ;; +# --port) +# PORT=$(getArg $*) +# shift +# ;; +# --seed) +# SEED=$(getArg $*) +# shift +# ;; +# --undo-pct) +# UNDO_PCT=$(getArg $*) +# shift +# ;; +# --http-pct) +# HTTP_PCT=$(getArg $*) +# [ $HTTP_PCT -ge 0 -a $HTTP_PCT -le 100 ] || usage "$1: n must be 0 <= n <= 100" +# shift +# ;; +# --send-chat) +# SEND_CHAT=$(getArg $*) +# shift +# ;; +# --resign-pct) +# RESIGN_PCT=$(getArg $*) +# [ $RESIGN_PCT -ge 0 -a $RESIGN_PCT -le 100 ] || usage "$1: n must be 0 <= n <= 100" +# shift +# ;; +# --no-timeout) +# TIMEOUT=0x7FFFFFFF +# ;; +# --help) +# usage +# ;; +# *) usage "unrecognized option $1" +# ;; +# esac +# shift +# done + +def assignDefaults(args): + if not args.NROOMS: args.NROOMS = args.NGAMES + if args.TIMEOUT: args.TIMEOUT = 100000000000 # huge number + if len(args.DICTS) == 0: args.DICTS.append('CollegeEng_2to8.xwd') + else: args.TIMEOUT = args.NGAMES * 60 + 500 + args.LOGDIR = os.path.basename(sys.argv[0]) + '_logs' + # Move an existing logdir aside + if os.path.exists(args.LOGDIR): + shutil.move(args.LOGDIR, '/tmp/' + args.LOGDIR + '_' + str(random.randint(0, 100000))) + for d in ['', 'done', 'dead',]: + os.mkdir(args.LOGDIR + '/' + d) +# [ -z "$SAVE_GOOD" ] && SAVE_GOOD=YES +# # [ -z "$RESIGN_PCT" -a "$NGAMES" -gt 1 ] && RESIGN_RATIO=1000 || RESIGN_RATIO=0 +# [ -z "$DROP_N" ] && DROP_N=0 +# [ -z "$USE_GTK" ] && USE_GTK=FALSE +# [ -z "$UPGRADE_ODDS" ] && UPGRADE_ODDS=10 +# #$((NGAMES/50)) +# [ 0 -eq $UPGRADE_ODDS ] && UPGRADE_ODDS=1 +# [ -n "$SEED" ] && RANDOM=$SEED +# [ -z "$ONEPER" -a $NROOMS -lt $NGAMES ] && usage "use --one-per if --num-rooms < --num-games" + +# [ -n "$DO_CLEAN" ] && cleanup + +# RESUME="" +# for FILE in $(ls $LOGDIR/*.{xwg,txt} 2>/dev/null); do +# if [ -e $FILE ]; then +# echo "Unfinished games found in $LOGDIR; continue with them (or discard)?" +# read -p " " ANSWER +# case "$ANSWER" in +# y|yes|Y|YES) +# RESUME=1 +# ;; +# *) +# ;; +# esac +# fi +# break +# done + +# if [ -z "$RESUME" -a -d $LOGDIR ]; then +# NEWNAME="$(basename $LOGDIR)_$$" +# (cd $(dirname $LOGDIR) && mv $(basename $LOGDIR) /tmp/${NEWNAME}) +# fi +# mkdir -p $LOGDIR + +# if [ "$SAVE_GOOD" = YES ]; then +# DONEDIR=$LOGDIR/done +# mkdir -p $DONEDIR +# fi +# DEADDIR=$LOGDIR/dead +# mkdir -p $DEADDIR + +# for VAR in NGAMES NROOMS USE_GTK TIMEOUT HOST PORT SAVE_GOOD \ +# MINDEVS MAXDEVS ONEPER RESIGN_PCT DROP_N ALL_VIA_RQ SEED \ +# APP_NEW; do +# echo "$VAR:" $(eval "echo \$${VAR}") 1>&2 +# done +# echo "DICTS: ${DICTS[*]}" +# echo -n "APPS_OLD: "; [ xx = "${APPS_OLD[*]+xx}" ] && echo "${APPS_OLD[*]}" || echo "" + +# echo "*********$0 starting: $(date)**************" +# STARTTIME=$(date +%s) +# [ -z "$RESUME" ] && build_cmds || read_resume_cmds +# if [ TRUE = "$ALL_VIA_RQ" ]; then +# run_via_rq +# else +# run_cmds +# fi + +# wait + +# SECONDS=$(($(date +%s)-$STARTTIME)) +# HOURS=$((SECONDS/3600)) +# SECONDS=$((SECONDS%3600)) +# MINUTES=$((SECONDS/60)) +# SECONDS=$((SECONDS%60)) +# echo "*********$0 finished: $(date) (took $HOURS:$MINUTES:$SECONDS)**************" + +def main(): + args = parseArgs() + devs = build_cmds(args) + run_cmds(args, devs) + +############################################################################## +if __name__ == '__main__': + main() From ba9b94a0c62ca4b7673b41ae52dbe49713302e09 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 16 Nov 2017 08:18:39 -0800 Subject: [PATCH 62/66] move g_free after last use of ptr (Made same on another branch but couldn't cherry-pick) --- xwords4/linux/relaycon.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xwords4/linux/relaycon.c b/xwords4/linux/relaycon.c index 050e477fa..0519177dd 100644 --- a/xwords4/linux/relaycon.c +++ b/xwords4/linux/relaycon.c @@ -257,12 +257,13 @@ relaycon_receive( GIOChannel* source, GIOCondition XP_UNUSED_DBG(condition), gpo gchar* b64 = g_base64_encode( (const guchar*)buf, ((0 <= nRead)? nRead : 0) ); XP_LOGF( "%s: read %zd bytes ('%s')", __func__, nRead, b64 ); - g_free( b64 ); #ifdef COMMS_CHECKSUM gchar* sum = g_compute_checksum_for_data( G_CHECKSUM_MD5, buf, nRead ); XP_LOGF( "%s: read %zd bytes ('%s')(sum=%s)", __func__, nRead, b64, sum ); g_free( sum ); #endif + g_free( b64 ); + if ( 0 <= nRead ) { const XP_U8* ptr = buf; const XP_U8* end = buf + nRead; From 5ff6f2ca22648b537343a19038caa32378cffeac Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 16 Nov 2017 08:27:51 -0800 Subject: [PATCH 63/66] fix TIMEOUT calculation --- xwords4/linux/scripts/discon_ok2.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/xwords4/linux/scripts/discon_ok2.py b/xwords4/linux/scripts/discon_ok2.py index 82bfa6c33..87c1d5c52 100755 --- a/xwords4/linux/scripts/discon_ok2.py +++ b/xwords4/linux/scripts/discon_ok2.py @@ -900,9 +900,8 @@ def parseArgs(): def assignDefaults(args): if not args.NROOMS: args.NROOMS = args.NGAMES - if args.TIMEOUT: args.TIMEOUT = 100000000000 # huge number + args.TIMEOUT = not args.TIMEOUT and (args.NGAMES * 60 + 500) or 100000000000 if len(args.DICTS) == 0: args.DICTS.append('CollegeEng_2to8.xwd') - else: args.TIMEOUT = args.NGAMES * 60 + 500 args.LOGDIR = os.path.basename(sys.argv[0]) + '_logs' # Move an existing logdir aside if os.path.exists(args.LOGDIR): From 4698c0e54b49d0814fb0d66f1d9ec9c2ae071623 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 19 Nov 2017 20:00:45 -0800 Subject: [PATCH 64/66] add not-again explanation for new archive feature --- .../java/org/eehouse/android/xw4/BoardDelegate.java | 12 +++++++++++- .../java/org/eehouse/android/xw4/DlgDelegate.java | 1 + .../android/app/src/main/res/values/common_rsrc.xml | 1 + xwords4/android/app/src/main/res/values/strings.xml | 8 ++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java index 71f60acf4..b90febc40 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java @@ -214,7 +214,11 @@ public class BoardDelegate extends DelegateBase lstnr = new OnClickListener() { public void onClick( DialogInterface dlg, int whichButton ) { - archiveAndClose( archiveName, archiveGroup ); + makeNotAgainBuilder( R.string.not_again_archive, + R.string.key_na_archive, + Action.ARCHIVE_ACTION ) + .setParams( archiveName, archiveGroup ) + .show(); } }; ab.setNeutralButton( R.string.button_archive, lstnr ); @@ -1107,6 +1111,12 @@ public class BoardDelegate extends DelegateBase makeOkOnlyBuilder( R.string.after_restart ).show(); break; + case ARCHIVE_ACTION: + String archiveName = (String)params[0]; + long archiveGroup = (Long)params[1]; + archiveAndClose( archiveName, archiveGroup ); + break; + case ENABLE_SMS_DO: post( new Runnable() { public void run() { diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java index 265e4b7d6..8b13e3b91 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java @@ -84,6 +84,7 @@ public class DlgDelegate { TRAY_PICKED, INVITE_INFO, DISABLE_DUALPANE, + ARCHIVE_ACTION, // Dict Browser FINISH_ACTION, diff --git a/xwords4/android/app/src/main/res/values/common_rsrc.xml b/xwords4/android/app/src/main/res/values/common_rsrc.xml index 2ef326001..d029cb00f 100644 --- a/xwords4/android/app/src/main/res/values/common_rsrc.xml +++ b/xwords4/android/app/src/main/res/values/common_rsrc.xml @@ -111,6 +111,7 @@ key_notagain_trading key_notagain_hidenewgamebuttons key_na_lookup + key_na_archive key_na_browse key_na_browseall key_na_values diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index 4a5a7eb29..713fd3211 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -1743,6 +1743,14 @@ This button lets you look up, online, the words just played. + Archiving uses a special group + called \"Archive\" to store finished games you want to keep. And, + since deleting an entire archive is easy, archiving is also a + great way to mark games for deletion – if that\'s what you prefer + to do.\n\n(Deleting the Archive group is safe because it will be + created anew when needed.) + + Move New group From b6124ca2d185b21eda90d8919165d720cca3352a Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 19 Nov 2017 20:10:54 -0800 Subject: [PATCH 65/66] change version strings and changelog --- xwords4/android/app/build.gradle | 4 ++-- xwords4/android/app/src/main/assets/changes.html | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/xwords4/android/app/build.gradle b/xwords4/android/app/build.gradle index 62e4504c7..563cce10e 100644 --- a/xwords4/android/app/build.gradle +++ b/xwords4/android/app/build.gradle @@ -1,6 +1,6 @@ def INITIAL_CLIENT_VERS = 8 -def VERSION_CODE_BASE = 125 -def VERSION_NAME = '4.4.129' +def VERSION_CODE_BASE = 126 +def VERSION_NAME = '4.4.130' def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY") def GCM_SENDER_ID = System.getenv("GCM_SENDER_ID") def BUILD_INFO_NAME = "build-info.txt" diff --git a/xwords4/android/app/src/main/assets/changes.html b/xwords4/android/app/src/main/assets/changes.html index 71661ee86..09d30d22e 100644 --- a/xwords4/android/app/src/main/assets/changes.html +++ b/xwords4/android/app/src/main/assets/changes.html @@ -13,9 +13,9 @@ -

CrossWords 4.4.129 release

+

CrossWords 4.4.130 release

-

Quick fix for a another crash reported via the Play Store

+

This release makes a couple of small UI tweaks.

Please take @@ -25,7 +25,12 @@

New with this release

    -
  • Fix crash that only showed up in games using timers
  • +
  • Offer to "Archive" finished games
  • +
  • Make tap on thumbnail toggle whether game's selected rather + than open it. (Tap to the right still opens)
  • +
  • Bug fix: don't allow duplicate group names
  • +
  • Fix battery-hogging behavior on non-Google-play + installs

(The full changelog From 58eafd5fd1dbaeef43adbf7152af146ad04b2b7b Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 20 Nov 2017 22:04:42 -0800 Subject: [PATCH 66/66] fix glitches in XML strings for Norwegian --- xwords4/android/res_src/values-nb-rNO/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/xwords4/android/res_src/values-nb-rNO/strings.xml b/xwords4/android/res_src/values-nb-rNO/strings.xml index c373ac4ce..bdc85ac73 100644 --- a/xwords4/android/res_src/values-nb-rNO/strings.xml +++ b/xwords4/android/res_src/values-nb-rNO/strings.xml @@ -56,11 +56,11 @@ Gjør til forvalg - Er du sikker på at du vil slette ordlisten %1$s? - Er du sikker på at du vil slette ordlistene %1$s? - + Er du sikker på at du vil slette ordlisten %1$s? + Er du sikker på at du vil slette ordlistene %1$s? + - Sletting av %1$s vil bety at du står uten noen %1$s-ordlister. Ett eller flere spill vil ikke kunne åpnes (til du laster ned en erstatningsliste). + Sletting av %1$s vil bety at du står uten noen %2$s-ordlister. Ett eller flere spill vil ikke kunne åpnes (til du laster ned en erstatningsliste). Menneske Maskin @@ -141,7 +141,7 @@ (Ingen trekk enda) - Invitasjon sendt via Blåtann til tilknyttet enhet \"%1½s\" på %2$s + Invitasjon sendt via Blåtann til tilknyttet enhet \"%1$s\" på %2$s Invitasjon sendt via %1$s på %2$s. Ukjent mottaker. Tilkoblingsproblem