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