Loading src/webclient/comms.c +48 −43 Original line number Diff line number Diff line Loading @@ -99,9 +99,50 @@ void open_command_socket() INIT_LIST_HEAD(&connlist); } static char * whoinfo = NULL; static time_t whowhen = 0; static int whowant = 0; #define WHOCACHE 120 /* cache who data for this many seconds */ extern int32_t userposn; const char * fetch_who(int immediate) { time_t now = time(NULL); if (whoinfo == NULL || whowhen < (now - WHOCACHE)) { ipc_message_t * msg = ipcmsg_create(IPC_WHOLIST, userposn); ipcmsg_transmit(msg); /* dont want stale, come back later */ if (!immediate) { whowant = 1; return NULL; } } /* no answer we can give, sorry, you will HAVE to come back */ if (whoinfo == NULL) { whowant=1; return NULL; } /* they want an immediate answer, even if its stale */ return whoinfo; } /* we got a message */ static void accept_pipe_cmd(ipc_message_t *ipc, struct person *mesg_user) { if (ipc->head.type == IPC_WHOLIST) { /* keep a copy for later */ if (whoinfo != NULL) free(whoinfo); whoinfo = strdup(ipc->body); whowhen = time(NULL); /* if we want it, fall through, otherwise stop now */ if (whowant != 0) whowant=0; else return; } MESG *msg = malloc(sizeof(MESG)); msg->state = ipc->head.type; //msg->pid = ipc->head.src; Loading Loading @@ -254,7 +295,7 @@ static int send_list(CONNECTION *co) mws_add(buff, "\"state\":%d,", tmp->state); mws_add(buff, "\"serial\":%"PRId64",", tmp->serial); mws_add(buff, "\"when\":%"PRId64",", tmp->when); mws_add(buff, "\"username\":\"%s\",\"text\":\"%s\"}", tmp->user.name, tmp->text); mws_add(buff, "\"name\":\"%s\",\"text\":\"%s\"}", tmp->user.name, tmp->text); if (pos->next != &msglist) { mws_add(buff, ","); } Loading Loading @@ -413,45 +454,9 @@ static int handle_command(CONNECTION *co) return 1; }else if (co->authd && strcasecmp(buff, "who")==0) { struct person u; struct who w; int ufile, wfile; int32_t now = time(NULL); mwstring *line = mws_new(2048); int count=0; wfile = who_open(O_RDONLY); ufile = userdb_open(O_RDONLY); mws_add(line, "["); while (read(wfile, &w, sizeof(w))) { char *realname; char *doing; if (w.posn < 0) continue; lseek(ufile, w.posn, SEEK_SET); read(ufile, &u, sizeof(u)); if (count > 0) mws_add(line, ","); realname = json_escape(u.realname); doing = json_escape(u.doing); mws_add(line, "{"); if (u_god(user->status)) { mws_add(line, "\"realname\":\"%s\",", realname); } mws_add(line, "\"pid\":%d,", w.pid); mws_add(line, "\"username\":\"%s\",", u.name); mws_add(line, "\"channel\":%d,", u.room); mws_add(line, "\"doing\":\"%s\",", doing); mws_add(line, "\"idle\":%d}", now - u.idletime); free(realname); free(doing); count++; } mws_add(line, "]"); send(co->fd, mws_cstr(line), mws_len(line), 0); mws_free(line); const char *line = fetch_who(0); if (line != NULL) send(co->fd, line, strlen(line), 0); return 1; }else if (co->authd && strcasecmp(buff, "whoami")==0) { Loading @@ -459,7 +464,7 @@ static int handle_command(CONNECTION *co) char stats[64]; char *tmp; mws_add(line, "{"); mws_add(line, "\"username\":\"%s\",", user->name); mws_add(line, "\"name\":\"%s\",", user->name); tmp = json_escape(user->realname); mws_add(line, "\"realname\":\"%s\",", tmp); free(tmp); Loading Loading @@ -550,7 +555,7 @@ void talk_rawbcast(const char *fmt, ...) } void create_user(struct person *me, int *userposn, const char *username, const char *password) void create_user(struct person *me, int *uposn, const char *username, const char *password) { struct passwd *pw; char salt[3]; Loading Loading @@ -579,7 +584,7 @@ void create_user(struct person *me, int *userposn, const char *username, const c } else { mwlog("CREATED Auto web user"); } userdb_write(me, userposn); userdb_write(me, uposn); } void mwlog(const char *fmt, ...) Loading src/webclient/comms.h +2 −0 Original line number Diff line number Diff line Loading @@ -7,3 +7,5 @@ char *json_escape(char *original); void close_cmd(void); void talk_rawbcast(const char *fmt, ...) __attribute__((format(printf,1,2))); void create_user(struct person *me, int *userposn, const char *username, const char *password); const char * fetch_who(int immediate); webclient/say.js +28 −28 Original line number Diff line number Diff line Loading @@ -34,7 +34,7 @@ function pageInit() { // make a crude approximation of the old message styles switch (detail.type) { case "say": body = msg[one].username + ": " + detail.text; body = msg[one].name + ": " + detail.text; break; case "raw": body = detail.text; Loading @@ -42,33 +42,33 @@ function pageInit() { case "emote": switch (detail.plural){ case 1: body = msg[one].username + "'s " + detail.text; body = msg[one].name + "'s " + detail.text; break; case 2: body = msg[one].username + "' " + detail.text; body = msg[one].name + "' " + detail.text; break; case 3: body = msg[one].username + "'d " + detail.text; body = msg[one].name + "'d " + detail.text; break; case 4: body = msg[one].username + "'ll " + detail.text; body = msg[one].name + "'ll " + detail.text; break; default: body = msg[one].username + " " + detail.text; body = msg[one].name + " " + detail.text; break; } break; case "notsayto": body = msg[one].username + " says (-" + detail.exclude + "): " + detail.text; body = msg[one].name + " says (-" + detail.exclude + "): " + detail.text; break; case "says": body = msg[one].username + " says: " + detail.text; body = msg[one].name + " says: " + detail.text; break; case "whispers": body = msg[one].username + " whispers: " + detail.text; body = msg[one].name + " whispers: " + detail.text; break; default: body = msg[one].username + " " + detail.text; body = msg[one].name + " " + detail.text; break; } Loading @@ -94,8 +94,8 @@ function pageInit() { } /* Detect username */ if (escapedMsg.substr(0, msg[one].username.length)==msg[one].username) { escapedMsg = escapedMsg.replace(msg[one].username, "<span class='msg_poster'>"+msg[one].username+"</span><span class='msg_content"+withts+"'><span>"); if (escapedMsg.substr(0, msg[one].name.length)==msg[one].name) { escapedMsg = escapedMsg.replace(msg[one].name, "<span class='msg_poster'>"+msg[one].name+"</span><span class='msg_content"+withts+"'><span>"); escapedMsg = escapedMsg+"</span></span>"; } Loading @@ -115,7 +115,7 @@ function pageInit() { } /* Output the processed line */ $("#textlist").append( "<p class='msg"+type+" user_"+userIndex[msg[one].username.toLowerCase()]+"'>" + timestamp + escapedMsg + "</p"); $("#textlist").append( "<p class='msg"+type+" user_"+userIndex[msg[one].name.toLowerCase()]+"'>" + timestamp + escapedMsg + "</p"); break; Loading Loading @@ -143,8 +143,8 @@ function pageInit() { } /* Detect username */ if (escapedMsg.substr(0, msg[one].username.length)==msg[one].username) { escapedMsg = escapedMsg.replace(msg[one].username, "<span class='msg_poster'>"+msg[one].username+"</span><span class='msg_content"+withts+"'><span>"); if (escapedMsg.substr(0, msg[one].name.length)==msg[one].name) { escapedMsg = escapedMsg.replace(msg[one].name, "<span class='msg_poster'>"+msg[one].name+"</span><span class='msg_content"+withts+"'><span>"); escapedMsg = escapedMsg+"</span></span>"; } Loading @@ -164,7 +164,7 @@ function pageInit() { } /* Output the processed line */ $("#textlist").append( "<p class='msg"+type+" user_"+userIndex[msg[one].username.toLowerCase()]+"'>" + timestamp + escapedMsg + "</p"); $("#textlist").append( "<p class='msg"+type+" user_"+userIndex[msg[one].name.toLowerCase()]+"'>" + timestamp + escapedMsg + "</p"); break; case 14: // IPC_KICK Loading @@ -174,11 +174,11 @@ function pageInit() { what += "You look up; a large object is falling towards you very fast, very very fast. It looks like a Magic Roundabout!<br />"; what += "\"I wouldn't stand there if I was you\" says Zebedee<br />"; what += "WWWHHHEEEEEEEKKKKEEEERRRRRUUUUUNNNNNCCCCCHHHHHH<br />"; what += msg[one].username + " has just dropped the Magic Roundabout of Death on you.<br />"; what += msg[one].name + " has just dropped the Magic Roundabout of Death on you.<br />"; } else { what += "Boing, Zebedee arrived.<br />"; what += "\"Time for bed\" says Zebedee<br />"; what += msg[one].username + " has just dropped the Zebedee of Death on you.<br />"; what += msg[one].name + " has just dropped the Zebedee of Death on you.<br />"; } if (msg[one].text.substr(1,1) == 'r') what += "\""+msg[one].text.substr(2)+"\" says Zebedee<br />"; what += "</div>"; Loading Loading @@ -301,8 +301,8 @@ function drawWho(data, stat) var text = "<hr /><table cellspacing=\"0\" cellpadding=\"0\"><tbody>"; text += "<tr><th style=\"width: 10em;\">Name</th><th style=\"width: 6em;\">Idle</th><th>What...</th></tr>"; for (person in data) { var line = "<td>"+data[person].username+"</td>"; line += "<td>"+drawTime(data[person].idle)+"</td>"; var line = "<td>"+data[person].name+"</td>"; line += "<td>"+drawTime(data[person].idletime)+"</td>"; line += "<td>Room "+data[person].channel+"</td>"; text += "<tr>"+line+"</tr>"; } Loading @@ -311,7 +311,7 @@ function drawWho(data, stat) } function sortWho(a, b) { var nameA=a.username.toLowerCase(), nameB=b.username.toLowerCase(); var nameA=a.name.toLowerCase(), nameB=b.name.toLowerCase(); if (nameA < nameB) return -1; if (nameA > nameB) return 1; return 0; Loading @@ -332,7 +332,7 @@ function userInit(data, stat) { data.sort(sortWho); usercount=0; for (var person in data) { var thisUsername=data[person].username.toLowerCase(); var thisUsername=data[person].name.toLowerCase(); if (thisUsername in userIndex) usercount--; userIndex[thisUsername]=usercount; usercount++; Loading @@ -341,7 +341,7 @@ function userInit(data, stat) { function userAdd(data, stat) { for (var person in data) { var thisUsername=data[person].username.toLowerCase(); var thisUsername=data[person].name.toLowerCase(); if (!(thisUsername in userIndex)) { userIndex[thisUsername]=usercount; usercount++; Loading @@ -358,18 +358,18 @@ function drawWhoList(data, stat) { data.sort(sortWho); for (var person in data) { idleness=""; if (data[person].idle>1800) idleness=" idle"; var thisUsername=data[person].username.toLowerCase(); if (data[person].idletime>1800) idleness=" idle"; var thisUsername=data[person].name.toLowerCase(); var personinfo = "<div class=\"whoinfo\">"; if (data[person].hgwidth>0) personinfo += "<img src=\"https://sucs.org/pictures/people/"+data[person].username.toLowerCase()+".png\" width=\""+data[person].hgwidth+"\" height=\"64\" />"; if (data[person].hgwidth>0) personinfo += "<img src=\"https://sucs.org/pictures/people/"+data[person].name.toLowerCase()+".png\" width=\""+data[person].hgwidth+"\" height=\"64\" />"; else personinfo += "<img src=\"person.png\" width=\"64\" height=\"64\" />"; personinfo += "<strong>"+data[person].username+"</strong>"; personinfo += "<strong>"+data[person].name+"</strong>"; personinfo += " "+data[person].doing; personinfo += "<br />"; if (data[person].realname!=undefined) personinfo += data[person].realname+"<br />"; personinfo += "Room "+data[person].channel; personinfo += "</div>"; $("#wholist").append("<li class=\"who user_"+userIndex[thisUsername]+idleness+"\"><a href=\"#\">"+data[person].username+personinfo+"</a></li>"); $("#wholist").append("<li class=\"who user_"+userIndex[thisUsername]+idleness+"\"><a href=\"#\">"+data[person].name+personinfo+"</a></li>"); } } Loading Loading
src/webclient/comms.c +48 −43 Original line number Diff line number Diff line Loading @@ -99,9 +99,50 @@ void open_command_socket() INIT_LIST_HEAD(&connlist); } static char * whoinfo = NULL; static time_t whowhen = 0; static int whowant = 0; #define WHOCACHE 120 /* cache who data for this many seconds */ extern int32_t userposn; const char * fetch_who(int immediate) { time_t now = time(NULL); if (whoinfo == NULL || whowhen < (now - WHOCACHE)) { ipc_message_t * msg = ipcmsg_create(IPC_WHOLIST, userposn); ipcmsg_transmit(msg); /* dont want stale, come back later */ if (!immediate) { whowant = 1; return NULL; } } /* no answer we can give, sorry, you will HAVE to come back */ if (whoinfo == NULL) { whowant=1; return NULL; } /* they want an immediate answer, even if its stale */ return whoinfo; } /* we got a message */ static void accept_pipe_cmd(ipc_message_t *ipc, struct person *mesg_user) { if (ipc->head.type == IPC_WHOLIST) { /* keep a copy for later */ if (whoinfo != NULL) free(whoinfo); whoinfo = strdup(ipc->body); whowhen = time(NULL); /* if we want it, fall through, otherwise stop now */ if (whowant != 0) whowant=0; else return; } MESG *msg = malloc(sizeof(MESG)); msg->state = ipc->head.type; //msg->pid = ipc->head.src; Loading Loading @@ -254,7 +295,7 @@ static int send_list(CONNECTION *co) mws_add(buff, "\"state\":%d,", tmp->state); mws_add(buff, "\"serial\":%"PRId64",", tmp->serial); mws_add(buff, "\"when\":%"PRId64",", tmp->when); mws_add(buff, "\"username\":\"%s\",\"text\":\"%s\"}", tmp->user.name, tmp->text); mws_add(buff, "\"name\":\"%s\",\"text\":\"%s\"}", tmp->user.name, tmp->text); if (pos->next != &msglist) { mws_add(buff, ","); } Loading Loading @@ -413,45 +454,9 @@ static int handle_command(CONNECTION *co) return 1; }else if (co->authd && strcasecmp(buff, "who")==0) { struct person u; struct who w; int ufile, wfile; int32_t now = time(NULL); mwstring *line = mws_new(2048); int count=0; wfile = who_open(O_RDONLY); ufile = userdb_open(O_RDONLY); mws_add(line, "["); while (read(wfile, &w, sizeof(w))) { char *realname; char *doing; if (w.posn < 0) continue; lseek(ufile, w.posn, SEEK_SET); read(ufile, &u, sizeof(u)); if (count > 0) mws_add(line, ","); realname = json_escape(u.realname); doing = json_escape(u.doing); mws_add(line, "{"); if (u_god(user->status)) { mws_add(line, "\"realname\":\"%s\",", realname); } mws_add(line, "\"pid\":%d,", w.pid); mws_add(line, "\"username\":\"%s\",", u.name); mws_add(line, "\"channel\":%d,", u.room); mws_add(line, "\"doing\":\"%s\",", doing); mws_add(line, "\"idle\":%d}", now - u.idletime); free(realname); free(doing); count++; } mws_add(line, "]"); send(co->fd, mws_cstr(line), mws_len(line), 0); mws_free(line); const char *line = fetch_who(0); if (line != NULL) send(co->fd, line, strlen(line), 0); return 1; }else if (co->authd && strcasecmp(buff, "whoami")==0) { Loading @@ -459,7 +464,7 @@ static int handle_command(CONNECTION *co) char stats[64]; char *tmp; mws_add(line, "{"); mws_add(line, "\"username\":\"%s\",", user->name); mws_add(line, "\"name\":\"%s\",", user->name); tmp = json_escape(user->realname); mws_add(line, "\"realname\":\"%s\",", tmp); free(tmp); Loading Loading @@ -550,7 +555,7 @@ void talk_rawbcast(const char *fmt, ...) } void create_user(struct person *me, int *userposn, const char *username, const char *password) void create_user(struct person *me, int *uposn, const char *username, const char *password) { struct passwd *pw; char salt[3]; Loading Loading @@ -579,7 +584,7 @@ void create_user(struct person *me, int *userposn, const char *username, const c } else { mwlog("CREATED Auto web user"); } userdb_write(me, userposn); userdb_write(me, uposn); } void mwlog(const char *fmt, ...) Loading
src/webclient/comms.h +2 −0 Original line number Diff line number Diff line Loading @@ -7,3 +7,5 @@ char *json_escape(char *original); void close_cmd(void); void talk_rawbcast(const char *fmt, ...) __attribute__((format(printf,1,2))); void create_user(struct person *me, int *userposn, const char *username, const char *password); const char * fetch_who(int immediate);
webclient/say.js +28 −28 Original line number Diff line number Diff line Loading @@ -34,7 +34,7 @@ function pageInit() { // make a crude approximation of the old message styles switch (detail.type) { case "say": body = msg[one].username + ": " + detail.text; body = msg[one].name + ": " + detail.text; break; case "raw": body = detail.text; Loading @@ -42,33 +42,33 @@ function pageInit() { case "emote": switch (detail.plural){ case 1: body = msg[one].username + "'s " + detail.text; body = msg[one].name + "'s " + detail.text; break; case 2: body = msg[one].username + "' " + detail.text; body = msg[one].name + "' " + detail.text; break; case 3: body = msg[one].username + "'d " + detail.text; body = msg[one].name + "'d " + detail.text; break; case 4: body = msg[one].username + "'ll " + detail.text; body = msg[one].name + "'ll " + detail.text; break; default: body = msg[one].username + " " + detail.text; body = msg[one].name + " " + detail.text; break; } break; case "notsayto": body = msg[one].username + " says (-" + detail.exclude + "): " + detail.text; body = msg[one].name + " says (-" + detail.exclude + "): " + detail.text; break; case "says": body = msg[one].username + " says: " + detail.text; body = msg[one].name + " says: " + detail.text; break; case "whispers": body = msg[one].username + " whispers: " + detail.text; body = msg[one].name + " whispers: " + detail.text; break; default: body = msg[one].username + " " + detail.text; body = msg[one].name + " " + detail.text; break; } Loading @@ -94,8 +94,8 @@ function pageInit() { } /* Detect username */ if (escapedMsg.substr(0, msg[one].username.length)==msg[one].username) { escapedMsg = escapedMsg.replace(msg[one].username, "<span class='msg_poster'>"+msg[one].username+"</span><span class='msg_content"+withts+"'><span>"); if (escapedMsg.substr(0, msg[one].name.length)==msg[one].name) { escapedMsg = escapedMsg.replace(msg[one].name, "<span class='msg_poster'>"+msg[one].name+"</span><span class='msg_content"+withts+"'><span>"); escapedMsg = escapedMsg+"</span></span>"; } Loading @@ -115,7 +115,7 @@ function pageInit() { } /* Output the processed line */ $("#textlist").append( "<p class='msg"+type+" user_"+userIndex[msg[one].username.toLowerCase()]+"'>" + timestamp + escapedMsg + "</p"); $("#textlist").append( "<p class='msg"+type+" user_"+userIndex[msg[one].name.toLowerCase()]+"'>" + timestamp + escapedMsg + "</p"); break; Loading Loading @@ -143,8 +143,8 @@ function pageInit() { } /* Detect username */ if (escapedMsg.substr(0, msg[one].username.length)==msg[one].username) { escapedMsg = escapedMsg.replace(msg[one].username, "<span class='msg_poster'>"+msg[one].username+"</span><span class='msg_content"+withts+"'><span>"); if (escapedMsg.substr(0, msg[one].name.length)==msg[one].name) { escapedMsg = escapedMsg.replace(msg[one].name, "<span class='msg_poster'>"+msg[one].name+"</span><span class='msg_content"+withts+"'><span>"); escapedMsg = escapedMsg+"</span></span>"; } Loading @@ -164,7 +164,7 @@ function pageInit() { } /* Output the processed line */ $("#textlist").append( "<p class='msg"+type+" user_"+userIndex[msg[one].username.toLowerCase()]+"'>" + timestamp + escapedMsg + "</p"); $("#textlist").append( "<p class='msg"+type+" user_"+userIndex[msg[one].name.toLowerCase()]+"'>" + timestamp + escapedMsg + "</p"); break; case 14: // IPC_KICK Loading @@ -174,11 +174,11 @@ function pageInit() { what += "You look up; a large object is falling towards you very fast, very very fast. It looks like a Magic Roundabout!<br />"; what += "\"I wouldn't stand there if I was you\" says Zebedee<br />"; what += "WWWHHHEEEEEEEKKKKEEEERRRRRUUUUUNNNNNCCCCCHHHHHH<br />"; what += msg[one].username + " has just dropped the Magic Roundabout of Death on you.<br />"; what += msg[one].name + " has just dropped the Magic Roundabout of Death on you.<br />"; } else { what += "Boing, Zebedee arrived.<br />"; what += "\"Time for bed\" says Zebedee<br />"; what += msg[one].username + " has just dropped the Zebedee of Death on you.<br />"; what += msg[one].name + " has just dropped the Zebedee of Death on you.<br />"; } if (msg[one].text.substr(1,1) == 'r') what += "\""+msg[one].text.substr(2)+"\" says Zebedee<br />"; what += "</div>"; Loading Loading @@ -301,8 +301,8 @@ function drawWho(data, stat) var text = "<hr /><table cellspacing=\"0\" cellpadding=\"0\"><tbody>"; text += "<tr><th style=\"width: 10em;\">Name</th><th style=\"width: 6em;\">Idle</th><th>What...</th></tr>"; for (person in data) { var line = "<td>"+data[person].username+"</td>"; line += "<td>"+drawTime(data[person].idle)+"</td>"; var line = "<td>"+data[person].name+"</td>"; line += "<td>"+drawTime(data[person].idletime)+"</td>"; line += "<td>Room "+data[person].channel+"</td>"; text += "<tr>"+line+"</tr>"; } Loading @@ -311,7 +311,7 @@ function drawWho(data, stat) } function sortWho(a, b) { var nameA=a.username.toLowerCase(), nameB=b.username.toLowerCase(); var nameA=a.name.toLowerCase(), nameB=b.name.toLowerCase(); if (nameA < nameB) return -1; if (nameA > nameB) return 1; return 0; Loading @@ -332,7 +332,7 @@ function userInit(data, stat) { data.sort(sortWho); usercount=0; for (var person in data) { var thisUsername=data[person].username.toLowerCase(); var thisUsername=data[person].name.toLowerCase(); if (thisUsername in userIndex) usercount--; userIndex[thisUsername]=usercount; usercount++; Loading @@ -341,7 +341,7 @@ function userInit(data, stat) { function userAdd(data, stat) { for (var person in data) { var thisUsername=data[person].username.toLowerCase(); var thisUsername=data[person].name.toLowerCase(); if (!(thisUsername in userIndex)) { userIndex[thisUsername]=usercount; usercount++; Loading @@ -358,18 +358,18 @@ function drawWhoList(data, stat) { data.sort(sortWho); for (var person in data) { idleness=""; if (data[person].idle>1800) idleness=" idle"; var thisUsername=data[person].username.toLowerCase(); if (data[person].idletime>1800) idleness=" idle"; var thisUsername=data[person].name.toLowerCase(); var personinfo = "<div class=\"whoinfo\">"; if (data[person].hgwidth>0) personinfo += "<img src=\"https://sucs.org/pictures/people/"+data[person].username.toLowerCase()+".png\" width=\""+data[person].hgwidth+"\" height=\"64\" />"; if (data[person].hgwidth>0) personinfo += "<img src=\"https://sucs.org/pictures/people/"+data[person].name.toLowerCase()+".png\" width=\""+data[person].hgwidth+"\" height=\"64\" />"; else personinfo += "<img src=\"person.png\" width=\"64\" height=\"64\" />"; personinfo += "<strong>"+data[person].username+"</strong>"; personinfo += "<strong>"+data[person].name+"</strong>"; personinfo += " "+data[person].doing; personinfo += "<br />"; if (data[person].realname!=undefined) personinfo += data[person].realname+"<br />"; personinfo += "Room "+data[person].channel; personinfo += "</div>"; $("#wholist").append("<li class=\"who user_"+userIndex[thisUsername]+idleness+"\"><a href=\"#\">"+data[person].username+personinfo+"</a></li>"); $("#wholist").append("<li class=\"who user_"+userIndex[thisUsername]+idleness+"\"><a href=\"#\">"+data[person].name+personinfo+"</a></li>"); } } Loading