move the rest of the places to use AppendImportantMessage() or GetServerStatusMsg()
[citadel.git] / webcit / roomlist.c
1 /*
2  * room listings and filters.
3  */
4
5 #include "webcit.h"
6 #include "webserver.h"
7
8 HashList *GetWhoKnowsHash(StrBuf *Target, WCTemplputParams *TP)
9 {
10         StrBuf *Line;
11         StrBuf *Token;
12         long State;
13         HashList *Whok = NULL;
14         int Done = 0;
15         int n;
16
17         serv_puts("WHOK");
18         Line = NewStrBuf();
19         StrBuf_ServGetln(Line);
20         if (GetServerStatus(Line, &State) == 1) 
21         {
22                 Whok = NewHash(1, Flathash);
23                 while(!Done && (StrBuf_ServGetln(Line) >= 0) )
24                         if ( (StrLength(Line)==3) && 
25                              !strcmp(ChrPtr(Line), "000")) 
26                         {
27                                 Done = 1;
28                         }
29                         else
30                         {
31                         
32                                 const char *Pos = NULL;
33                                 Token = NewStrBufPlain (NULL, StrLength(Line));
34                                 StrBufExtract_NextToken(Token, Line, &Pos, '|');
35
36                                 Put(Whok, 
37                                     IKEY(n),
38                                     Token, 
39                                     HFreeStrBuf);
40                                 n++;
41                         }
42         }
43         else if (State == 550)
44                 AppendImportantMessage(_("Higher access is required to access this function."), -1);
45
46
47         FreeStrBuf(&Line);
48         return Whok;
49 }
50
51
52 void DeleteFloor(void *vFloor)
53 {
54         Floor *pFloor;
55         pFloor = (Floor*) vFloor;
56         FreeStrBuf(&pFloor->Name);
57         free(pFloor);
58 }
59
60 int SortFloorsByNameOrder(const void *vfloor1, const void *vfloor2) 
61 {
62         Floor *f1 = (Floor*) GetSearchPayload(vfloor1);
63         Floor *f2 = (Floor*) GetSearchPayload(vfloor2);
64         
65         /* prefer My floor over alpabetical sort */
66         if (f1->ID == VIRTUAL_MY_FLOOR)
67                 return 1;
68         if (f2->ID == VIRTUAL_MY_FLOOR)
69                 return -1;
70
71         return strcmp(ChrPtr(f1->Name), ChrPtr(f2->Name));
72 }
73
74 HashList *GetFloorListHash(StrBuf *Target, WCTemplputParams *TP) 
75 {
76         int Done = 0;
77         const char *Err;
78         StrBuf *Buf;
79         HashList *floors;
80         HashList *floorsbyname;
81         HashPos *it;
82         Floor *pFloor;
83         void *vFloor;
84         const char *Pos;
85         int i;
86         wcsession *WCC = WC;
87         const char *HashKey;
88         long HKLen;
89
90
91         if (WCC->Floors != NULL)
92                 return WCC->Floors;
93         WCC->Floors = floors = NewHash(1, Flathash);
94         WCC->FloorsByName = floorsbyname = NewHash(1, NULL);
95         Buf = NewStrBuf();
96
97         pFloor = (Floor*) malloc(sizeof(Floor));
98         pFloor->ID = VIRTUAL_MY_FLOOR;
99         pFloor->Name = NewStrBufPlain(_("My Folders"), -1);
100         pFloor->NRooms = 0;
101         
102         Put(floors, IKEY(pFloor->ID), pFloor, DeleteFloor);
103         Put(floorsbyname, SKEY(pFloor->Name), pFloor, reference_free_handler);
104
105         serv_puts("LFLR"); /* get floors */
106         StrBufTCP_read_line(Buf, &WC->serv_sock, 0, &Err); /* '100', we hope */
107         if (GetServerStatus(Buf, NULL) == 1) 
108         {
109                 while(!Done && StrBuf_ServGetln(Buf) >= 0)
110                         if ( (StrLength(Buf)==3) && 
111                              !strcmp(ChrPtr(Buf), "000")) 
112                         {
113                                 Done = 1;
114                         }
115                         else
116                         {
117                         
118                                 Pos = NULL;
119
120                                 pFloor = (Floor*) malloc(sizeof(Floor));
121                                 pFloor->ID = StrBufExtractNext_int(Buf, &Pos, '|');
122                                 pFloor->Name = NewStrBufPlain(NULL, StrLength(Buf));
123                                 StrBufExtract_NextToken(pFloor->Name, Buf, &Pos, '|');
124                                 pFloor->NRooms = StrBufExtractNext_long(Buf, &Pos, '|');
125
126                                 Put(floors, IKEY(pFloor->ID), pFloor, DeleteFloor);
127                                 Put(floorsbyname, SKEY(pFloor->Name), pFloor, reference_free_handler);
128                         }
129         }
130         FreeStrBuf(&Buf);
131         
132         /* now lets pre-sort them alphabeticaly. */
133         i = 1;
134         SortByPayload(floors, SortFloorsByNameOrder);
135         it = GetNewHashPos(floors, 0);
136         while ( GetNextHashPos(floors, it, &HKLen, &HashKey, &vFloor)) 
137                 ((Floor*) vFloor)->AlphaN = i++;
138         DeleteHashPos(&it);
139         SortByHashKeyStr(floors);
140
141         return floors;
142 }
143
144 HashList *GetZappedRoomListHash(StrBuf *Target, WCTemplputParams *TP) 
145 {
146         wcsession *WCC = WC;
147
148         if (WCC->Floors == NULL)
149                 GetFloorListHash(Target, TP);
150         serv_puts("LZRM -1");
151         return GetRoomListHash(Target, TP);
152 }
153 HashList *GetRoomListHashLKRA(StrBuf *Target, WCTemplputParams *TP) 
154 {
155         wcsession *WCC = WC;
156
157         if (WCC->Floors == NULL)
158                 GetFloorListHash(Target, TP);
159         if (WCC->Rooms == NULL) 
160         {
161                 serv_puts("LKRA");
162                 WCC->Rooms =  GetRoomListHash(Target, TP);
163         }
164         return WCC->Rooms;
165 }
166
167
168 void FlushIgnetCfgs(folder *room)
169 {
170         int i;
171         if (room->IgnetCfgs[maxRoomNetCfg] == (HashList*) StrBufNOTNULL)
172         {
173                 for (i = ignet_push_share; i < maxRoomNetCfg; i++)
174                         DeleteHash(&room->IgnetCfgs[i]);
175         }
176         memset(&room->IgnetCfgs, 0, sizeof(HashList *) * (maxRoomNetCfg + 1));
177
178 }
179
180 void FlushFolder(folder *room)
181 {
182         int i;
183
184         FreeStrBuf(&room->XAPass);
185         FreeStrBuf(&room->Directory);
186         FreeStrBuf(&room->RoomAide);
187         FreeStrBuf(&room->XInfoText);
188         room->XHaveInfoTextLoaded = 0;
189
190         FreeStrBuf(&room->name);
191
192         FlushIgnetCfgs(room);
193
194         if (room->RoomNameParts != NULL)
195         {
196                 for (i=0; i < room->nRoomNameParts; i++)
197                         FreeStrBuf(&room->RoomNameParts[i]);
198                 free(room->RoomNameParts);
199         }
200         memset(room, 0, sizeof(folder));
201 }
202
203 void vDeleteFolder(void *vFolder)
204 {
205         folder *room;
206
207         room = (folder*) vFolder;
208         FlushFolder(room);
209
210         free(room);
211 }
212
213
214 HashList *GetRoomListHash(StrBuf *Target, WCTemplputParams *TP) 
215 {
216         int Done = 0;
217         HashList *rooms;
218         folder *room;
219         StrBuf *Buf;
220         const char *Pos;
221         void *vFloor;
222         wcsession *WCC = WC;
223         CompareFunc SortIt;
224         WCTemplputParams SubTP;
225
226         Buf = NewStrBuf();
227         rooms = NewHash(1, NULL);
228         StrBuf_ServGetln(Buf);
229         if (GetServerStatus(Buf, NULL) == 1) 
230         {
231                 while(!Done && (StrBuf_ServGetln(Buf) >= 0))
232                         if ( (StrLength(Buf)==3) && 
233                              !strcmp(ChrPtr(Buf), "000")) 
234                         {
235                                 Done = 1;
236                         }
237                         else
238                         {                               
239                                 Pos = NULL;
240                                 room = (folder*) malloc (sizeof(folder));
241                                 memset(room, 0, sizeof(folder));
242
243                                 /* Load the base data from the server reply */
244                                 room->name = NewStrBufPlain(NULL, StrLength(Buf));
245                                 StrBufExtract_NextToken(room->name, Buf, &Pos, '|');
246
247                                 room->QRFlags = StrBufExtractNext_long(Buf, &Pos, '|');
248                                 room->floorid = StrBufExtractNext_int(Buf, &Pos, '|');
249                                 room->Order = StrBufExtractNext_long(Buf, &Pos, '|');
250                                 room->QRFlags2 = StrBufExtractNext_long(Buf, &Pos, '|');
251
252                                 room->RAFlags = StrBufExtractNext_long(Buf, &Pos, '|');
253
254 /*
255   ACWHUT?
256   room->ACL = NewStrBufPlain(NULL, StrLength(Buf));
257   StrBufExtract_NextToken(room->ACL, Buf, &Pos, '|');
258 */
259
260                                 room->view = StrBufExtractNext_long(Buf, &Pos, '|');
261                                 room->defview = StrBufExtractNext_long(Buf, &Pos, '|');
262                                 room->lastchange = StrBufExtractNext_long(Buf, &Pos, '|');
263
264                                 /* Evaluate the Server sent data for later use */
265                                 /* find out, whether we are in a sub-room */
266                                 room->nRoomNameParts = StrBufNum_tokens(room->name, '\\');
267                                 if (room->nRoomNameParts > 1)
268                                 {
269                                         int i;
270
271                                         Pos = NULL;
272                                         room->RoomNameParts = malloc(sizeof(StrBuf*) * (room->nRoomNameParts + 1));
273                                         memset(room->RoomNameParts, 0, sizeof(StrBuf*) * (room->nRoomNameParts + 1));
274                                         for (i=0; i < room->nRoomNameParts; i++)
275                                         {
276                                                 room->RoomNameParts[i] = NewStrBuf();
277                                                 StrBufExtract_NextToken(room->RoomNameParts[i],
278                                                                         room->name, &Pos, '\\');
279                                         }
280                                 }
281
282                                 /* Private mailboxes on the main floor get remapped to the personal folder */
283                                 if ((room->QRFlags & QR_MAILBOX) && 
284                                     (room->floorid == 0))
285                                 {
286                                         room->floorid = VIRTUAL_MY_FLOOR;
287                                         if ((room->nRoomNameParts == 1) && 
288                                             (StrLength(room->name) == 4) && 
289                                             (strcmp(ChrPtr(room->name), "Mail") == 0))
290                                         {
291                                                 room->is_inbox = 1;
292                                         }
293
294                                 }
295                                 /* get a pointer to the floor we're on: */
296                                 GetHash(WCC->Floors, IKEY(room->floorid), &vFloor);
297                                 room->Floor = (const Floor*) vFloor;
298
299
300
301                                 /* now we know everything, remember it... */
302                                 Put(rooms, SKEY(room->name), room, vDeleteFolder);
303                         }
304         }
305
306         SubTP.Filter.ContextType = CTX_ROOMS;
307         SortIt = RetrieveSort(&SubTP, NULL, 0, HKEY("fileunsorted"), 0);
308         if (SortIt != NULL)
309                 SortByPayload(rooms, SortIt);
310         else 
311                 SortByPayload(rooms, SortRoomsByListOrder);
312         FreeStrBuf(&Buf);
313         return rooms;
314 }
315
316 HashList *GetNetConfigHash(StrBuf *Target, WCTemplputParams *TP) 
317 {
318         wcsession *WCC = WC;
319         StrBuf *Line;
320         StrBuf *Token;
321         StrBuf *Content;
322         long WantThisOne;
323         long PutTo;
324         long State;
325         
326         WantThisOne = GetTemplateTokenNumber(Target, TP, 5, -1);
327         if ((WantThisOne < 0) || (WantThisOne > maxRoomNetCfg))
328                 return NULL;
329         if (WCC->CurRoom.IgnetCfgs[maxRoomNetCfg] == (HashList*) StrBufNOTNULL)
330                 return WCC->CurRoom.IgnetCfgs[WantThisOne];
331
332         WCC->CurRoom.IgnetCfgs[maxRoomNetCfg] = (HashList*) StrBufNOTNULL;
333         serv_puts("GNET");
334         Line = NewStrBuf();
335         Token = NewStrBuf();
336         StrBuf_ServGetln(Line);
337         if (GetServerStatus(Line, &State) == 1) 
338         {
339                 const char *Pos = NULL;
340                 int Done = 0;
341
342                 while(!Done && (StrBuf_ServGetln(Line) >= 0))
343                         if ( (StrLength(Line)==3) && 
344                              !strcmp(ChrPtr(Line), "000"))
345                         {
346                                 Done = 1;
347                         }
348                         else
349                         {
350                                 StrBufExtract_NextToken(Token, Line, &Pos, '|');
351                                 PutTo = GetTokenDefine(SKEY(Token), -1);
352                                 if ((PutTo >= 0) && 
353                                     (PutTo < maxRoomNetCfg) &&
354                                     (Pos != StrBufNOTNULL))
355                                 {
356                                         int n;
357                                         HashList *SubH;
358                                         
359                                         if (WCC->CurRoom.IgnetCfgs[PutTo] == NULL)
360                                         {
361                                                 n = 0;
362                                                 WCC->CurRoom.IgnetCfgs[PutTo] = NewHash(1, NULL);
363                                         }
364                                         else 
365                                         {
366                                                 n = GetCount(WCC->CurRoom.IgnetCfgs[PutTo]);
367                                         }
368                                         SubH = NewHash(1, NULL);
369                                         Put(WCC->CurRoom.IgnetCfgs[PutTo], 
370                                             IKEY(n),
371                                             SubH, 
372                                             HDeleteHash);
373                                         n = 1; /* #0 is the type... */
374                                         while (Pos != StrBufNOTNULL) {
375                                                 Content = NewStrBuf();
376                                                 StrBufExtract_NextToken(Content, Line, &Pos, '|');
377                                                 Put(SubH, 
378                                                     IKEY(n),
379                                                     Content, 
380                                                     HFreeStrBuf);
381                                                 n++;
382                                         }
383                                 }
384                                 Pos = NULL;
385                         }
386         }
387         else if (State == 550)
388                 AppendImportantMessage(_("Higher access is required to access this function."), -1);
389
390
391         return WCC->CurRoom.IgnetCfgs[WantThisOne];
392 }
393
394 /** Unused function that orders rooms by the listorder flag */
395 int SortRoomsByListOrder(const void *room1, const void *room2) 
396 {
397         folder *r1 = (folder*) GetSearchPayload(room1);
398         folder *r2 = (folder*) GetSearchPayload(room2);
399   
400         if (r1->Order == r2->Order) return 0;
401         if (r1->Order > r2->Order) return 1;
402         return -1;
403 }
404
405 int CompareRoomListByFloorRoomPrivFirst(const void *room1, const void *room2) 
406 {
407         folder *r1 = (folder*) GetSearchPayload(room1);
408         folder *r2 = (folder*) GetSearchPayload(room2);
409   
410         if ((r1->Floor == NULL)  ||
411             (r2->Floor == NULL))
412                 return 0;
413                 
414         /**
415          * are we on the same floor? else sort by floor.
416          */
417         if (r1->Floor != r2->Floor)
418         {
419                 /**
420                  * the private rooms are first in any case.
421                  */
422                 if (r1->Floor->ID == VIRTUAL_MY_FLOOR)
423                         return -1;
424                 if (r2->Floor->ID == VIRTUAL_MY_FLOOR)
425                         return 1;
426                 /**
427                  * else decide alpaheticaly by floorname
428                  */
429                 return (r1->Floor->AlphaN > r2->Floor->AlphaN)? 1 : -1;
430         }
431
432         /**
433          * if we have different levels of subdirectories, 
434          * we want the toplevel to be first, regardless of sort
435          * sequence.
436          */
437         if (((r1->nRoomNameParts > 1) || 
438             (r2->nRoomNameParts > 1)    )&&
439             (r1->nRoomNameParts != r2->nRoomNameParts))
440         {
441                 int i, ret;
442                 int nparts = (r1->nRoomNameParts > r2->nRoomNameParts)?
443                         r2->nRoomNameParts : r1->nRoomNameParts;
444
445                 for (i=0; i < nparts; i++)
446                 {
447                         ret = strcmp (ChrPtr(r1->name), 
448                                       ChrPtr(r2->name));
449                         /**
450                          * Deltas in common parts? exit here.
451                          */
452                         if (ret != 0) 
453                                 return ret;
454                 }
455
456                 /**
457                  * who's a subdirectory of whom?
458                  */
459                 if (r1->nRoomNameParts > r2->nRoomNameParts)
460                         return 1;
461                 else
462                         return -1;
463
464         }
465
466         /**
467          * else just sort alphabeticaly.
468          */
469         return strcmp (ChrPtr(r1->name), 
470                        ChrPtr(r2->name));
471 }
472
473 int CompareRoomListByFloorRoomPrivFirstRev(const void *room1, const void *room2) 
474 {
475         folder *r1 = (folder*) GetSearchPayload(room1);
476         folder *r2 = (folder*) GetSearchPayload(room2);
477
478         if ((r1->Floor == NULL)  ||
479             (r2->Floor == NULL))
480                 return 0;
481
482         /**
483          * are we on the same floor? else sort by floor.
484          */
485         if (r2->Floor != r1->Floor)
486         {
487                 /**
488                  * the private rooms are first in any case.
489                  */
490                 if (r1->Floor->ID == VIRTUAL_MY_FLOOR)
491                         return -1;
492                 if (r2->Floor->ID == VIRTUAL_MY_FLOOR)
493                         return 1;
494                 /**
495                  * else decide alpaheticaly by floorname
496                  */
497
498                 return (r1->Floor->AlphaN < r2->Floor->AlphaN)? 1 : -1;
499         }
500
501         /**
502          * if we have different levels of subdirectories, 
503          * we want the toplevel to be first, regardless of sort
504          * sequence.
505          */
506         if (((r1->nRoomNameParts > 1) || 
507             (r2->nRoomNameParts > 1)    )&&
508             (r1->nRoomNameParts != r2->nRoomNameParts))
509         {
510                 int i, ret;
511                 int nparts = (r1->nRoomNameParts > r2->nRoomNameParts)?
512                         r2->nRoomNameParts : r1->nRoomNameParts;
513
514                 for (i=0; i < nparts; i++)
515                 {
516                         /**
517                          * special cases if one room is top-level...
518                          */
519                         if (r2->nRoomNameParts == 1)
520                                 ret = strcmp (ChrPtr(r2->name), 
521                                               ChrPtr(r1->RoomNameParts[i]));
522                         else if (r1->nRoomNameParts == 1)
523                                 ret = strcmp (ChrPtr(r2->RoomNameParts[i]),
524                                               ChrPtr(r1->name));
525                         else 
526                                 ret = strcmp (ChrPtr(r2->RoomNameParts[i]), 
527                                               ChrPtr(r1->RoomNameParts[i]));
528                         /**
529                          * Deltas in common parts? exit here.
530                          */
531                         if (ret != 0) 
532                                 return ret;
533                 }
534
535                 /**
536                  * who's a subdirectory of whom?
537                  */
538                 if (r1->nRoomNameParts > r2->nRoomNameParts)
539                         return 1;
540                 else
541                         return -1;
542         }
543
544         return strcmp (ChrPtr(r2->name), 
545                        ChrPtr(r1->name));
546 }
547
548 int GroupchangeRoomListByFloorRoomPrivFirst(const void *room1, const void *room2) 
549 {
550         folder *r1 = (folder*) room1;
551         folder *r2 = (folder*) room2;
552   
553
554         if ((r1->Floor == NULL)  ||
555             (r2->Floor == NULL))
556                 return 0;
557                 
558         if (r1->Floor == r2->Floor)
559                 return 0;
560         else 
561         {
562                 wcsession *WCC = WC;
563                 static int columns = 3;
564                 int boxes_per_column = 0;
565                 int nf;
566
567                 nf = GetCount(WCC->Floors);
568                 while (nf % columns != 0) ++nf;
569                 boxes_per_column = (nf / columns);
570                 if (boxes_per_column < 1)
571                         boxes_per_column = 1;
572                 if (r1->Floor->AlphaN % boxes_per_column == 0)
573                         return 2;
574                 else 
575                         return 1;
576         }
577 }
578
579
580 int CompareRooms(const folder *room1, const folder *room2) 
581 {
582         if ((room1 == NULL) || (room2 == NULL))
583                 return -1;
584         return CompareRoomListByFloorRoomPrivFirst(room1, room2);
585 }
586
587 int ConditionalRoomIsRESTSubRoom(StrBuf *Target, WCTemplputParams *TP)
588 {
589         wcsession  *WCC = WC;
590         folder     *Folder = (folder *)CTX;
591         HashPos    *it;
592         StrBuf     * Dir;
593         void       *vDir;
594         long        len;
595         const char *Key;
596         int i, j, urlp;
597         int delta;
598
599
600         /* list only folders relative to the current floor... */
601         if (Folder->Floor != WCC->CurrentFloor)
602                 return 0;
603
604         urlp = GetCount(WCC->Directory);
605         delta = Folder->nRoomNameParts - urlp + 1;
606
607         syslog(0, "\n->%s: %d - %ld ", 
608                ChrPtr(Folder->name), 
609                urlp, 
610                Folder->nRoomNameParts);
611         /* list only the floors which are in relation to the dav_depth header */
612         if (WCC->Hdr->HR.dav_depth != delta) {
613                 syslog(0, "1\n");
614                 return 0;
615         }
616
617
618         it = GetNewHashPos(WCC->Directory, 0);
619         /* Fast forward the floorname we checked above... */
620         GetNextHashPos(WCC->Directory, it, &len, &Key, &vDir);
621
622         if (Folder->nRoomNameParts > 1) {               
623                 for (i = 0, j = 1; 
624                      (i > Folder->nRoomNameParts) && (j > urlp); 
625                      i++, j++)
626                 {
627                         if (!GetNextHashPos(WCC->Directory, 
628                                             it, &len, &Key, &vDir) ||
629                             (vDir == NULL))
630                         {
631                                 DeleteHashPos(&it);
632
633                                 syslog(0, "3\n");
634                                 return 0;
635                         }
636                         Dir = (StrBuf*) vDir;
637                         if (strcmp(ChrPtr(Folder->RoomNameParts[i]), 
638                                    ChrPtr(Dir)) != 0)
639                         {
640                                 DeleteHashPos(&it);
641                                 syslog(0, "4\n");
642                                 return 0;
643                         }
644                 }
645                 DeleteHashPos(&it);
646                 return 1;
647         }
648         else {
649                 if (!GetNextHashPos(WCC->Directory, 
650                                     it, &len, &Key, &vDir) ||
651                     (vDir == NULL))
652                 {
653                         DeleteHashPos(&it);
654                         
655                         syslog(0, "5\n");
656                         return WCC->Hdr->HR.dav_depth == 1;
657                 }
658                 DeleteHashPos(&it);
659                 Dir = (StrBuf*) vDir;
660                 if (WCC->Hdr->HR.dav_depth == 0) {
661                         return (strcmp(ChrPtr(Folder->name), 
662                                        ChrPtr(Dir))
663                                 == 0);
664
665                 }
666                 return 0;
667         }
668 }
669
670
671 void 
672 InitModule_ROOMLIST
673 (void)
674 {
675
676         RegisterIterator("ITERATE:THISROOM:WHO_KNOWS", 0, NULL, GetWhoKnowsHash, NULL, DeleteHash, CTX_STRBUF, CTX_NONE, IT_NOFLAG);
677         RegisterIterator("ITERATE:THISROOM:GNET", 1, NULL, GetNetConfigHash, NULL, NULL, CTX_STRBUFARR, CTX_NONE, IT_NOFLAG);
678
679         RegisterIterator("LFLR", 0, NULL, GetFloorListHash, NULL, NULL, CTX_FLOORS, CTX_NONE, IT_FLAG_DETECT_GROUPCHANGE);
680         RegisterIterator("LKRA", 0, NULL, GetRoomListHashLKRA, NULL, NULL, CTX_ROOMS, CTX_NONE, IT_FLAG_DETECT_GROUPCHANGE);
681         RegisterIterator("LZRM", 0, NULL, GetZappedRoomListHash, NULL, DeleteHash, CTX_ROOMS, CTX_NONE, IT_FLAG_DETECT_GROUPCHANGE);
682
683         RegisterConditional(HKEY("COND:ROOM:REST:ISSUBROOM"), 0, ConditionalRoomIsRESTSubRoom, CTX_ROOMS);
684
685         RegisterSortFunc(HKEY("byfloorroom"),
686                          NULL, 0,
687                          CompareRoomListByFloorRoomPrivFirst,
688                          CompareRoomListByFloorRoomPrivFirstRev,
689                          GroupchangeRoomListByFloorRoomPrivFirst,
690                          CTX_ROOMS);
691
692 }