24 years developing SWAD and OpenSWAD. Hits, misses and lessons learned. Including implementation details of the timeline or social network integrated into this free software Learning Management System developed by Antonio Cañas, professor of the University of Granada (Spain).
1. 1
1
Antonio Cañas et al.
Nov 3, 2023, Granada, Spain
24 years developing SWAD:
hits, misses & lessons learned
Including timeline implementation
Antonio Cañas
University of Granada
@acanasvargas acanas@ugr.es acanas@openswad.org
https://openswad.org/ @openswad
3. SWAD
“swad: a bunch, a grouping
of a number of similar things”
.
https://www.thefreedictionary.com/swad
4. Sistema Web de Apoyo a la Docencia
(Web System for Teaching Support)
⬇
Social Workspace At a Distance
https://swad.ugr.es/ https://openswad.org/
A web platform to manage courses, students and teachers,
with functions to support teaching and learning.
SWAD
5. SWAD
This is how it looks.
Some parts of its appearance,
such as colors or icons, are customizable
6. SWAD: Features
Free software · 10 languages · Responsive design · Android
app · iOS app · Face-to-face or blended learning
Hierarchical organization: System · Countries · Institutions
(universities, companies) · Centers (faculties, schools) ·
Degrees · Courses · Group types · Groups
10 available roles: Unknown · Guest · User · Student · Non-
editing teacher · Teacher · Degree admin · Center admin ·
Institution admin · System admin
7. SWAD: Features
Social network · Calendar · Notifications · Course
information · Syllabus · Documents · Shared files ·
Portfolio · Grades · Assignments · Projects · Exam
announcements · Quizzes · Exams · Games · Rubrics ·
Groups · Lists of students and teachers · Attendance
control using QR codes · Forums · Notices · Messaging
system · Surveys · Statistics · Agenda · Preferences
All features in
https://github.com/acanas/swad-core/wiki/UserGuide.en
8. SWAD: History
●
LMS in 1999: WebCT
(1997), Moodle (1999)
Radio Granada, May 12, 2000
I just want to test
if it's possible to fill out
the student record card
via the web
9. SWAD: History
●
LMS in 1999: WebCT
(1997), Moodle (1999)
Version 4.2.1, May 13, 2003
(it was not called SWAD yet)
I just want to test
if it's possible to fill out
the student record card
via the web
10. SWAD: History
●
LMS in 2023: a very broad offer
●
Hundreds of LMS (Learning Management Systems)
●
proprietary / free software
●
expensive / free of charge
●
specific / generic
●
installable on the client's servers / accessible in the cloud
15. Software: HTML
●
HTML (HyperText Markup Language)
●
1990 (Tim Berners-Lee)
●
Standard language for documents to be displayed in a web browser
●
Describes the semantic structure of a web page
●
...by using tags and attributes: <a href="https://openswad.org/">...</a>
●
Assisted by CSS (appearance) and JavaScript (behavior)
●
Web browsers
●
receive HTML documents from a web server
●
render the documents into multimedia web pages
16. Software: HTML
●
HTML:
<html lang="en">
<head>
<link rel="stylesheet"
href="https://openswad.org/swad/swad21.95.5.css"
type="text/css" />
<script type="text/javascript"
src="https://openswad.org/swad/swad21.92.js">
</script>
<title>
OpenSWAD: social learning platform
</title>
</head>
<body class="BODY_GREY" onload="init();">
<!-- HTML depending on action -->
</body>
</html>
HTML code
in user’s browser
20. Software: CSS
“Learning HTML and CSS is a lot more challenging
than it used to be. Responsive web design adds more
layers of complexity to design and develop websites.”
Jacob Lett – Digital Marketing & Web Development Manager
21. Software: CSS
●
CSS (Cascading Style Sheets) language
●
1996 (Håkon Wium Lie, Bert Bos)
●
Separates
●
presentation (layout, colors, fonts)
●
content
of an HTML document
●
Multiple web pages can share formatting in a .css file
●
“Cascading”: priority scheme
●
determines which style rule applies if more than one rule matches a particular element
22. Software: CSS
●
CSS:
.Tml_COM_TEXTAREA
{
box-sizing: border-box;
margin: 0;
resize: none;
}
@media only screen and (max-width: 590px)
{ /* For mobile-phones */
...
.Tml_RIGHT_WIDTH {width: 260px;} /* 500-240 */
...
}
@media only screen and (min-width: 590px)
{ /* For tablets and desktop */
...
.Tml_RIGHT_WIDTH {width: 500px;}
...
}
swad23.35.1.css
23. Software: JavaScript
“The strength of JavaScript is that you can do
anything. The weakness is that you will.”
Reg Braithwaite – Canadian programmer and writer
24. Software: JavaScript
●
JavaScript:
●
1995 (Brendan Eich, Netscape, Sun)
●
Multi-paradigm, interpreted / just-in-time compiled, syntax similar to C and Java
●
Modern browsers interpret JavaScript code
●
embedded in the web page
●
or in a .js file
to add interactive features
●
Interacts with the Document Object Model (DOM)
●
Represents a document as a logical tree, each node is an object (part of the document )
25. <form method="post" action="https://openswad.org/en">
<input type="hidden" name="act" value="1492">
<input type="hidden" name="ses" value="6BJQ...G46g">
<input type="hidden" name="Who" value="4">
<textarea name="Txt" rows="1" maxlength="1000"
placeholder="New post…"
class="Tml_COM_TEXTAREA Tml_RIGHT_WIDTH"
onfocus="expandTextarea (this, 'id_EwuT...BhSs_6', '6');">
</textarea>
<div id="id_EwuT...BhSs_6" style="display:none;">
...
<button type="submit" class="...">
Post
</button>
</div>
</form>
Software: JavaScript
●
HTML:
Textarea used to write the post
27. Software: JavaScript
●
HTML
●
JavaScript:
/*****************************************************************************/
/*********************** Expand textarea when focus **************************/
/*****************************************************************************/
// Called from a textarea onfocus
function expandTextarea (textareaElem,idButton,rows) {
textareaElem.rows = rows;
document.getElementById(idButton).style.display = '';
}
swad22.49.js
<textarea name="Txt" rows="1" maxlength="1000"
placeholder="New post…"
class="Tml_COM_TEXTAREA Tml_RIGHT_WIDTH"
onfocus="expandTextarea (this, 'id_EwuT...BhSs_6', '6');">
</textarea>
28. Software: CGI in C
“C is quirky, flawed, and an enormous success.”
Dennis Ritchie – Creator of the C programming language
29. Software: CGI in C
●
C language
●
imperative, procedural, general-purpose
●
1970s (Dennis Ritchie)
●
Use
●
Remains widely used in OSs, device drivers, embedded system applications, libraries...
●
Decreasingly used for application software
●
Hardly used for the web
30. Software: CGI in C
●
All code is contained within functions
●
Function parameters are passed by value
●
Pass-by-reference is simulated by passing pointers
#include <stdio.h>
int main (void)
{
printf ("hello, worldn");
return 0;
}
main.c
compiled, not interpreted
31. Software: CGI in C
●
Common Gateway Interface (CGI)
1.The user submits a web form on a web page (eg by clicking on a submit button)
2.The form's data is sent by browser to web server within an HTTP request with a URL
denoting a server CGI script in a cgi-bin directory
●
Written in a scripting language (eg Perl)
●
Or may be a compiled program (a C program in our case)
3.The web server (Apache in our case) launches the CGI script in a new process, passing
the form data to it
4.The output sent to standard output by the CGI script, usually in the form of HTML, is
passed to the client browser instead of being shown on-screen in a terminal window
32. Up to 400K times per day in 2014
Up to 2000 times / minute (30 times / second)
Software: CGI in C
“click”
logged access
HTML5 + CSS3 + JavaScript
server
MySQL database
swad-core
37. ●
HTML:
<form method="post" action="https://openswad.org/en">
<input type="hidden" name="act" value="1492">
<input type="hidden" name="ses" value="6BJQ...G46g">
<input type="hidden" name="Who" value="4">
<textarea name="Txt" rows="6" maxlength="1000"
placeholder="New post…"
class="Tml_COM_TEXTAREA Tml_RIGHT_WIDTH"
onfocus="expandTextarea (this, 'id_EwuT...BhSs_6', '6');">
</textarea>
<div id="id_EwuT...BhSs_6" style="">
...
<button type="submit" class="...">
Post
</button>
</div>
</form>
Software: CGI in C
Textarea used to write the post
38. ●
HTML:
<form method="post" action="https://openswad.org/en">
<input type="hidden" name="act" value="1492">
<input type="hidden" name="ses" value="6BJQ...G46g">
<input type="hidden" name="Who" value="4">
<textarea name="Txt" rows="6" maxlength="1000"
placeholder="New post…"
class="Tml_COM_TEXTAREA Tml_RIGHT_WIDTH"
onfocus="expandTextarea (this, 'id_EwuT...BhSs_6', '6');">
</textarea>
<div id="id_EwuT...BhSs_6" style="">
...
<button type="submit" class="...">
Post
</button>
</div>
</form>
Software: CGI in C
Button clicked to submit the post
39. Software: CGI in C
●
Input:
Method ← getenv ("REQUEST_METHOD");
ContentType ← getenv ("CONTENT_TYPE");
if (!strcmp (Method,"GET")) { // GET method
ContentLength = strlen (getenv ("QUERY_STRING"));
/* Copy query string from environment variable */
QueryString = malloc (ContentLength + 1);
Str_Copy (QueryString,getenv ("QUERY_STRING"),ContentLength);
}
else { // POST method
ContentLength ← getenv ("CONTENT_LENGTH");
/* Copy query string from stdin */
QueryString = malloc (ContentLength + 1);
fread (QueryString,sizeof (char),ContentLength,stdin);
}
/* Parse QueryString creating linked list of parameters */
Par_CreateListOfParams ();
Simplified C code on the server (swad-core)
40. Software: CGI in C
●
Output:
fprintf (stdout,"Content-type: text/html;rnrn"
"<!DOCTYPE html>n");
HTM_TxtF ("<html lang="%s">n",
Lan_STR_LANG_ID[Gbl.Prefs.Language]);
HTM_Txt ("<head>n");
HTM_TxtF ("<link rel="stylesheet" href="%s/%s" type="text/css" />n",
Cfg_URL_SWAD_PUBLIC,CSS_FILE);
HTM_TxtF ("<script type="text/javascript" src="%s/%s">n",
Cfg_URL_SWAD_PUBLIC,JS_FILE);
HTM_Txt ("</script>n");
...
HTM_Txt ("</head>n");
HTM_Txt ("<body onload="init();">n");
/* Write HTML depending on action */
...
HTM_Txt ("</body>n");
HTM_Txt ("</html>n");
Simplified C code on the server (swad-core)
41. Software: CGI in C
●
Output:
void HTM_TxtF (const char *fmt,...) {
va_list ap;
int NumBytesPrinted;
char *Attr;
if (fmt)
if (fmt[0]) {
va_start (ap,fmt);
NumBytesPrinted = vasprintf (&Attr,fmt,ap);
va_end (ap);
if (NumBytesPrinted < 0) // -1 if no memory or any other error
Err_NotEnoughMemoryExit ();
/***** Print HTML *****/
HTM_Txt (Attr);
free (Attr);
}
}
Simplified C code on the server (swad-core)
void HTM_Txt (const char *Txt) {
if (Txt)
if (Txt[0])
fputs (Txt,Gbl.F.Out);
}
43. Software: Database
“Information is the oil of the 21st century, and
analytics is the combustion engine.”
Peter Sondergaard – Former CEO of Gartner, Inc.
45. Software: Other modules
“We build our computer systems the way we build our
cities: over time, without a plan, on top of ruins.”
Ellen Ullman – American computer programmer and author
46. Software: Other modules
●
User photos
●
Our own automatic system for detecting
faces and improving the quality of
photos, trained with 90K photos
●
Program written in C++ / OpenCV and
called from swad-core
●
Daniel J. Calandria Hernández
●
Jesús Mesa González
47. Software: Other modules
●
Photo average
●
Average / median
photo, pixel by pixel,
of all the students of a
degree
●
Program written in C++
/ OpenCV and called
from swad-core
Daniel J. Calandria
Hernández
48. Software: Other modules
●
API
●
It is possible to develop add-ons (plugins) that run on:
●
other servers
●
mobile devices
●
SWADroid, iSWAD, Triswados
●
The plugins interact with swad-core through its integrated API
●
https://openswad.org/api/
49. Software: Other modules
●
SWADroid
●
App for Android
●
https://play.google.com/store/apps/details?id=es.ugr.swad.swadroid
By Juan Miguel Boyero Corral (2010-2023)
●
https://github.com/Amab/SWADroid
50. Software: Other modules
●
Former developers:
Antonio Manuel Aguilera Malagón (2012, QR attendance control)
●
https://github.com/aguilerin/SWADroid
●
https://es.slideshare.net/antonioaguileramalagon/swadroid-pasar-lista-manual-de-usuario
Helena Rodríguez Gijón (2012, file download, registration in groups, notices)
●
https://es.slideshare.net/helenarguez/swa-droid-f4
●
https://swadroid.wordpress.com/2012/11/17/publicado-swadroid-0-10-1/
●
https://github.com/hrguez
José Antonio Guerrero Avilés (2014, course information, messages)
●
https://github.com/antonio314/SWADroid
●
https://es.slideshare.net/JoseAntonioGuerreroA/presentacion-proyecto-fin-de-carrera-ampliacion-de-swadroid
●
https://www.linkedin.com/in/antonioguerreroaviles/overlay/50268922/single-media-viewer/
●
https://www.linkedin.com/in/antonioguerreroaviles/overlay/50268914/single-media-viewer/
52. Software: Other modules
swad-core SWADroid
Affero GPL v3 license
https://github.com/acanas/swad-core
GPL v3 license
https://git.cuernodehipnos.es/Marown/SWADroid
387,435
C code lines
151
MySQL tables
39,216
Java code lines
17K
downloads
101 person-years
estimated effort*
9 person-years
estimated effort*
$5,579,446
estimated cost*
$500,680
estimated cost*
Other modules and more info: https://openswad.org/source
* According to the COCOMO model in Open Hub
53. Software: Other modules
●
iSWAD
●
App for iOS
https://apps.apple.com/es/app/iswad-platform/id1570162425
By Bate Ye (2021, Swift)
https://github.com/WolfYe98/iSWAD
54. Software: Other modules
●
Former developers:
Diego Montesinos Hervás (2011, Objective C)
https://github.com/diegort/iSWAD
Raúl Álvarez Hinojosa (2016, Swift)
https://github.com/Rauleinstein/iSWAD
Adrián Lara Roldán (2018, Swift)
https://github.com/mitomono/iSWAD
55. Timeline: Notes & Pubs
“You think you know when you learn, are more sure
when you can write, even more when you can teach,
but certain when you can program.”
Alan Perlis – Computer scientist and professor
58. Timeline: Notes & Pubs
●
Timeline: set of publications
●
from a user
●
global
●
Only me
●
Followed users
●
All users
typedef enum
{
Tml_Usr_TIMELINE_USR,
Tml_Usr_TIMELINE_GBL,
} Tml_Usr_UsrOrGbl_t;
typedef enum
{
Usr_WHO_UNKNOWN,
Usr_WHO_ME,
Usr_WHO_SELECTED, // Not applicable to timeline
Usr_WHO_FOLLOWED,
Usr_WHO_ALL,
} Usr_Who_t;
59. Timeline: Notes & Pubs
●
Publication: · original note (26851, 75% of 35968)
· shared note ( 1456, 4% of 35968)
· comment to a note ( 7661, 21% of 35968)
typedef enum
{
Tml_Pub_UNKNOWN = 0,
Tml_Pub_ORIGINAL_NOTE = 1,
Tml_Pub_SHARED_NOTE = 2,
Tml_Pub_COMMENT_TO_NOTE = 3,
} TmlPub_Type_t;
struct TmlPub_Publication
{
long PubCod; // Publication code
long NotCod; // Note code
long PublisherCod; // Sharer or writer of the publication
TmlPub_Type_t Type; // Original note, shared note, comment
struct TmlPub_Publication *Next; // Used for chained list
};
*swad.ugr.es, nov 2023
60. Timeline: Notes & Pubs
●
Note: timeline post ( 5484, 20% of 26851)
public file ( 82, <1% of 26851)
call for exam ( 3067, 11% of 26851)
notice (18011, 67% of 26851)
forum post ( 207, 1% of 26851)
*swad.ugr.es, nov 2023
61. Timeline: Notes & Pubs
typedef enum
{
TmlNot_UNKNOWN = 0,
/* Start tab */
TmlNot_POST = 10, // Post written directly in timeline
/* Institution tab */
TmlNot_INS_DOC_PUB_FILE = 1, // Public file in documents of institution
TmlNot_INS_SHA_PUB_FILE = 2, // Public file in shared files of institution
/* Center tab */
TmlNot_CTR_DOC_PUB_FILE = 3, // Public file in documents of center
TmlNot_CTR_SHA_PUB_FILE = 4, // Public file in shared files of center
/* Degree tab */
TmlNot_DEG_DOC_PUB_FILE = 5, // Public file in documents of degree
TmlNot_DEG_SHA_PUB_FILE = 6, // Public file in shared files of degree
/* Course tab */
TmlNot_CRS_DOC_PUB_FILE = 7, // Public file in documents of course
TmlNot_CRS_SHA_PUB_FILE = 8, // Public file in shared files of course
/* Assessment tab */
TmlNot_CALL_FOR_EXAM = 9, // Call for exam in a course
/* Users tab */
/* Messages tab */
TmlNot_NOTICE = 12, // A public notice in a course
TmlNot_FORUM_POST = 11, // Post in global/swad forums
/* Analytics tab */
/* Profile tab */
} TmlNot_Type_t;
62. Timeline: Notes & Pubs
struct TmlNot_Note
{
long NotCod; // Unique code/identifier for each note
TmlNot_Type_t Type; // Timeline post, public file,
// call for exam, notice, forum post...
long UsrCod; // Publisher
long HieCod; // Hierarchy code
// (institution/center/degree/course)
long Cod; // Code of file, forum post,
// notice, timeline post...
bool Unavailable; // File, forum post, notice...
// unavailable (removed)
time_t DateTimeUTC; // Date-time of publication in UTC time
unsigned NumShared; // Number of times (users)
// this note has been shared
unsigned NumFavs; // Number of times (users)
// this note has been favourited
};
63. Timeline: Notes & Pubs
__________________
|@author |
| Note |
|__________________|
|@author |
| Comment 1 |
|______________|
|@author |
| Comment 2 |
|______________|
| |
| ... |
|______________|
|@author |
| Comment n |
|______________|
●
A note can have comments attached to it:
68. Timeline: Database
mysql> DESCRIBE tml_pubs;
+--------------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+----------+------+-----+---------+----------------+
| PubCod | bigint | NO | PRI | NULL | auto_increment |
| NotCod | bigint | NO | MUL | NULL | |
| PublisherCod | int | NO | MUL | NULL | |
| PubType | tinyint | NO | MUL | NULL | |
| TimePublish | datetime | NO | MUL | NULL | |
+--------------+----------+------+-----+---------+----------------+
69. Timeline: Database
mysql> DESCRIBE tml_notes;
+-------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------+------+-----+---------+----------------+
| NotCod | bigint | NO | PRI | NULL | auto_increment |
| NoteType | tinyint | NO | MUL | NULL | |
| Cod | int | NO | | -1 | |
| UsrCod | int | NO | MUL | NULL | |
| HieCod | int | NO | | -1 | |
| Unavailable | enum('N','Y') | NO | | N | |
| TimeNote | datetime | NO | MUL | NULL | |
+-------------+---------------+------+-----+---------+----------------+
mysql> DESCRIBE tml_notes_fav;
+---------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+----------+------+-----+---------+----------------+
| FavCod | bigint | NO | PRI | NULL | auto_increment |
| NotCod | bigint | NO | MUL | NULL | |
| UsrCod | int | NO | MUL | NULL | |
| TimeFav | datetime | NO | | NULL | |
+---------+----------+------+-----+---------+----------------+
70. Timeline: Database
mysql> DESCRIBE tml_comments;
+--------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+----------+------+-----+---------+-------+
| PubCod | bigint | NO | PRI | NULL | |
| Txt | longtext | NO | MUL | NULL | |
| MedCod | int | NO | MUL | -1 | |
+--------+----------+------+-----+---------+-------+
mysql> DESCRIBE tml_comments_fav;
+---------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+----------+------+-----+---------+----------------+
| FavCod | bigint | NO | PRI | NULL | auto_increment |
| PubCod | bigint | NO | MUL | NULL | |
| UsrCod | int | NO | MUL | NULL | |
| TimeFav | datetime | NO | | NULL | |
+---------+----------+------+-----+---------+----------------+
71. Timeline: Database
mysql> DESCRIBE tml_posts;
+--------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------+----------+------+-----+---------+----------------+
| PstCod | int | NO | PRI | NULL | auto_increment |
| Txt | longtext | NO | MUL | NULL | |
| MedCod | int | NO | MUL | -1 | |
+--------+----------+------+-----+---------+----------------+
mysql> DESCRIBE cfe_exams;
+-------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------+------+-----+---------+----------------+
| ExaCod | int | NO | PRI | NULL | auto_increment |
| ... | ... | ... | ... | ... | ... |
mysql> DESCRIBE brw_files;
+-----------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+---------------+------+-----+---------+----------------+
| FilCod | int | NO | PRI | NULL | auto_increment |
| ... | ... | ... | ... | ... | ... |
mysql> DESCRIBE not_notices;
+-----------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+----------+------+-----+---------+----------------+
| NotCod | int | NO | PRI | NULL | auto_increment |
| ... | ... | ... | ... | ... | ... |
mysql> DESCRIBE for_posts;
+-----------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+----------+------+-----+---------+----------------+
| PstCod | int | NO | PRI | NULL | auto_increment |
| ... | ... | ... | ... | ... | ... |
72. Timeline: Database
mysql> DESCRIBE tml_timelines;
+-----------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+----------+------+-----+---------+-------+
| SessionId | char(43) | NO | PRI | NULL | |
| NotCod | bigint | NO | PRI | NULL | |
+-----------+----------+------+-----+---------+-------+
mysql> DESCRIBE tml_tmp_timeline;
+--------+--------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+--------+------+-----+---------+-------+
| NotCod | bigint | NO | PRI | NULL | NULL |
+--------+--------+------+-----+---------+-------+
What is being displayed on the screens of each of the users
What is being displayed on the current user's screen
73. Timeline: Database
●
Example of
database query:
unsigned TmlDB_GetPubDataByCod (long PubCod,
MYSQL_RES **mysql_res) {
return (unsigned)
DB_QuerySELECT (mysql_res,
"can not get data of publication",
"SELECT PubCod," // row[0]
"NotCod," // row[1]
"PublisherCod," // row[2]
"PubType" // row[3]
" FROM tml_pubs"
" WHERE PubCod=%ld",
PubCod);
}
void TmlPub_GetPubDataFromRow (MYSQL_RES *mysql_res,
struct TmlPub_Publication *Pub) {
MYSQL_ROW row;
row = mysql_fetch_row (mysql_res);
Pub->PubCod = Str_ConvertStrCodToLongCod (row[0]);
Pub->NotCod = Str_ConvertStrCodToLongCod (row[1]);
Pub->PublisherCod = Str_ConvertStrCodToLongCod (row[2]);
Pub->Type = TmlPub_GetPubTypeFromStr (row[3]);
}
74. Timeline: Database
unsigned long DB_QuerySELECT (MYSQL_RES **mysql_res,
const char *MsgError,
const char *fmt,...) {
va_list ap;
int NumBytesPrinted;
char *Query;
/***** Create query string *****/
va_start (ap,fmt);
NumBytesPrinted = vasprintf (&Query,fmt,ap);
va_end (ap);
if (NumBytesPrinted < 0) // -1 if no memory or any other error
Err_NotEnoughMemoryExit ();
/***** Do SELECT query *****/
return DB_QuerySELECTusingQueryStr (Query,mysql_res,MsgError);
}
static unsigned long DB_QuerySELECTusingQueryStr (char *Query,
MYSQL_RES **mysql_res,
const char *MsgError) {
int Result;
[...]
Result = mysql_query (&DB_Database.mysql,Query); // 0 on success
free (Query);
if (Result)
DB_ExitOnMySQLError (MsgError);
if ((*mysql_res = mysql_store_result (&DB_Database.mysql)) == NULL)
DB_ExitOnMySQLError (MsgError);
return (unsigned long) mysql_num_rows (*mysql_res);
}
75. Timeline: Getting pubs
“When I wrote this code, only God and I understood
what I did. Now only God knows.”
Unknown source
76. Timeline: Getting pubs
●
Our algorithm:
●
Select publications one by one in a loop
●
In each iteration:
●
Get the most recent publication (original, shared or comment)
checking that its note is not already retrieved
●
After getting a publication, save its note code to not get it again.
SELECT PubCod
FROM tml_pubs
WHERE NotCod NOT IN
(SELECT NotCod
FROM tml_tmp_timeline)
ORDER BY PubCod DESC
LIMIT 1;
77. Timeline: Getting pubs
●
Slower alternative (may need seconds for large tables):
●
Get the maximum PubCod, i.e more recent publication (original, shared or commment),
of every set of publications corresponding to the same note:
SELECT MAX(PubCod) AS NewestPubCod
FROM tml_pubs
GROUP BY NotCod
ORDER BY NewestPubCod DESC
LIMIT 10;
78. Timeline: Getting pubs
●
Restricting publications to mine and those I follow:
CREATE TEMPORARY TABLE fol_tmp_me_and_followed
(UsrCod INT NOT NULL,
UNIQUE INDEX(UsrCod)) ENGINE=MEMORY
SELECT my_usr_cod AS UsrCod
UNION
SELECT FollowedCod AS UsrCod
FROM usr_follow
WHERE FollowerCod=my_usr_cod;
------------------------------
SELECT tml_pubs.PubCod,
tml_pubs.NotCod,
tml_pubs.PublisherCod,
tml_pubs.PubType
FROM tml_pubs,
fol_tmp_me_and_followed
WHERE tml_pubs.PublisherCod=fol_tmp_me_and_followed.UsrCod
AND tml_pubs.NotCod NOT IN
(SELECT NotCod
FROM tml_tmp_timeline)
ORDER BY PubCod DESC
LIMIT 1;
mysql> DESCRIBE usr_follow;
+-------------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+----------+------+-----+---------+-------+
| FollowerCod | int | NO | PRI | NULL | |
| FollowedCod | int | NO | PRI | NULL | |
| FollowTime | datetime | NO | MUL | NULL | |
+-------------+----------+------+-----+---------+-------+
79. Timeline: Getting pubs
●
Three types of timeline updates
_ ______________________
/ |______________________|
New < |______________________|
_|______________________|
_|_See_new_activity_(3)_|
/ |______________________|
| |______________________|
Recent < |______________________|
| |______________________|
_|______________________|
_|_______See_more_______|
/ |______________________|
| |______________________|
Old < |______________________|
| |______________________|
_|______________________|
typedef enum
{
Tml_GET_NEW_PUBS, // automatically
// from time
// to time (AJAX)
Tml_GET_REC_PUBS, // user clicks
// on menu
// or after
// editing
// timeline
Tml_GET_OLD_PUBS, // user clicks
// on bottom
// link (AJAX)
} Tml_WhatToGet_t;
81. Timeline: Getting pubs
case Tml_GET_REC_PUBS: // Get some limited recent publications
/* First query to get initial timeline shown
==> no notes yet in current timeline table */
RangePubsToGet->Top = 0;
/* _ _____ 0 <-- RangePubsToGet.Top = +infinite
/ |_____| 8
Get | |_____| 7
pubs < |_____| 6
from | |_____| 5
all | |_____| 4
range . |_____| 3
. |_____| 2
. |_____| 1
0 <-- RangePubsToGet.Bottom = -infinite */
RangePubsToGet->Bottom = 0;
82. Timeline: Getting pubs
case Tml_GET_NEW_PUBS: // Get the publications (without limit)
// newer than last pub. code
/* Via AJAX automatically from time to time */
RangePubsToGet->Top = 0;
/* _ _____ 0 <-- RangePubsToGet.Top = +infinite
Get / |_____|11
these < |_____|10
pubs _|_____| 9
/ |_____| 8 <-- RangePubsToGet.Bottom = last pub. code
Pubs | |_____| 7
already < |_____| 6
shown | |_____| 5
| |_____| 4
. |_____| .
. |_____| .
. |_____| .
*/
RangePubsToGet->Bottom = Tml_DB_GetPubCodFromSession (Tml_Pub_LAST);
83. Timeline: Getting pubs
case Tml_GET_OLD_PUBS: // Get some limited publications
// older than first pub. code
/* Via AJAX when I click in link to get old publications */
RangePubsToGet->Top = Tml_DB_GetPubCodFromSession (Tml_Pub_FIRST);
/* _____
. |_____| .
. |_____| .
. |_____| .
Pubs | |_____| 8
already < |_____| 7
shown | |_____| 6
| |_____| 5
Get _|_____| 4 <-- RangePubsToGet.Top = first pub. code
pubs / |_____| 3
from < |_____| 2
this _|_____| 1
rage 0 <-- RangePubsToGet.Bottom = -infinite */
RangePubsToGet->Bottom = 0;
84. Timeline: Getting pubs
●
Restricting publications to range:
SELECT tml_pubs.PubCod,
tml_pubs.NotCod,
tml_pubs.PublisherCod,
tml_pubs.PubType
FROM tml_pubs,
fol_tmp_me_and_followed
WHERE tml_pubs.PublisherCod=fol_tmp_me_and_followed.UsrCod
AND tml_pubs.PubCod>bottom // if type == Tml_GET_NEW_PUBS
AND tml_pubs.PubCod<top // if type == Tml_GET_OLD_PUBS
AND tml_pubs.NotCod NOT IN
(SELECT NotCod
FROM tml_tmp_timeline)
ORDER BY PubCod DESC
LIMIT 1;
85. Timeline: Getting pubs
Timeline->Pubs.Top Pub #0
______ ______ Pub #1
|______|------>|______| ______ Pub #2
|______| -> |______| ______ Pub #3
|______| / |______| ->|______| ______
|______| / |______| / |______| ->|______|
|_Next_|-- |______| / |______| // |______|
more recent |_Next_|-- |______| // |______|
______ |_Next_|--/ |______|
|______|---------------------------------------------- |_NULL_|
older
Timeline->Pubs.Bottom
●
After getting the publications, the result is a chained list:
87. Timeline: Showing pubs
_____
/ |_____| just_now_timeline_list (Posts retrieved automatically
| |_____| via AJAX from time to time.
| |_____| They are transferred inmediately
| | to new_timeline_list.)
Hidden < __v__
| |_____| new_timeline_list (Posts retrieved but hidden.
| |_____| When user clicks to view them,
| |_____| the most recent of each note
|_____| is transferred
| to visible timeline_list.)
See new activity (0)
__v__
/ |_____| timeline_list (Posts visible on page)
| |_____|
Visible | |_____|
on < |_____|
page | |_____|
| |_____|
|_____|
^
See more
__|__
/ |_____| old_timeline_list (Posts just retrieved via AJAX
| |_____| when user clicks "see more".
| |_____| They are transferred inmediately
Hidden < |_____| to timeline_list.)
| |_____|
| |_____|
|_____|
<ul id="just_now_timeline_list" ...>
</ul>
<ul id="new_timeline_list" ...>
</ul>
<div id="view_new_container" ...
style="display:none;">
<a href="" ...
onclick="moveNewTimelineToTimeline();
return false;">
See new activity
(<span id="view_new_count">
0
</span>)
</a>
</div>
<ul id="timeline_list" ...>
visible timeline
</ul>
<div id="view_old_container" ...>
<a href="" ...
onclick="...
refreshOldTimeline();
return false;">
...
See more
</a>
</div>
<ul id="old_timeline_list" ...>
</ul>
88. Timeline: Showing pubs
<head>
...
<script type="text/javascript" ...>
var delayNewTml = Cfg_TIME_TO_REFRESH_TIMELINE; // 2000 ms
function init() {
ActionAJAX = "SWAD_URL";
...
setTimeout('refreshNewTimeline()',delayNewTL);
...
}
</script>
<script type="text/javascript" ...>
var refreshParamIdSes = "ses=...";
var refreshParamNxtActNewPub = "act=...";
var refreshParamWho = "Who=...";
</script>
...
</head>
<body onload="init();">
...
</body>
●
Automatic refresh via AJAX every 2 s → 3 s → 4 s...
_____
/ |_____| just_now_timeline_list
| |_____|
| |_____|
| |
Hidden < __v__
| |_____| new_timeline_list
| |_____|
| |_____|
|_____|
|
See new activity (0)
__v__
/ |_____| timeline_list
| |_____|
Visible | |_____|
on < |_____|
page | |_____|
| |_____|
|_____|
^
See more
__|__
/ |_____| old_timeline_list
| |_____|
| |_____|
Hidden < |_____|
| |_____|
| |_____|
|_____|
90. Timeline: Showing pubs
function readNewTimelineData () {
if (objXMLHttpReqNewTml.readyState == 4) // Check if data have been received
if (objXMLHttpReqNewTml.status == 200) {
// Access to UL for just now timeline
var justNowTimeline = document.getElementById('just_now_timeline_list');
if (justNowTimeline) {
// Update list of publications in just now timeline
justNowTimeline.innerHTML = objXMLHttpReqNewTml.responseText;
var numNotesJustGot = justNowTimeline.childNodes.length;
if (numNotesJustGot) {// New notes received
// Scripts in timeline got via AJAX not executed ==> execute them
evalScriptsInElem (justNowTimeline);
// Process maths
MathJax.typeset();
...
_____
/ |_____| just_now_timeline_list
| |_____|
| |_____|
| |
Hidden < __v__
| |_____| new_timeline_list
| |_____|
| |_____|
|_____|
|
See new activity (0)
__v__
/ |_____| timeline_list
| |_____|
Visible | |_____|
on < |_____|
page | |_____|
| |_____|
|_____|
^
See more
__|__
/ |_____| old_timeline_list
| |_____|
| |_____|
Hidden < |_____|
| |_____|
| |_____|
|_____|
91. Timeline: Showing pubs
...
// Move all the LI elements (notes) in UL 'just_now_timeline_list'
// ...to the top of UL 'new_timeline_list'
var newTimeline = document.getElementById('new_timeline_list');
for (var i=0; i<numNotesJustGot; i++) {
// Move node from just now timeline to new timeline
newTimeline.insertBefore(justNowTimeline.lastChild,
newTimeline.firstChild);
newTimeline.firstChild.className += " Tml_NEW_PUB";
}
// Update number of notes in new timeline
var viewNewCount = document.getElementById('view_new_count');
viewNewCount.innerHTML = newTimeline.childNodes.length;
// Unhide message with number of notes if hidden
var viewNewContainer = document.getElementById('view_new_container');
viewNewContainer.style.display = '';
}
}
// Global delay variable is set initially in swad-core
delayNewTml += 1000; // Increase one second on each call
setTimeout('refreshNewTimeline()',delayNewTml);
}
}
_____
/ |_____| just_now_timeline_list
| |_____|
| |_____|
| |
Hidden < __v__
| |_____| new_timeline_list
| |_____|
| |_____|
|_____|
|
See new activity (0)
__v__
/ |_____| timeline_list
| |_____|
Visible | |_____|
on < |_____|
page | |_____|
| |_____|
|_____|
^
See more
__|__
/ |_____| old_timeline_list
| |_____|
| |_____|
Hidden < |_____|
| |_____|
| |_____|
|_____|
92. _____
/ |_____| just_now_timeline_list
| |_____|
| |_____|
| |
Hidden < __v__
| |_____| new_timeline_list
| |_____|
| |_____|
|_____|
|
See new activity (0)
__v__
/ |_____| timeline_list
| |_____|
Visible | |_____|
on < |_____|
page | |_____|
| |_____|
|_____|
^
See more
__|__
/ |_____| old_timeline_list
| |_____|
| |_____|
Hidden < |_____|
| |_____|
| |_____|
|_____|
Timeline: Showing pubs
function moveNewTimelineToTimeline () {
// Move the LI elements (notes) in UL 'new_timeline_list'...
// ...to the top of UL 'timeline_list', only if not repeated before
var newTimeline = document.getElementById('new_timeline_list');
var numNewNotes = newTimeline.childNodes.length;
if (numNewNotes) {
var timeline = document.getElementById("timeline_list");
for (var i=1; i<=numNewNotes; i++) {
// Check if the last child (the oldest) in the new timeline...
// ...is the last ocurrence of the note
var mostRecentOcurrenceOfNote = true;
var lastChildIndex = numNewNotes - i;
var noteCode = newTimeline.lastChild.dataset.noteCode;
for (var j=0; j<lastChildIndex; j++)
if (newTimeline.childNodes[j].dataset.noteCode == noteCode) {
mostRecentOcurrenceOfNote = false;
break;
}
...
●
User clicks "See new activity" → View new pubs.
93. Timeline: Showing pubs
...
// Move or remove node from new timeline
if (mostRecentOcurrenceOfNote) {
// Move node from new timeline to timeline
timeline.insertBefore(newTimeline.lastChild,timeline.firstChild);
timeline.firstChild.className += " Tml_NEW_PUB";
}
else
// Remove last child (because is repeated in more recent pubs)
newTimeline.removeChild(newTimeline.lastChild);
}
}
// Reset number of new publications after moving
var viewNewCount = document.getElementById('view_new_count');
viewNewCount.innerHTML = 0;
// Hide link to view new publications after moving
var viewNewContainer = document.getElementById('view_new_container');
viewNewContainer.style.display = 'none';
}
_____
/ |_____| just_now_timeline_list
| |_____|
| |_____|
| |
Hidden < __v__
| |_____| new_timeline_list
| |_____|
| |_____|
|_____|
|
See new activity (0)
__v__
/ |_____| timeline_list
| |_____|
Visible | |_____|
on < |_____|
page | |_____|
| |_____|
|_____|
^
See more
__|__
/ |_____| old_timeline_list
| |_____|
| |_____|
Hidden < |_____|
| |_____|
| |_____|
|_____|
94. _____
/ |_____| just_now_timeline_list
| |_____|
| |_____|
| |
Hidden < __v__
| |_____| new_timeline_list
| |_____|
| |_____|
|_____|
|
See new activity (0)
__v__
/ |_____| timeline_list
| |_____|
Visible | |_____|
on < |_____|
page | |_____|
| |_____|
|_____|
^
See more
__|__
/ |_____| old_timeline_list
| |_____|
| |_____|
Hidden < |_____|
| |_____|
| |_____|
|_____|
Timeline: Showing pubs
var objXMLHttpReqOldTml = false;
function refreshOldTimeline () {
objXMLHttpReqOldTml = AJAXCreateObject (); // new XMLHttpRequest()
if (objXMLHttpReqOldTml) {
var refreshParams = refreshParamNxtActOldPub + '&' +
refreshParamIdSes;
if (typeof refreshParamUsr !== 'undefined') {
if (refreshParamUsr.length)
refreshParams += '&' + refreshParamUsr;
}
if (typeof refreshParamWho !== 'undefined') {
if (refreshParamWho.length)
refreshParams += '&' + refreshParamWho;
}
objXMLHttpReqOldTml.onreadystatechange = readOldTimelineData;
objXMLHttpReqOldTml.open('POST',actionAJAX,true);
objXMLHttpReqOldTml.setRequestHeader('Content-Type',
'application/x-www-form-urlencoded');
objXMLHttpReqOldTml.send(refreshParams);
}
}
●
User clicks "See more..." → View old pubs.
95. Timeline: Showing pubs
function readOldTimelineData () {
if (objXMLHttpReqOldTml.readyState == 4) // Check if data have been received
if (objXMLHttpReqOldTml.status == 200) {
// Access to UL with the old timeline
var oldTimeline = document.getElementById('old_timeline_list');
if (oldTimeline) {
// Fill list of publications in old timeline
oldTimeline.innerHTML = objXMLHttpReqOldTml.responseText;
var countOldTimeline = oldTimeline.childNodes.length;
if (countOldTimeline) {
// Scripts in timeline got via AJAX not executed ==> execute them
evalScriptsInElem (oldTimeline);
// Process maths
MathJax.typeset();
// Move all elements in 'old_timeline_list to bottom of 'timeline_list'
var timeline = document.getElementById("timeline_list");
for (var i=0; i<countOldTimeline; i++)
timeline.appendChild(oldTimeline.firstChild);
}
else // No old publications retrieved, so we have reached the oldest pub.
// Hide container with link to get old publications
document.getElementById("view_old_pubs_container").style.display = 'none';
}
}
}
_____
/ |_____| just_now_timeline_list
| |_____|
| |_____|
| |
Hidden < __v__
| |_____| new_timeline_list
| |_____|
| |_____|
|_____|
|
See new activity (0)
__v__
/ |_____| timeline_list
| |_____|
Visible | |_____|
on < |_____|
page | |_____|
| |_____|
|_____|
^
See more
__|__
/ |_____| old_timeline_list
| |_____|
| |_____|
Hidden < |_____|
| |_____|
| |_____|
|_____|
96. Timeline: Layout
“If you find an element of your interface requires
instructions, then you need to redesign it.”
Dan Rubin – Designer, photographer
109. Timeline: Layout
function updateDivFaversSharers (form,Params) {
var id = form.parentNode.parentNode.id;
var objXMLHttp = AJAXCreateObject ();
if (objXMLHttp) {
/* Send request to server */
objXMLHttp.onreadystatechange = function () {
if (objXMLHttp.readyState == 4) // Check if data have been received
if (objXMLHttp.status == 200)
if (id) {
var div = document.getElementById (id); // Access to DIV
if (div)
div.innerHTML = objXMLHttp.responseText; // Update DIV content
}
};
objXMLHttp.open ('POST',actionAJAX,true);
objXMLHttp.setRequestHeader ('Content-Type','application/x-www-form-urlencoded');
objXMLHttp.send (Params);
}
}
127. Timeline: Inserting links
“Some of the best programming is done on paper.
Putting it into the computer is just a minor detail.”
Max Kanat-Alexander – Software Engineer at Google, author of Code Simplicity
128. Timeline: Inserting links
●
1. Parse string creating a list of links (URLs or nicknames)
●
Hi @admin, can I use https://openswad.org for free?
______ ______ ______ ______
|______|<-- -->|______|<-- -->|______|<-- -->|______|<--- LastLink
|______| / |______| / |______| / |______|
|______| / |______| / |______| / |______|
|_NULL_| / ---|_Prev_| / ---|_Prev_| / ---|_Prev_|
|_Next_|-- |_Next_|-- |_Next_|-- |_NULL_|
struct ALn_Link
{
ALn_LinkType_t Type; // URL or nickname?
struct ALn_Substring URLorNick; // Link text
struct ALn_Substring NickAnchor[3]; // Pointer to anchors if nick
size_t LengthAddedUpToHere; // Total length of extra HTML code...
// ...added up to this link (included)
struct ALn_Link *Prev; // Pointer to previous link
struct ALn_Link *Next; // Pointer to next link
};
struct ALn_Substring
{
// Pointer to
// the first
// char of
// substring
char *Str;
// Length of
// the substring
size_t Len;
};
129. Timeline: Inserting links
●
2. For each link in the list, going back from last to first:
●
1 Move forward the text after the link
●
2 Copy the 3rd part of the anchor
●
3 Move forward the link
_______________________________
|H|i|_|@|a|d|m|i|n|,|_|c|a|n|...|
| | | | | | | | | | | | | | |
| | | | | | | | | ___________________________________________1____
| | |
| | | ______________________________3____ | | | | | |
| | | | | | | | |
| | | ______5____ | | | | | | | | | | | |
| | | | | | | | | | | | | | |
| | | 6 | | | | | | 4 | | | | | | 2 | | | | | |
v v v anchor#1 v v v v v v anchor#2 v v v v v v anchor#3 v v v v v v
___________________________________________________________________________
|H|i|_|<|_|_|_|_|@|a|d|m|i|n|_|_|_|_|>|@|a|d|m|i|n|<|_|_|_|_|>|,|_|c|a|n|...|
●
4 Copy the 2nd part of the anchor
●
5 Copy the link into the anchor
●
6 Copy the 1st part of the anchor
130. Miss: Written in C
“The C language combines
all the power of assembly language
with all the ease-of-use of assembly language.”
Mark Pearce – Freelance consultant and developer
131. Miss: Written in C
●
swad-core written from scratch in C
✘Disadvantages:
●
lack of specialized library functions for the web
●
rejected by potential collaborators who see it as an old programming
language
132. Hit: Written in C
“The C language combines
all the power of assembly language
with all the ease-of-use of assembly language.”
Mark Pearce – Freelance consultant and developer
133. Hit: Written in C
●
swad-core written from scratch in C
Advantages:
●
It requires few resources
●
+ speed
●
- memory
●
swad-core executable: 3.2 MB
●
Functional even in a Raspberry Pi
●
Reliability
●
Compile-time error detection
●
It works 24 hours, fast and almost without failures
●
Source code stability over time
134. Hit: It's fast, you don't
need a cluster
“Software is getting slower more rapidly than
hardware is becoming faster.” (Wirth's law)
Niklaus Wirth – Designer of several programming languages, including Pascal
135. ●
SWAD-UGR former servers (1999-2016)
2nd: 2004-2006
Pentium 4 HT
RAM 2 GiB
2 HD 160 GB
Fedora 3
3rd: 2007-2008
Core 2 Duo
RAM 4 GiB
2 HD 500 GB
Fedora 6
4th: 2009-2010
Core 2 Quad
RAM 4 GiB
2 HD 146 GB
2 HD 1 TB
Fedora 10
5th: 2011-2016
2 Xeon Quad
RAM 24 GiB
4 HD 146 GB
4 HD 500 GB
CentOS 5.7
1st: 1999-2003
Shared server
Hit: It's fast, you don't need a cluster
136. Hit: It's fast, you don't need a cluster
●
SWAD-UGR current server (6th: 2016-2023)
●
HP Proliant DL160 G9, 2 Xeon with 6 cores, RAM 32 GiB
4 HD 146 GB
SAS 15000 rpm
RAID 1+0 (292 GB)
SO CentOS 7.2
MySQL database
4 HD 1 TB
SAS 7200 rpm
RAID 5 (3 TB)
Web files
( /var/www )