/* Copyright (C) 1998-2003 XIAO, Gang of Universite de Nice - Sophia Antipolis
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* student score management */
double oldfactor=0.85; /* quality factor, should remain stable. */
/* User score information of an exercise. Size: 16 bytes. */
typedef struct scoredata {
unsigned short int num, new, try, hint;
float user, user2;
} scoredata;
struct scoreheader {
char raf[8][20];
int sheet, exo;
char session[32];
} scoreheader;
struct scoredata uscore[MAX_CLASSEXOS];
struct scoreresult tscore[MAX_CLASSEXOS];
#define oldraf scoreheader.raf
#define oldsheet scoreheader.sheet
#define oldexo scoreheader.exo
#define oldsession scoreheader.session
/* one line of score. */
void scoreline(struct classdata *cd, char *l)
{
int i,sheet,exo,num;
char *pm[16];
struct scoredata *thiscore;
float score;
i=cutwords(l,pm,8); if(i<6) return;
if(strcmp(pm
[i
-1],"noscore")==0 || strcmp(pm
[i
-1],"erased")==0) {
if(strcmp(pm
[1],oldsession
)!=0)
mystrncpy(oldsession,pm[1],sizeof(oldsession));
return;
}
if(sheet<=0 || sheet>MAX_SHEETS || exo<=0 || exo>MAX_EXOS) return;
num=search_data(cd->exos,cd->exocnt,sizeof(exodata),((sheet-1)<<8)+(exo-1));
if(num<0) return;
thiscore=uscore+num;
if(strcmp(pm
[4],"score")==0) {
score
=atof(pm
[5]); if(!finite
(score
)) score
=0;
if(score>10) score=10; if(score<-10) score=-10;
if(strcmp(pm
[1],oldsession
)==0 && /* measure to prohibit simultaneous scoring. */
sheet==oldsheet && exo==oldexo &&
strncmp(pm
[0],oldraf
[6],13)!=0 /* prohit scores immediately after rafale */
) {
thiscore->user+=score;
thiscore->user2*=oldfactor;
thiscore->user2+=score;
if(thiscore->try<60000) thiscore->try++;
oldsheet=oldexo=0;
}
}
else {
if(strcmp(pm
[4],"rafale")==0) { /* rafale punishment */
if(strncmp(pm
[0],oldraf
[3],13)==0 && thiscore
->new
<60000) thiscore
->new
++;
memmove(oldraf
[1],oldraf
[0],sizeof(oldraf
[0])*7);
mystrncpy(oldraf[0],pm[0],sizeof(oldraf[0]));
}
if(strcmp(pm
[4],"resume")!=0 && strcmp(pm
[4],"rafale")!=0) {
if(strcmp(pm
[4],"hint")==0) thiscore
->hint
++;
else if(thiscore->new<60000) thiscore->new++;
}
mystrncpy(oldsession,pm[1],sizeof(oldsession));
oldsheet=sheet; oldexo=exo;
}
}
unsigned int _cuttime(char ends[], char starts[], unsigned int startn)
{
int h1,h2,m1,m2,s2, t;
if(ends[0]==0) return 0;
if(strncmp(ends
,starts
,14)<0) return 10;
if(strncmp(ends
,starts
,8)>0) return 0;
t=((h1-h2)*60+(m1-m2))*60-s2;
return startn+t;
}
/* Gather exam score. */
void examscorecalc(struct classdata *cd, char *uname)
{
struct scoredata *thiscore;
char nbuf[MAX_FNAME+1];
char cuttimes[MAX_EXOS][16];
char rbuf[MAX_FILELEN+1];
char *wlist[8];
char *p1, *p2;
int i, k, ecnt, num;
double ss, sc[MAX_EXOS], sc2[MAX_EXOS];
int ind[MAX_EXOS];
unsigned int tr[MAX_EXOS], all[MAX_EXOS], ver[MAX_EXOS], start[MAX_EXOS], dure[MAX_EXOS];
char *ip[MAX_EXOS], *ses[MAX_EXOS];
unsigned int start1, endtime[MAX_EXOS];
signed int dure1;
ecnt=cd->examcnt; if(ecnt<=0) return; if(ecnt>MAX_EXOS) ecnt=MAX_EXOS;
for(i=0;i<MAX_EXOS;i++) ind[i]=-1;
for(i=0;i<ecnt;i++) {
k=((cd->exos[i+cd->examstart].num)&255);
all[k]=cd->exos[i+cd->examstart].require;
ind[k]=i+cd->examstart;
}
memset(tr
,0,sizeof(tr
)); memset(cuttimes
,0,sizeof(cuttimes
));
memset(endtime
,0,sizeof(endtime
));
snprintf(nbuf
,sizeof(nbuf
),"score/%s.exam",uname
);
readfile(nbuf,rbuf,sizeof(rbuf));
if(rbuf[0]==0) goto end;
for(p1=rbuf; p1!=NULL && *p1; p1=p2) {
p2
=strchr(p1
,'\n'); if(p2
!=NULL
) *p2
++=0;
i=cutwords(find_word_start(p1),wlist,7);
if(i<6) continue;
i
=atoi(wlist
[0])-1; if(i
<0 || i
>=ecnt
) continue;
dure1
=atoi(wlist
[2]); start1
=atoi(wlist
[3]);
if(strcmp(wlist
[1],"--")==0) { /* session closure */
start[i]=dure[i]=0; ip[i]=ses[i]="";
continue;
}
if(strcmp(wlist
[1],"00")==0) {
if(sc2[i]<sc[i]) sc2[i]=sc[i];
ver[i]=1; tr[i]++; start[i]=start1; dure[i]=dure1; sc[i]=0;
ip[i]=wlist[4]; ses[i]=wlist[5];
if(tr[i]==1 && wlist[6]!=NULL) {
char *pp1, *pp2, lbuf[CTBUFLEN];
if(cd->ctptr[ind[i]]>=0)
mystrncpy(lbuf,cd->ctbuf+cd->ctptr[ind[i]],sizeof(lbuf));
else lbuf[0]=0;
if(lbuf[0]) {
for(pp1=find_word_start(lbuf); *pp1; pp1=find_word_start(pp2)) {
pp2=find_word_end(pp1); if(pp2-pp1!=14) continue;
if(*pp2) *pp2++=0;
pp1[8]='.'; pp1[11]=':';
if(strcmp(pp1
,wlist
[6])<0) continue;
memmove(cuttimes
[i
],pp1
,15); break;
}
}
}
endtime[i]=_cuttime(cuttimes[i],wlist[6],start1);
}
else if(ver[i]==0) tr[i]++;
if(tr[i]>all[i]) continue;
ss
=atof(wlist
[1]); if(ss
<=0) continue; if(ss
>10) ss
=10;
if(ss!=sc[i] && (dure1>=0 || /* checking conditions */
(start1-start[i]<dure[i]*60 &&
dure[i]>0 && dure[i]<4096 &&
*ses[i]!=0 && *ip[i]!=0 &&
start[i]!=0 && start1>start[i] &&
(endtime[i]==0 || endtime[i]>=start1) &&
sc[i]=ss;
}
end:
for(i=0; i<ecnt; i++) {
if(sc2[i]<sc[i]) sc2[i]=sc[i];
num=search_data(cd->exos,cd->exocnt,sizeof(exodata),0xFF00+i);
if(num<0) continue;
thiscore=uscore+num;
thiscore->user=sc2[i];
thiscore->try=tr[i];
if(cuttimes
[i
][0] && strncmp(cuttimes
[i
],nowstr
,14)<0) k
=0; else k
=1;
thiscore->hint=k;
}
}
/* calculate score from raw data, core routine. */
void rawscorecalc(struct classdata *cd, char *uname)
{
int i;
char fbuf[MAX_FILELEN+1];
char *p1, *p2;
char namebuf[MAX_FNAME+1];
memset(uscore
,0,sizeof(uscore
[0])*cd
->exocnt
);
memset(&scoreheader
,0,sizeof(scoreheader
));
for(i=0;i<cd->exocnt;i++) uscore[i].num=cd->exos[i].num;
snprintf(namebuf
,sizeof(namebuf
),"score/%s",uname
);
readfile(namebuf,fbuf,sizeof(fbuf));
if(fbuf[0]!=0) {
oldsession[0]=oldsheet=oldexo=0;
for(p1=fbuf; *p1; p1=p2) {
if(myisdigit(*p1)) scoreline(cd,p1);
}
}
examscorecalc(cd,uname);
}
void savescorebin(struct classdata *cd, char *uname)
{
int fd, cnt;
char fname[MAX_FNAME+1];
snprintf(fname
,sizeof(fname
),"score/%s.bin",uname
);
cnt=cd->exocnt;
fd=creat(fname,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
if(fd==-1) return;
write(fd,&scoreheader,sizeof(scoreheader));
write(fd,uscore,sizeof(uscore[0])*cnt);
close(fd);
}
void readscorebin(char *fname,int cnt)
{
int fd;
fd=open(fname,O_RDONLY);
if(fd==-1) return;
read(fd,&scoreheader,sizeof(scoreheader));
read(fd,uscore,sizeof(uscore[0])*cnt);
close(fd);
}
void getscore(struct classdata *cd, char *user)
{
struct stat st[3];
int i, cnt, non[3];
char buf[3][MAX_FNAME+1];
snprintf(buf
[0],sizeof(buf
[0]),"score/%s",user
);
snprintf(buf
[1],sizeof(buf
[1]),"score/%s.exam",user
);
snprintf(buf
[2],sizeof(buf
[2]),"score/%s.bin",user
);
cnt=cd->exocnt; if(cnt<=0) return;
for(i=0;i<3;i++) non[i]=stat(buf[i],st+i);
if(non[0] && non[1]) {
memset(uscore
,0,sizeof(uscore
[0])*cnt
);
memset(&scoreheader
,0,sizeof(scoreheader
));
return;
}
if(!non[2] &&
st[2].st_size==sizeof(scoreheader)+sizeof(uscore[0])*cnt &&
(non[0] || st[2].st_mtime>=st[0].st_mtime) &&
st[2].st_mtime>=cd->modif) {
readscorebin(buf[2],cnt);
if(!non[1] && st[2].st_mtime<st[1].st_mtime) {
examscorecalc(cd,user);
savescorebin(cd,user);
}
return;
}
rawscorecalc(cd,user);
savescorebin(cd,user);
}
void cmd_getscore(char *p)
{
struct classdata *cd;
char *cut[4];
int i, sheet, exo, snew, stry, lastsheet, thissheet, thisexo;
double score, score2, quality, tt, ts, thisscore;
if(cwdtype!=dir_class) {
sockerror(2,"getscore_no_class"); return;
}
if(*opt_user==0) {
sockerror(2,"getscore_no_user"); return;
}
cd=getclasscache(opt_class);
if(cd==NULL) {
sockerror(2,"getscore_bad_class"); return;
}
if(cutwords(p,cut,3)==3) {
thissheet
=atoi(cut
[0]); thisexo
=atoi(cut
[1]); thisscore
=atof(cut
[2]);
if(!finite(thisscore)) thisscore=0;
if(thisscore<-10) thisscore=-10;
if(thisscore>10) thisscore=10;
}
else {thissheet=thisexo=thisscore=0;}
getscore(cd,opt_user);
lastsheet=0;
for(i=0;i<cd->exocnt;i++) {
tscore[i].num=cd->exos[i].num;
tscore[i].require=cd->exos[i].require;
tscore[i].weight=cd->exos[i].weight;
sheet=(cd->exos[i].num>>8)+1;
exo=((cd->exos[i].num)&255)+1;
score=uscore[i].user; stry=uscore[i].try; score2=uscore[i].user2;
if(sheet==thissheet && exo==thisexo) {
score+=thisscore; stry++;
score2*=oldfactor; score2+=thisscore;
}
if(sheet==256) {
tscore[i].score=score;
tscore[i].mean=stry*2+uscore[i].hint;
continue;
}
if(score>cd->exos[i].require) score=cd->exos[i].require;
if(score>0 && stry>0) {
snew=uscore[i].new; if(uscore[i].hint>0) snew++;
/* here we give up to 1 time unsuccessful tries.
* Together with a premium of 5 uncounted tries. */
if(snew<stry*2+5) tt=1;
else tt=(double) (snew-4)/(2*stry); /* tt>=1 */
ts
=(1-pow(oldfactor
,stry
))/(1-oldfactor
);
quality=score2/(ts*tt);
}
else score=quality=0;
tscore[i].score=score; tscore[i].mean=quality;
}
answerlen=cd->exocnt*sizeof(tscore[0]);
memmove(textbuf
+3,tscore
,answerlen
);
answerlen+=3;
}
void cmd_scorelog(char *p)
{
struct classdata *cd;
char buf[MAX_LINELEN+1];
if(cwdtype!=dir_class) {
sockerror(2,"scorelog_no_class"); return;
}
if(*opt_user==0) {
sockerror(2,"scorelog_no_user"); return;
}
cd=getclasscache(opt_class);
if(cd==NULL) {
sockerror(2,"scorelog_bad_class"); return;
}
getscore(cd,opt_user);
p=find_word_start(p); strip_trailing_spaces(p);
accessfile(buf,"a","score/%s",opt_user);
if(myisdigit(*p)) scoreline(cd,p);
savescorebin(cd,opt_user);
}