From 2428e91bdd38f1274ae903bcbe79e37b898f6f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Ca=C3=B1as=20Vargas?= Date: Sun, 1 Dec 2019 21:34:50 +0100 Subject: [PATCH] Version19.81 --- soap/swad_web_service.h | 87 ++++++++-- swad_API.c | 351 +++++++++++++++++++++++++++++++++++----- swad_API.h | 4 + swad_changelog.h | 3 +- swad_game.c | 2 +- 5 files changed, 394 insertions(+), 53 deletions(-) diff --git a/soap/swad_web_service.h b/soap/swad_web_service.h index f0111ee61..b84364faf 100644 --- a/soap/swad_web_service.h +++ b/soap/swad_web_service.h @@ -192,7 +192,7 @@ struct swad__tagsArray struct swad__tag *__ptr; // pointer to array int __size; // number of elements pointed to }; -struct swad__question +struct swad__testQuestion { int questionCode; char *answerType; @@ -200,12 +200,12 @@ struct swad__question char *stem; char *feedback; }; -struct swad__questionsArray +struct swad__testQuestionsArray { - struct swad__question *__ptr; // pointer to array + struct swad__testQuestion *__ptr; // pointer to array int __size; // number of elements pointed to }; -struct swad__answer +struct swad__testAnswer { int questionCode; int answerIndex; @@ -213,9 +213,9 @@ struct swad__answer char *answerText; char *answerFeedback; }; -struct swad__answersArray +struct swad__testAnswersArray { - struct swad__answer *__ptr; // pointer to array + struct swad__testAnswer *__ptr; // pointer to array int __size; // number of elements pointed to }; struct swad__questionTag @@ -232,17 +232,16 @@ struct swad__questionTagsArray struct swad__getTestsOutput { struct swad__tagsArray tagsArray; - struct swad__questionsArray questionsArray; - struct swad__answersArray answersArray; + struct swad__testQuestionsArray questionsArray; + struct swad__testAnswersArray answersArray; struct swad__questionTagsArray questionTagsArray; }; struct swad__getTrivialQuestionOutput { - struct swad__question question; - struct swad__answersArray answersArray; + struct swad__testQuestion question; + struct swad__testAnswersArray answersArray; }; - /* getGames */ struct swad__game { @@ -255,7 +254,8 @@ struct swad__game int endTime; char *title; char *text; - char *groups; + int numQuestions; + float maxGrade; }; struct swad__gamesArray { @@ -268,6 +268,61 @@ struct swad__getGamesOutput struct swad__gamesArray gamesArray; }; +/* getMatches */ +struct swad__match + { + int matchCode; + char *userSurname1; + char *userSurname2; + char *userFirstname; + char *userPhoto; + int startTime; + int endTime; + char *title; + int questionIndex; + char *groups; + }; +struct swad__matchesArray + { + struct swad__match *__ptr; // pointer to array + int __size; // number of elements pointed to + }; +struct swad__getMatchesOutput + { + int numMatches; + struct swad__matchesArray matchesArray; + }; + +/* playMatch */ +struct swad__playMatchOutput + { + int matchCode; + }; + +/* getMatchStatus */ +struct swad__matchAnswer + { + int answerIndex; + int selected; + }; +struct swad__matchAnswersArray + { + struct swad__matchAnswer *__ptr; // pointer to array + int __size; // number of elements pointed to + }; +struct swad__getMatchStatusOutput + { + int matchCode; + int questionIndex; + struct swad__matchAnswersArray answersArray; + }; + +/* answerMatchQuestion */ +struct swad__answerMatchQuestionOutput + { + int matchCode; + }; + /* structs used in getUsers and sendMessage */ struct swad__user { @@ -445,6 +500,14 @@ int swad__getTrivialQuestion (char *wsKey,char *degrees,float lowerScore,float u /* Games */ int swad__getGames (char *wsKey,int courseCode, struct swad__getGamesOutput *getGamesOut); +int swad__getMatches (char *wsKey,int gameCode, + struct swad__getMatchesOutput *getMatchesOut); +int swad__playMatch (char *wsKey,int matchCode, + struct swad__playMatchOutput *playMatchOut); +int swad__getMatchStatus (char *wsKey,int matchCode, + struct swad__getMatchStatusOutput *getMatchStatusOut); +int swad__answerMatchQuestion (char *wsKey,int matchCode,int questionIndex,int numOption, + struct swad__answerMatchQuestionOutput *answerMatchQuestionOut); /* List of users */ int swad__getUsers (char *wsKey,int courseCode,char *groups,int userRole, diff --git a/swad_API.c b/swad_API.c index 1c5e8a9a0..82c30c461 100644 --- a/swad_API.c +++ b/swad_API.c @@ -238,7 +238,7 @@ static int API_GetTstQuestions (long CrsCod,long BeginTime,struct swad__getTests static int API_GetTstAnswers (long CrsCod,long BeginTime,struct swad__getTestsOutput *getTestsOut); static int API_GetTstQuestionTags (long CrsCod,long BeginTime,struct swad__getTestsOutput *getTestsOut); -static void API_GetListGrpsInGameFromDB (long GamCod,char **ListGroups); +static void API_GetListGrpsInGameFromDB (long MchCod,char **ListGroups); static void API_ListDir (unsigned Level,const char *Path,const char *PathInTree); static bool API_WriteRowFileBrowser (unsigned Level,Brw_FileType_t FileType,const char *FileName); @@ -4324,7 +4324,7 @@ int swad__getGames (struct soap *soap, int ReturnCode; MYSQL_RES *mysql_res; MYSQL_ROW row; - unsigned NumRows; + unsigned long NumRows; int NumGame; long GamCod; char PhotoURL[Cns_MAX_BYTES_WWW + 1]; @@ -4373,32 +4373,28 @@ int swad__getGames (struct soap *soap, } /***** Query list of games *****/ - NumRows = - (unsigned) DB_QuerySELECT (&mysql_res,"can not get games", - "SELECT GamCod," // row[0] - "UsrCod," // row[1] - "UNIX_TIMESTAMP(StartTime) AS ST," // row[2] - "UNIX_TIMESTAMP(EndTime) AS ET," // row[3] - "Title," // row[4] - "Txt" // row[5] - " FROM gam_games" - " WHERE CrsCod=%ld" - " AND Hidden='N'" - " AND NOW() BETWEEN StartTime AND EndTime" - " AND (GamCod NOT IN (SELECT GamCod FROM mch_groups) OR" - " GamCod IN (SELECT mch_groups.GamCod FROM mch_groups,crs_grp_usr" - " WHERE crs_grp_usr.UsrCod=%ld" - " AND mch_groups.GrpCod=crs_grp_usr.GrpCod))" - " ORDER BY ST DESC,ET DESC,Title DESC", - courseCode, - Gbl.Usrs.Me.UsrDat.UsrCod); - + NumRows = DB_QuerySELECT (&mysql_res,"can not get games", + "SELECT gam_games.GamCod," // row[0] + "gam_games.UsrCod," // row[1] + "MIN(mch_matches.StartTime) AS StartTime," // row[2] + "MAX(mch_matches.EndTime) AS EndTime" // row[3] + "gam_games.MaxGrade," // row[4] + "gam_games.Title," // row[5] + "gam_games.Txt" // row[6] + " FROM gam_games" + " LEFT JOIN mch_matches" + " ON gam_games.GamCod=mch_matches.GamCod" + " WHERE gam_games.CrsCod=%ld" + " AND Hidden='N'" + " GROUP BY gam_games.GamCod" + " ORDER BY StartTime DESC,EndTime DESC,gam_games.Title DESC", + Gbl.Hierarchy.Crs.CrsCod); getGamesOut->gamesArray.__size = getGamesOut->numGames = (int) NumRows; if (getGamesOut->numGames == 0) getGamesOut->gamesArray.__ptr = NULL; - else // Events found + else // Games found { getGamesOut->gamesArray.__ptr = soap_malloc (Gbl.soap,(getGamesOut->gamesArray.__size) * sizeof (*(getGamesOut->gamesArray.__ptr))); @@ -4406,7 +4402,7 @@ int swad__getGames (struct soap *soap, NumGame < getGamesOut->numGames; NumGame++) { - /* Get next group */ + /* Get next game */ row = mysql_fetch_row (mysql_res); /* Get game code (row[0]) */ @@ -4462,20 +4458,22 @@ int swad__getGames (struct soap *soap, sscanf (row[3],"%ld",&EndTime); getGamesOut->gamesArray.__ptr[NumGame].endTime = EndTime; - /* Get title of the game (row[4]) */ - Length = strlen (row[4]); - getGamesOut->gamesArray.__ptr[NumGame].title = (char *) soap_malloc (Gbl.soap,Length + 1); - Str_Copy (getGamesOut->gamesArray.__ptr[NumGame].title,row[4], - Length); + /* Get maximum grade (row[4]) */ + getGamesOut->gamesArray.__ptr[NumGame].maxGrade = Str_GetDoubleFromStr (row[4]); + if (getGamesOut->gamesArray.__ptr[NumGame].maxGrade < 0.0) // Only positive values allowed + getGamesOut->gamesArray.__ptr[NumGame].maxGrade = 0.0; - /* Get Txt (row[5]) */ + /* Get title of the game (row[5]) */ Length = strlen (row[5]); - getGamesOut->gamesArray.__ptr[NumGame].text = (char *) soap_malloc (Gbl.soap,Length + 1); - Str_Copy (getGamesOut->gamesArray.__ptr[NumGame].text,row[5], + getGamesOut->gamesArray.__ptr[NumGame].title = (char *) soap_malloc (Gbl.soap,Length + 1); + Str_Copy (getGamesOut->gamesArray.__ptr[NumGame].title,row[5], Length); - /* Get list of groups for this game */ - API_GetListGrpsInGameFromDB (GamCod,&(getGamesOut->gamesArray.__ptr[NumGame].groups)); + /* Get Txt (row[6]) */ + Length = strlen (row[6]); + getGamesOut->gamesArray.__ptr[NumGame].text = (char *) soap_malloc (Gbl.soap,Length + 1); + Str_Copy (getGamesOut->gamesArray.__ptr[NumGame].text,row[6], + Length); } } @@ -4486,10 +4484,285 @@ int swad__getGames (struct soap *soap, } /*****************************************************************************/ -/**************** Get lists of groups of an attendance event *****************/ +/********************** Return list of matches in a game *********************/ /*****************************************************************************/ -static void API_GetListGrpsInGameFromDB (long GamCod,char **ListGroups) +int swad__getMatches (struct soap *soap, + char *wsKey,int gameCode, // input + struct swad__getMatchesOutput *getMatchesOut) // output + { + int ReturnCode; + struct Game Game; + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned long NumRows; + int NumMatch; + long MchCod; + char PhotoURL[Cns_MAX_BYTES_WWW + 1]; + long StartTime; + long EndTime; + size_t Length; + + /***** Initializations *****/ + Gbl.soap = soap; + Gbl.WebService.Function = API_getMatches; + + /***** Check web service key *****/ + if ((ReturnCode = API_CheckWSKey (wsKey)) != SOAP_OK) + return ReturnCode; + if (Gbl.Usrs.Me.UsrDat.UsrCod < 0) // Web service key does not exist in database + return soap_receiver_fault (Gbl.soap, + "Bad web service key", + "Web service key does not exist in database"); + + /***** Get game data from database *****/ + Game.GamCod = (long) gameCode; + if (Game.GamCod <= 0) + return soap_sender_fault (Gbl.soap, + "Bad game code", + "Game code must be a integer greater than 0"); + Gam_GetDataOfGameByCod (&Game); + + /***** Check if course code is correct *****/ + Gbl.Hierarchy.Crs.CrsCod = (long) Game.CrsCod; + if (Gbl.Hierarchy.Crs.CrsCod <= 0) + return soap_sender_fault (Gbl.soap, + "Bad course code", + "Course code must be a integer greater than 0"); + + /***** Get some of my data *****/ + if (!API_GetSomeUsrDataFromUsrCod (&Gbl.Usrs.Me.UsrDat,Gbl.Hierarchy.Crs.CrsCod)) + return soap_receiver_fault (Gbl.soap, + "Can not get user's data from database", + "User does not exist in database"); + Gbl.Usrs.Me.Logged = true; + Gbl.Usrs.Me.Role.Logged = Gbl.Usrs.Me.UsrDat.Roles.InCurrentCrs.Role; + + /***** Check if I am a student in the course *****/ + switch (Gbl.Usrs.Me.UsrDat.Roles.InCurrentCrs.Role) + { + case Rol_STD: + // OK. I am a student to the course + break; + default: + return soap_receiver_fault (Gbl.soap, + "Request forbidden", + "Requester must be a student in the course"); + } + + /***** Query list of matches *****/ + NumRows = DB_QuerySELECT (&mysql_res,"can not get matches", + "SELECT MchCod," // row[ 0] + "UsrCod," // row[ 1] + "UNIX_TIMESTAMP(StartTime)," // row[ 2] + "UNIX_TIMESTAMP(EndTime)," // row[ 3] + "Title," // row[ 4] + "QstInd" // row[ 5] + " FROM mch_matches" + " WHERE GamCod=%ld" + " AND" + "(MchCod NOT IN" + " (SELECT MchCod FROM mch_groups)" + " OR" + " MchCod IN" + " (SELECT mch_groups.MchCod" + " FROM mch_groups,crs_grp_usr" + " WHERE crs_grp_usr.UsrCod=%ld" + " AND mch_groups.GrpCod=crs_grp_usr.GrpCod))", + " ORDER BY MchCod", + Game.GamCod, + Gbl.Usrs.Me.UsrDat.UsrCod); + getMatchesOut->matchesArray.__size = + getMatchesOut->numMatches = (int) NumRows; + + if (getMatchesOut->numMatches == 0) + getMatchesOut->matchesArray.__ptr = NULL; + else // Matches found + { + getMatchesOut->matchesArray.__ptr = soap_malloc (Gbl.soap,(getMatchesOut->matchesArray.__size) * sizeof (*(getMatchesOut->matchesArray.__ptr))); + + for (NumMatch = 0; + NumMatch < getMatchesOut->numMatches; + NumMatch++) + { + /* Get next game */ + row = mysql_fetch_row (mysql_res); + + /* Get match code (row[0]) */ + MchCod = Str_ConvertStrCodToLongCod (row[0]); + getMatchesOut->matchesArray.__ptr[NumMatch].matchCode = (int) MchCod; + + /* Get user's code of the user who created the game (row[1]) */ + Gbl.Usrs.Other.UsrDat.UsrCod = Str_ConvertStrCodToLongCod (row[1]); + if (API_GetSomeUsrDataFromUsrCod (&Gbl.Usrs.Other.UsrDat,Gbl.Hierarchy.Crs.CrsCod)) // Get some user's data from database + { + Length = strlen (Gbl.Usrs.Other.UsrDat.Surname1); + getMatchesOut->matchesArray.__ptr[NumMatch].userSurname1 = (char *) soap_malloc (Gbl.soap,Length + 1); + Str_Copy (getMatchesOut->matchesArray.__ptr[NumMatch].userSurname1, + Gbl.Usrs.Other.UsrDat.Surname1, + Length); + + Length = strlen (Gbl.Usrs.Other.UsrDat.Surname2); + getMatchesOut->matchesArray.__ptr[NumMatch].userSurname2 = (char *) soap_malloc (Gbl.soap,Length + 1); + Str_Copy (getMatchesOut->matchesArray.__ptr[NumMatch].userSurname2, + Gbl.Usrs.Other.UsrDat.Surname2, + Length); + + Length = strlen (Gbl.Usrs.Other.UsrDat.FirstName); + getMatchesOut->matchesArray.__ptr[NumMatch].userFirstname = (char *) soap_malloc (Gbl.soap,Length + 1); + Str_Copy (getMatchesOut->matchesArray.__ptr[NumMatch].userFirstname, + Gbl.Usrs.Other.UsrDat.FirstName, + Length); + + Pho_BuildLinkToPhoto (&Gbl.Usrs.Other.UsrDat,PhotoURL); + Length = strlen (PhotoURL); + getMatchesOut->matchesArray.__ptr[NumMatch].userPhoto = (char *) soap_malloc (Gbl.soap,Length + 1); + Str_Copy (getMatchesOut->matchesArray.__ptr[NumMatch].userPhoto, + PhotoURL, + Length); + } + else + { + getMatchesOut->matchesArray.__ptr[NumMatch].userSurname1 = NULL; + getMatchesOut->matchesArray.__ptr[NumMatch].userSurname2 = NULL; + getMatchesOut->matchesArray.__ptr[NumMatch].userFirstname = NULL; + getMatchesOut->matchesArray.__ptr[NumMatch].userPhoto = NULL; + } + + /* Get match start time (row[2]) */ + StartTime = 0L; + if (row[2]) + sscanf (row[2],"%ld",&StartTime); + getMatchesOut->matchesArray.__ptr[NumMatch].startTime = StartTime; + + /* Get match end time (row[3]) */ + EndTime = 0L; + if (row[3]) + sscanf (row[3],"%ld",&EndTime); + getMatchesOut->matchesArray.__ptr[NumMatch].endTime = EndTime; + + /* Get title of the match (row[4]) */ + Length = strlen (row[4]); + getMatchesOut->matchesArray.__ptr[NumMatch].title = (char *) soap_malloc (Gbl.soap,Length + 1); + Str_Copy (getMatchesOut->matchesArray.__ptr[NumMatch].title,row[4], + Length); + + /* Get current question index (row[5]) */ + getMatchesOut->matchesArray.__ptr[NumMatch].questionIndex = (int) Gam_GetQstIndFromStr (row[5]); + + /* Get list of groups for this match */ + API_GetListGrpsInGameFromDB (MchCod,&(getMatchesOut->matchesArray.__ptr[NumMatch].groups)); + } + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + return SOAP_OK; + } + +/*****************************************************************************/ +/********************** Try to join to a match as student ********************/ +/*****************************************************************************/ + +int swad__playMatch (struct soap *soap, + char *wsKey,int matchCode, // input + struct swad__playMatchOutput *playMatchOut) // output + { + int ReturnCode; + + /***** Initializations *****/ + Gbl.soap = soap; + Gbl.WebService.Function = API_playMatch; + + /***** Check web service key *****/ + if ((ReturnCode = API_CheckWSKey (wsKey)) != SOAP_OK) + return ReturnCode; + if (Gbl.Usrs.Me.UsrDat.UsrCod < 0) // Web service key does not exist in database + return soap_receiver_fault (Gbl.soap, + "Bad web service key", + "Web service key does not exist in database"); + + // TODO: Write the code + playMatchOut->matchCode = matchCode; + + return SOAP_OK; + } + +/*****************************************************************************/ +/*********** Get match status to be refreshed in student's screen ************/ +/*****************************************************************************/ + +int swad__getMatchStatus (struct soap *soap, + char *wsKey,int matchCode, // input + struct swad__getMatchStatusOutput *getMatchStatusOut) // output + { + int ReturnCode; + + /***** Initializations *****/ + Gbl.soap = soap; + Gbl.WebService.Function = API_getMatchStatus; + + /***** Check web service key *****/ + if ((ReturnCode = API_CheckWSKey (wsKey)) != SOAP_OK) + return ReturnCode; + if (Gbl.Usrs.Me.UsrDat.UsrCod < 0) // Web service key does not exist in database + return soap_receiver_fault (Gbl.soap, + "Bad web service key", + "Web service key does not exist in database"); + + // TODO: Write the code + getMatchStatusOut->matchCode = matchCode; + + return SOAP_OK; + } + +/*****************************************************************************/ +/************* Send an answer to the current question in a match *************/ +/*****************************************************************************/ + +int swad__answerMatchQuestion (struct soap *soap, + char *wsKey,int matchCode,int questionIndex,int numOption, // input + struct swad__answerMatchQuestionOutput *answerMatchQuestionOut) // output + { + int ReturnCode; + // unsigned QstInd; // 0 means that the game has not started. First question has index 1. + // unsigned NumOpt; + + /***** Initializations *****/ + Gbl.soap = soap; + Gbl.WebService.Function = API_answerMatchQuestion; + + /***** Check web service key *****/ + if ((ReturnCode = API_CheckWSKey (wsKey)) != SOAP_OK) + return ReturnCode; + if (Gbl.Usrs.Me.UsrDat.UsrCod < 0) // Web service key does not exist in database + return soap_receiver_fault (Gbl.soap, + "Bad web service key", + "Web service key does not exist in database"); + + // TODO: Write the code + if (questionIndex > 0 && numOption > 0) + { + // QstInd = (unsigned) questionIndex; + // NumOpt = (unsigned) numOption; + answerMatchQuestionOut->matchCode = matchCode; + } + else + { + // QstInd = 0; + // NumOpt = 0; + answerMatchQuestionOut->matchCode = -1; + } + + return SOAP_OK; + } + +/*****************************************************************************/ +/*********************** Get lists of groups of a match **********************/ +/*****************************************************************************/ + +static void API_GetListGrpsInGameFromDB (long MchCod,char **ListGroups) { MYSQL_RES *mysql_res; MYSQL_ROW row; @@ -4502,11 +4775,11 @@ static void API_GetListGrpsInGameFromDB (long GamCod,char **ListGroups) /***** Get list of groups *****/ NumGrps = (unsigned) DB_QuerySELECT (&mysql_res,"can not get groups of a match", - "SELECT GrpCod FROM mch_groups WHERE GamCod=%ld", - GamCod); + "SELECT GrpCod FROM mch_groups WHERE MchCod=%ld", + MchCod); if (NumGrps == 0) *ListGroups = NULL; - else // Events found + else // Groups found { Length = NumGrps * (10 + 1) - 1; *ListGroups = soap_malloc (Gbl.soap,Length + 1); diff --git a/swad_API.h b/swad_API.h index 0eb091c17..130cf2732 100644 --- a/swad_API.h +++ b/swad_API.h @@ -69,6 +69,10 @@ typedef enum API_findUsers = 26, API_removeAttendanceEvent = 27, API_getGames = 28, + API_getMatches = 29, + API_playMatch = 30, + API_getMatchStatus = 31, + API_answerMatchQuestion = 32, } API_Function_t; /*****************************************************************************/ diff --git a/swad_changelog.h b/swad_changelog.h index b5dfb4095..a3f8e2c04 100644 --- a/swad_changelog.h +++ b/swad_changelog.h @@ -490,7 +490,7 @@ enscript -2 --landscape --color --file-align=2 --highlight --line-numbers -o - * En OpenSWAD: ps2pdf source.ps destination.pdf */ -#define Log_PLATFORM_VERSION "SWAD 19.80.7 (2019-11-29)" +#define Log_PLATFORM_VERSION "SWAD 19.81 (2019-12-01)" #define CSS_FILE "swad19.78.1.css" #define JS_FILE "swad19.70.js" /* @@ -498,6 +498,7 @@ ps2pdf source.ps destination.pdf // TODO: Impedir la creación y edición de proyectos si no son editables. // TODO: En cada juego, poder listar los resultados en una tabla como la de resultados globales + Version 19.81: Dec 01, 2019 New API functions related to games and matches. (247540 lines) Version 19.80.7: Nov 29, 2019 Changes in match results. (247242 lines) Version 19.80.6: Nov 28, 2019 Changes in match results and test results. (247245 lines) Version 19.80.5: Nov 28, 2019 Total grade is always displayed in results. (247278 lines) diff --git a/swad_game.c b/swad_game.c index d78074abe..ad793eda5 100644 --- a/swad_game.c +++ b/swad_game.c @@ -688,7 +688,7 @@ void Gam_GetListGames (Gam_Order_t SelectedOrder) switch (Gbl.Usrs.Me.Role.Logged) { case Rol_STD: - if (asprintf (&HiddenSubQuery," AND Hidden='N'") < 0) + if (asprintf (&HiddenSubQuery," AND gam_games.Hidden='N'") < 0) Lay_NotEnoughMemoryExit (); break; case Rol_NET: