source: trunk/dll/filldir.c@ 773

Last change on this file since 773 was 773, checked in by Steven Levine, 18 years ago

Correct some compare directories collector nits
Use BldQuoted... functions
Move BldQuoted... functions near primary callers
Add RUNTYPE_MASK
Use Runtime_Error2 more

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 47.2 KB
Line 
1
2/***********************************************************************
3
4 $Id: filldir.c 773 2007-08-07 21:23:27Z stevenhl $
5
6 Fill Directory Tree Containers
7
8 Copyright (c) 1993-98 M. Kimes
9 Copyright (c) 2001, 2007 Steven H. Levine
10
11 10 Jan 04 SHL ProcessDirectory: avoid most large drive failures
12 24 May 05 SHL Rework Win_Error usage
13 24 May 05 SHL Rework for CNRITEM.szSubject
14 25 May 05 SHL Rework for ULONGLONG
15 25 May 05 SHL Rework FillInRecordFromFFB
16 25 May 05 SHL Rework FillTreeCnr
17 28 May 05 SHL Drop stale debug code
18 05 Jun 05 SHL Comments
19 09 Jun 05 SHL Rework WinLoadFileIcon enables
20 09 Jun 05 SHL Rework IDFile
21 13 Aug 05 SHL Renames
22 24 Oct 05 SHL FillInRecordFromFFB: correct longname display enable
23 24 Oct 05 SHL FillInRecordFromFSA: correct longname display enable
24 24 Oct 05 SHL Drop obsolete code
25 22 Jul 06 SHL Check more run time errors
26 20 Oct 06 SHL Sync . .. check code
27 22 Oct 06 GKY Add NDFS32 support
28 17 Feb 07 GKY Additional archive and image file tyoes identifed by extension
29 17 Feb 07 GKY Add more drive types
30 09 Mar 07 GKY Use SelectDriveIcon
31 20 Mar 07 GKY Increase extention check to 4 letters for icon selections
32 23 Jun 07 GKY Fixed ram disk without a directory not appearing on states drive list
33 23 Jul 07 SHL Sync with CNRITEM updates (ticket#24)
34 29 Jul 07 SHL Add CNRITEM free and remove support (ticket#24)
35 02 Aug 07 SHL Add FileAttrToString
36 03 Aug 07 GKY Enlarged and made setable everywhere Findbuf (speed file loading)
37 04 Aug 07 SHL Update #pragma alloc_test for new functions
38
39***********************************************************************/
40
41#define INCL_DOS
42#define INCL_WIN
43#define INCL_LONGLONG
44#include <os2.h>
45
46#include <stdarg.h>
47#include <stdio.h>
48#include <stdlib.h>
49#include <string.h>
50#include <ctype.h>
51#include <time.h>
52#include <time.h>
53
54#if 1 // fixme to disable or to be configurable
55#include <malloc.h> // _heapchk
56#endif
57
58#include "fm3dll.h"
59#include "fm3str.h"
60
61static PSZ pszSrcFile = __FILE__;
62
63#pragma alloc_text(FILLDIR,FillInRecordFromFFB,FillInRecordFromFSA,IDFile)
64#pragma alloc_text(FILLDIR1,ProcessDirectory,FillDirCnr,FillTreeCnr,FileAttrToString)
65#pragma alloc_text(EMPTYCNR,EmptyCnr,FreeCnrItemData,FreeCnrItem,FreeCnrItemList,RemoveCnrItems)
66
67/**
68 * Return display string given standard file attribute mask
69 * @param fileAttr attribute mask in FILEFINDBUF format
70 * @return fixed length string for display
71 */
72
73const PSZ FileAttrToString(ULONG fileAttr)
74{
75 // From os2win.h
76 // FILE_ATTRIBUTE_READONLY 0x00000001
77 // FILE_ATTRIBUTE_HIDDEN 0x00000002
78 // FILE_ATTRIBUTE_SYSTEM 0x00000004
79 // 0x00000008
80 // FILE_ATTRIBUTE_DIRECTORY 0x00000010
81 // FILE_ATTRIBUTE_ARCHIVE 0x00000020
82
83 static CHAR *apszAttrString[] = {
84 // RHSDA
85 "-----",
86 "R----",
87 "-H---",
88 "RH---",
89 "--S--",
90 "R-S--",
91 "-HS--",
92 "RHS--",
93 "---D-",
94 "R--D-",
95 "-H-D-",
96 "RH-D-",
97 "--SD-",
98 "R-SD-",
99 "-HSD-",
100 "RHSD-",
101 "----A",
102 "R---A",
103 "-H--A",
104 "RH--A",
105 "--S-A",
106 "R-S-A",
107 "-HS-A",
108 "RHS-A",
109 "---DA",
110 "R--DA",
111 "-H-DA",
112 "RH-DA",
113 "--SDA",
114 "R-SDA",
115 "-HSDA",
116 "RHSDA"
117 };
118
119 fileAttr = ((fileAttr & 0x30) >> 1) | (fileAttr & 7); // Drop don't care bit from index
120
121 return apszAttrString[fileAttr];
122
123}
124
125static HPOINTER IDFile(PSZ p)
126{
127 HPOINTER hptr;
128 ULONG cmp;
129 CHAR cmps[5];
130
131 p = strrchr(p, '.');
132 if (p && !p[5]) {
133 cmps[0] = '.';
134 cmps[1] = toupper(p[1]);
135 cmps[2] = toupper(p[2]);
136 cmps[3] = toupper(p[3]);
137 cmps[4] = toupper(p[4]);
138
139 cmp = *(ULONG *) cmps;
140
141 if (cmp == *(ULONG *) ".EXE" || cmp == *(ULONG *) ".CMD" ||
142 cmp == *(ULONG *) ".BAT" || cmp == *(ULONG *) ".COM")
143 hptr = hptrApp;
144 else if (cmp == *(ULONG *) ".ZIP" || cmp == *(ULONG *) ".LZH" ||
145 cmp == *(ULONG *) ".ARJ" || cmp == *(ULONG *) ".ARC" ||
146 cmp == *(ULONG *) ".ZOO" || cmp == *(ULONG *) ".RAR" ||
147 cmp == *(ULONG *) ".TAR" || cmp == *(ULONG *) ".TGZ" ||
148 cmp == *(ULONG *) ".GZ" || cmp == *(ULONG *) ".Z" ||
149 cmp == *(ULONG *) ".CAB" || cmp == *(ULONG *) ".BZ2")
150 hptr = hptrArc;
151 else if (cmp == *(ULONG *) ".BMP" || cmp == *(ULONG *) ".ICO" ||
152 cmp == *(ULONG *) ".PTR" || cmp == *(ULONG *) ".GIF" ||
153 cmp == *(ULONG *) ".TIF" || cmp == *(ULONG *) ".PCX" ||
154 cmp == *(ULONG *) ".TGA" || cmp == *(ULONG *) ".XBM" ||
155 cmp == *(ULONG *) ".JPEG" || cmp == *(ULONG *) ".JPG" ||
156 cmp == *(ULONG *) ".PNG" || cmp == *(ULONG *) ".PSD" ||
157 cmp == *(ULONG *) ".LGO" || cmp == *(ULONG *) ".EPS" ||
158 cmp == *(ULONG *) ".RLE" || cmp == *(ULONG *) ".RAS" ||
159 cmp == *(ULONG *) ".PLC" || cmp == *(ULONG *) ".MSP" ||
160 cmp == *(ULONG *) ".IFF" || cmp == *(ULONG *) ".FIT" ||
161 cmp == *(ULONG *) ".DCX" || cmp == *(ULONG *) ".MAC" ||
162 cmp == *(ULONG *) ".SFF" || cmp == *(ULONG *) ".SGI" ||
163 cmp == *(ULONG *) ".XWD" || cmp == *(ULONG *) ".XPM" ||
164 cmp == *(ULONG *) ".WPG" || cmp == *(ULONG *) ".CUR" ||
165 cmp == *(ULONG *) ".PNM" || cmp == *(ULONG *) ".PPM" ||
166 cmp == *(ULONG *) ".PGM" || cmp == *(ULONG *) ".PBM")
167 hptr = hptrArt;
168 else
169 hptr = (HPOINTER) 0;
170 }
171 else
172 hptr = (HPOINTER) 0;
173
174 return hptr;
175}
176
177static BOOL IsDefaultIcon(HPOINTER hptr)
178{
179 HPOINTER hptr2;
180 HPOINTER hptr3;
181 UINT u;
182
183 static HPOINTER hptrPMFile;
184 static HPOINTER hptrWPSFile;
185
186 if (!hptrPMFile) {
187 hptrPMFile = WinQuerySysPointer(HWND_DESKTOP, SPTR_FILE, FALSE);
188 }
189
190 // try to guess WPS default file icon
191 hptr2 = (HPOINTER) 0;
192 for (u = 0; !hptrWPSFile && u < 10; u++) {
193 char szFileName[CCHMAXPATH];
194 char *psz;
195
196 psz = getenv("TMP");
197 if (!psz && *psz)
198 psz = getenv("TEMP");
199 if (psz && *psz) {
200 strcpy(szFileName, psz);
201 psz = szFileName + strlen(szFileName) - 1;
202 if (*psz != '\\') {
203 psz++;
204 *psz++ = '\\';
205 }
206 }
207 else
208 psz = szFileName;
209
210 sprintf(psz, "%08x.%03x", rand() & 0xffffffff, rand() & 0xfff);
211 if (IsFile(szFileName) != 1) {
212 FILE *fp = fopen(szFileName, "w");
213
214 if (fp) {
215 fclose(fp);
216 hptr3 = WinLoadFileIcon(szFileName, FALSE);
217 unlinkf("%s", szFileName);
218 if (!hptr2)
219 hptr2 = hptr3;
220 else if (hptr3 == hptr3) {
221 hptrWPSFile = hptr3; // Got same icon twice
222 break;
223 }
224 }
225 }
226 DosSleep(rand() % 100);
227
228 } // for
229
230 return hptr == hptrPMFile || hptr == hptrWPSFile;
231
232} // IsDefaultIcon
233
234ULONGLONG FillInRecordFromFFB(HWND hwndCnr,
235 PCNRITEM pci,
236 const PSZ pszDirectory,
237 const PFILEFINDBUF4 pffb,
238 const BOOL partial,
239 DIRCNRDATA *dcd)
240{
241 /* fill in a container record from a FILEFINDBUF4 structure */
242
243 CHAR *p;
244 HPOINTER hptr;
245
246 pci->hwndCnr = hwndCnr;
247
248 /* note that we cheat below, and accept the full pathname in pszDirectory
249 if !*pffb->achName. This speeds up and simplifies processing elsewhere
250 (like in update.c)
251 */
252 if (!*pffb->achName) {
253 pci->pszFileName = xstrdup(pszDirectory, pszSrcFile, __LINE__);
254 strcpy(pci->pszFileName, pszDirectory);
255 }
256 else {
257 INT c = strlen(pszDirectory);
258 INT c2 = pffb->cchName + 1;
259 if (pszDirectory[c - 1] != '\\')
260 c2++;
261 pci->pszFileName = xmalloc(c + c2, pszSrcFile, __LINE__);
262 memcpy(pci->pszFileName, pszDirectory, c + 1);
263 p = pci->pszFileName + c - 1;
264 if (*p != '\\') {
265 p++;
266 *p = '\\';
267 }
268 p++;
269 memcpy(p, pffb->achName, pffb->cchName + 1);
270 }
271
272 /* load the object's Subject, if required */
273 pci->pszSubject = NullStr;
274 if (pffb->cbList > 4L &&
275 dcd && fLoadSubject &&
276 (isalpha(*pci->pszFileName) &&
277 !(driveflags[toupper(*pci->pszFileName) - 'A'] & DRIVE_NOLOADSUBJS)))
278 {
279 APIRET rc;
280 EAOP2 eaop;
281 PGEA2LIST pgealist;
282 PFEA2LIST pfealist;
283 PGEA2 pgea;
284 PFEA2 pfea;
285 CHAR *value;
286
287 pgealist = xmallocz(sizeof(GEA2LIST) + 32, pszSrcFile, __LINE__);
288 if (pgealist) {
289 pgea = &pgealist->list[0];
290 strcpy(pgea->szName, SUBJECT);
291 pgea->cbName = strlen(pgea->szName);
292 pgea->oNextEntryOffset = 0;
293 pgealist->cbList = (sizeof(GEA2LIST) + pgea->cbName);
294 pfealist = xmallocz(1532, pszSrcFile, __LINE__);
295 if (pfealist) {
296 pfealist->cbList = 1024;
297 eaop.fpGEA2List = pgealist;
298 eaop.fpFEA2List = pfealist;
299 eaop.oError = 0;
300 rc = DosQueryPathInfo(pci->pszFileName, FIL_QUERYEASFROMLIST,
301 (PVOID) & eaop, (ULONG) sizeof(EAOP2));
302 if (!rc) {
303 pfea = &eaop.fpFEA2List->list[0];
304 value = pfea->szName + pfea->cbName + 1;
305 value[pfea->cbValue] = 0;
306 if (*(USHORT *) value == EAT_ASCII)
307 pci->pszSubject = xstrdup(value + (sizeof(USHORT) * 2), pszSrcFile, __LINE__);
308 }
309 free(pfealist);
310 }
311 free(pgealist);
312 }
313 }
314 if (!pci->pszSubject)
315 pci->pszSubject = NullStr;
316
317 /* load the object's longname */
318 pci->pszLongName = 0;
319 if (fLoadLongnames &&
320 dcd &&
321 pffb->cbList > 4L &&
322 isalpha(*pci->pszFileName) &&
323 ~driveflags[toupper(*pci->pszFileName) - 'A'] & DRIVE_NOLONGNAMES &&
324 ~driveflags[toupper(*pci->pszFileName) - 'A'] & DRIVE_NOLOADLONGS)
325 {
326 APIRET rc;
327 EAOP2 eaop;
328 PGEA2LIST pgealist;
329 PFEA2LIST pfealist;
330 PGEA2 pgea;
331 PFEA2 pfea;
332 CHAR *value;
333
334 pgealist = xmallocz(sizeof(GEA2LIST) + 32, pszSrcFile, __LINE__);
335 if (pgealist) {
336 pgea = &pgealist->list[0];
337 strcpy(pgea->szName, LONGNAME);
338 pgea->cbName = strlen(pgea->szName);
339 pgea->oNextEntryOffset = 0;
340 pgealist->cbList = (sizeof(GEA2LIST) + pgea->cbName);
341 pfealist = xmallocz(1532, pszSrcFile, __LINE__);
342 if (pfealist) {
343 pfealist->cbList = 1024;
344 eaop.fpGEA2List = pgealist;
345 eaop.fpFEA2List = pfealist;
346 eaop.oError = 0;
347 rc = DosQueryPathInfo(pci->pszFileName, FIL_QUERYEASFROMLIST,
348 (PVOID) & eaop, (ULONG) sizeof(EAOP2));
349 if (!rc) {
350 pfea = &eaop.fpFEA2List->list[0];
351 value = pfea->szName + pfea->cbName + 1;
352 value[pfea->cbValue] = 0;
353 if (*(USHORT *) value == EAT_ASCII)
354 pci->pszLongName = xstrdup(value + (sizeof(USHORT) * 2), pszSrcFile, __LINE__);
355 }
356 free(pfealist);
357 }
358 free(pgealist);
359 }
360 }
361 if (!pci->pszLongName)
362 pci->pszLongName = NullStr;
363
364 /* do anything required to case of filename */
365 if (fForceUpper)
366 strupr(pci->pszFileName);
367 else if (fForceLower)
368 strlwr(pci->pszFileName);
369
370 /* get an icon to use with it */
371 if (pffb->attrFile & FILE_DIRECTORY) {
372 // is directory
373 if (fNoIconsDirs ||
374 (driveflags[toupper(*pci->pszFileName) - 'A'] & DRIVE_NOLOADICONS) ||
375 !isalpha(*pci->pszFileName)) {
376 hptr = (HPOINTER) 0;
377 }
378 else
379 hptr = WinLoadFileIcon(pci->pszFileName, FALSE);
380 }
381 else {
382 // is file
383 if (fNoIconsFiles ||
384 (driveflags[toupper(*pci->pszFileName) - 'A'] & DRIVE_NOLOADICONS) ||
385 !isalpha(*pci->pszFileName)) {
386 hptr = (HPOINTER) 0;
387 }
388 else
389 hptr = WinLoadFileIcon(pci->pszFileName, FALSE);
390
391 if (!hptr || IsDefaultIcon(hptr))
392 hptr = IDFile(pci->pszFileName);
393 }
394
395 if (!hptr) {
396 hptr = pffb->attrFile & FILE_DIRECTORY ?
397 hptrDir : pffb->attrFile & FILE_SYSTEM ?
398 hptrSystem :
399 pffb->attrFile & FILE_HIDDEN ?
400 hptrHidden :
401 pffb->attrFile & FILE_READONLY ?
402 hptrReadonly : hptrFile;
403 }
404
405 // Tell container what part of pathname to display
406 if (partial) {
407 p = strrchr(pci->pszFileName, '\\');
408 if (!p) {
409 p = strrchr(pci->pszFileName, ':');
410 if (!p)
411 p = pci->pszFileName;
412 else
413 p++;
414 }
415 else if ((dcd && dcd->type == TREE_FRAME) ||
416 (!(pffb->attrFile & FILE_DIRECTORY) || !*(p + 1))) {
417 p++;
418 }
419 if (!*p)
420 p = pci->pszFileName;
421 }
422 else
423 p = pci->pszFileName;
424 pci->pszDisplayName = p;
425
426 /* now fill the darned thing in... */
427 pci->date.day = pffb->fdateLastWrite.day;
428 pci->date.month = pffb->fdateLastWrite.month;
429 pci->date.year = pffb->fdateLastWrite.year + 1980;
430 pci->time.seconds = pffb->ftimeLastWrite.twosecs * 2;
431 pci->time.minutes = pffb->ftimeLastWrite.minutes;
432 pci->time.hours = pffb->ftimeLastWrite.hours;
433 pci->ladate.day = pffb->fdateLastAccess.day;
434 pci->ladate.month = pffb->fdateLastAccess.month;
435 pci->ladate.year = pffb->fdateLastAccess.year + 1980;
436 pci->latime.seconds = pffb->ftimeLastAccess.twosecs * 2;
437 pci->latime.minutes = pffb->ftimeLastAccess.minutes;
438 pci->latime.hours = pffb->ftimeLastAccess.hours;
439 pci->crdate.day = pffb->fdateCreation.day;
440 pci->crdate.month = pffb->fdateCreation.month;
441 pci->crdate.year = pffb->fdateCreation.year + 1980;
442 pci->crtime.seconds = pffb->ftimeCreation.twosecs * 2;
443 pci->crtime.minutes = pffb->ftimeCreation.minutes;
444 pci->crtime.hours = pffb->ftimeCreation.hours;
445 pci->easize = CBLIST_TO_EASIZE(pffb->cbList);
446 pci->cbFile = pffb->cbFile;
447 pci->attrFile = pffb->attrFile;
448 pci->pszDispAttr = FileAttrToString(pci->attrFile);
449 pci->rc.pszIcon = pci->pszDisplayName;
450 pci->rc.hptrIcon = hptr;
451
452 /* check to see if record should be visible */
453 if (dcd && (*dcd->mask.szMask || dcd->mask.antiattr ||
454 ((dcd->mask.attrFile &
455 (FILE_HIDDEN | FILE_SYSTEM | FILE_READONLY | FILE_ARCHIVED))
456 !=
457 (FILE_HIDDEN | FILE_SYSTEM | FILE_READONLY | FILE_ARCHIVED))))
458 {
459 if (*dcd->mask.szMask || dcd->mask.antiattr) {
460 if (!Filter((PMINIRECORDCORE) pci, (PVOID) & dcd->mask))
461 pci->rc.flRecordAttr |= CRA_FILTERED;
462 }
463 else if ((!(dcd->mask.attrFile & FILE_HIDDEN) &&
464 (pci->attrFile & FILE_HIDDEN)) ||
465 (!(dcd->mask.attrFile & FILE_SYSTEM) &&
466 (pci->attrFile & FILE_SYSTEM)) ||
467 (!(dcd->mask.attrFile & FILE_READONLY) &&
468 (pci->attrFile & FILE_READONLY)) ||
469 (!(dcd->mask.attrFile & FILE_ARCHIVED) &&
470 (pci->attrFile & FILE_ARCHIVED))) {
471 pci->rc.flRecordAttr |= CRA_FILTERED;
472 }
473 }
474
475 return pffb->cbFile + pci->easize;
476
477} // FillInRecordFromFFB
478
479ULONGLONG FillInRecordFromFSA(HWND hwndCnr, PCNRITEM pci, const PSZ pszFileName, const PFILESTATUS4 pfsa4, const BOOL partial, DIRCNRDATA * dcd) // Optional
480{
481 HPOINTER hptr;
482 CHAR *p;
483
484 /* fill in a container record from a FILESTATUS4 structure */
485
486 pci->hwndCnr = hwndCnr;
487 pci->pszFileName = xstrdup(pszFileName, pszSrcFile, __LINE__);
488 strcpy(pci->pszFileName, pszFileName);
489
490 /* load the object's Subject, if required */
491 pci->pszSubject = NullStr;
492 if (pfsa4->cbList > 4L &&
493 dcd &&
494 fLoadSubject &&
495 (!isalpha(*pci->pszFileName) ||
496 !(driveflags[toupper(*pci->pszFileName) - 'A'] & DRIVE_NOLOADSUBJS)))
497 {
498 APIRET rc;
499 EAOP2 eaop;
500 PGEA2LIST pgealist;
501 PFEA2LIST pfealist;
502 PGEA2 pgea;
503 PFEA2 pfea;
504 CHAR *value;
505
506 pgealist = xmallocz(sizeof(GEA2LIST) + 32, pszSrcFile, __LINE__);
507 if (pgealist) {
508 pgea = &pgealist->list[0];
509 strcpy(pgea->szName, SUBJECT);
510 pgea->cbName = strlen(pgea->szName);
511 pgea->oNextEntryOffset = 0;
512 pgealist->cbList = (sizeof(GEA2LIST) + pgea->cbName);
513 pfealist = xmallocz(1532, pszSrcFile, __LINE__);
514 if (pfealist) {
515 pfealist->cbList = 1024;
516 eaop.fpGEA2List = pgealist;
517 eaop.fpFEA2List = pfealist;
518 eaop.oError = 0;
519 rc = DosQueryPathInfo(pci->pszFileName, FIL_QUERYEASFROMLIST,
520 (PVOID) & eaop, (ULONG) sizeof(EAOP2));
521 if (!rc) {
522 pfea = &eaop.fpFEA2List->list[0];
523 value = pfea->szName + pfea->cbName + 1;
524 value[pfea->cbValue] = 0;
525 if (*(USHORT *) value == EAT_ASCII)
526 pci->pszSubject = xstrdup(value + (sizeof(USHORT) * 2), pszSrcFile, __LINE__);
527 }
528 free(pfealist);
529 }
530 free(pgealist);
531 }
532 }
533 if (!pci->pszSubject)
534 pci->pszSubject = NullStr;
535
536 pci->pszLongName = 0;
537 if (fLoadLongnames &&
538 dcd &&
539 pfsa4->cbList > 4L &&
540 isalpha(*pci->pszFileName) &&
541 ~driveflags[toupper(*pci->pszFileName) - 'A'] & DRIVE_NOLONGNAMES &&
542 ~driveflags[toupper(*pci->pszFileName) - 'A'] & DRIVE_NOLOADLONGS)
543 {
544 APIRET rc;
545 EAOP2 eaop;
546 PGEA2LIST pgealist;
547 PFEA2LIST pfealist;
548 PGEA2 pgea;
549 PFEA2 pfea;
550 CHAR *value;
551
552 pgealist = xmallocz(sizeof(GEA2LIST) + 32, pszSrcFile, __LINE__);
553 if (pgealist) {
554 pgea = &pgealist->list[0];
555 strcpy(pgea->szName, LONGNAME);
556 pgea->cbName = strlen(pgea->szName);
557 pgea->oNextEntryOffset = 0;
558 pgealist->cbList = (sizeof(GEA2LIST) + pgea->cbName);
559 pfealist = xmallocz(1532, pszSrcFile, __LINE__);
560 if (pfealist) {
561 pfealist->cbList = 1024;
562 eaop.fpGEA2List = pgealist;
563 eaop.fpFEA2List = pfealist;
564 eaop.oError = 0;
565 rc = DosQueryPathInfo(pci->pszFileName, FIL_QUERYEASFROMLIST,
566 (PVOID) & eaop, (ULONG) sizeof(EAOP2));
567 if (!rc) {
568 pfea = &eaop.fpFEA2List->list[0];
569 value = pfea->szName + pfea->cbName + 1; // Point at EA value
570 value[pfea->cbValue] = 0; // Terminate
571 if (*(USHORT *) value == EAT_ASCII) {
572 p = value + sizeof(USHORT) * 2; // Point at value string
573 pci->pszLongName = xstrdup(p, pszSrcFile, __LINE__);
574 }
575 }
576 free(pfealist);
577 }
578 free(pgealist);
579 }
580 }
581 if (!pci->pszLongName)
582 pci->pszLongName = NullStr;
583
584 if (fForceUpper)
585 strupr(pci->pszFileName);
586 else if (fForceLower)
587 strlwr(pci->pszFileName);
588
589 if (pfsa4->attrFile & FILE_DIRECTORY) {
590 if (fNoIconsDirs ||
591 (driveflags[toupper(*pci->pszFileName) - 'A'] & DRIVE_NOLOADICONS) ||
592 !isalpha(*pci->pszFileName)) {
593 hptr = (HPOINTER) 0;
594 }
595 else
596 hptr = WinLoadFileIcon(pci->pszFileName, FALSE);
597 }
598 else {
599 if (fNoIconsFiles ||
600 (driveflags[toupper(*pci->pszFileName) - 'A'] & DRIVE_NOLOADICONS) ||
601 !isalpha(*pci->pszFileName)) {
602 hptr = IDFile(pci->pszFileName);
603 }
604 else
605 hptr = WinLoadFileIcon(pci->pszFileName, FALSE);
606 }
607 if (!hptr) {
608 hptr = pfsa4->attrFile & FILE_DIRECTORY ?
609 hptrDir :
610 pfsa4->attrFile & FILE_SYSTEM ?
611 hptrSystem :
612 pfsa4->attrFile & FILE_HIDDEN ?
613 hptrHidden : pfsa4->attrFile & FILE_READONLY ? hptrReadonly : hptrFile;
614 }
615
616 // Tell container what part of pathname to display
617 if (partial) {
618 p = strrchr(pci->pszFileName, '\\');
619 if (!p) {
620 p = strrchr(pci->pszFileName, ':');
621 if (!p)
622 p = pci->pszFileName;
623 else
624 p++;
625 }
626 else if ((dcd && dcd->type == TREE_FRAME) ||
627 !(pfsa4->attrFile & FILE_DIRECTORY) || !*(p + 1))
628 p++;
629 if (!*p)
630 p = pci->pszFileName;
631 }
632 else
633 p = pci->pszFileName;
634 pci->pszDisplayName = p;
635
636 pci->date.day = pfsa4->fdateLastWrite.day;
637 pci->date.month = pfsa4->fdateLastWrite.month;
638 pci->date.year = pfsa4->fdateLastWrite.year + 1980;
639 pci->time.seconds = pfsa4->ftimeLastWrite.twosecs * 2;
640 pci->time.minutes = pfsa4->ftimeLastWrite.minutes;
641 pci->time.hours = pfsa4->ftimeLastWrite.hours;
642 pci->ladate.day = pfsa4->fdateLastAccess.day;
643 pci->ladate.month = pfsa4->fdateLastAccess.month;
644 pci->ladate.year = pfsa4->fdateLastAccess.year + 1980;
645 pci->latime.seconds = pfsa4->ftimeLastAccess.twosecs * 2;
646 pci->latime.minutes = pfsa4->ftimeLastAccess.minutes;
647 pci->latime.hours = pfsa4->ftimeLastAccess.hours;
648 pci->crdate.day = pfsa4->fdateCreation.day;
649 pci->crdate.month = pfsa4->fdateCreation.month;
650 pci->crdate.year = pfsa4->fdateCreation.year + 1980;
651 pci->crtime.seconds = pfsa4->ftimeCreation.twosecs * 2;
652 pci->crtime.minutes = pfsa4->ftimeCreation.minutes;
653 pci->crtime.hours = pfsa4->ftimeCreation.hours;
654 pci->easize = CBLIST_TO_EASIZE(pfsa4->cbList);
655 pci->cbFile = pfsa4->cbFile;
656 pci->attrFile = pfsa4->attrFile;
657 pci->pszDispAttr = FileAttrToString(pci->attrFile);
658 pci->rc.pszIcon = pci->pszDisplayName;
659 pci->rc.hptrIcon = hptr;
660
661 if (dcd &&
662 (*dcd->mask.szMask || dcd->mask.antiattr ||
663 ((dcd->mask.attrFile &
664 (FILE_HIDDEN | FILE_SYSTEM | FILE_READONLY | FILE_ARCHIVED)) !=
665 (FILE_HIDDEN | FILE_SYSTEM | FILE_READONLY | FILE_ARCHIVED)))) {
666 if (*dcd->mask.szMask || dcd->mask.antiattr) {
667 if (!Filter((PMINIRECORDCORE) pci, (PVOID) & dcd->mask))
668 pci->rc.flRecordAttr |= CRA_FILTERED;
669 }
670 else if ((!(dcd->mask.attrFile & FILE_HIDDEN) &&
671 (pci->attrFile & FILE_HIDDEN)) ||
672 (!(dcd->mask.attrFile & FILE_SYSTEM) &&
673 (pci->attrFile & FILE_SYSTEM)) ||
674 (!(dcd->mask.attrFile & FILE_READONLY) &&
675 (pci->attrFile & FILE_READONLY)) ||
676 (!(dcd->mask.attrFile & FILE_ARCHIVED) &&
677 (pci->attrFile & FILE_ARCHIVED)))
678 pci->rc.flRecordAttr |= CRA_FILTERED;
679 }
680
681 return pfsa4->cbFile + pci->easize;
682
683} // FillInRecordFromFSA
684
685VOID ProcessDirectory(const HWND hwndCnr,
686 const PCNRITEM pciParent,
687 const CHAR *szDirBase,
688 const BOOL filestoo,
689 const BOOL recurse,
690 const BOOL partial,
691 CHAR *stopflag,
692 DIRCNRDATA *dcd, // Optional
693 ULONG *pulTotalFiles, // Optional
694 PULONGLONG pullTotalBytes) // Optional
695{
696 /* put all the directories (and files if filestoo is TRUE) from a
697 * directory into the container. recurse through subdirectories if
698 * recurse is TRUE.
699 */
700
701 PSZ pszFileSpec;
702 INT t;
703 PFILEFINDBUF4 paffbFound;
704 PFILEFINDBUF4 *papffbSelected;
705 PFILEFINDBUF4 pffbFile;
706 PFILEFINDBUF4 paffbTotal = NULL;
707 PFILEFINDBUF4 paffbTemp;
708 HDIR hdir = HDIR_CREATE;
709 ULONG ulFileCnt;
710 ULONG ulExtraBytes;
711 ULONG ulM = 1;
712 ULONG ulTotal = 0;
713 ULONGLONG ullBytes;
714 ULONGLONG ullTotalBytes;
715 ULONG ulReturnFiles = 0;
716 ULONGLONG ullReturnBytes = 0;
717 PCH pchEndPath;
718 APIRET rc;
719 PCNRITEM pci;
720 PCNRITEM pciFirst;
721 RECORDINSERT ri;
722 PBYTE pByte;
723 PBYTE pByte2;
724 BOOL ok = TRUE;
725
726 if (isalpha(*szDirBase) && szDirBase[1] == ':' && szDirBase[2] == '\\') {
727 ulExtraBytes = EXTRA_RECORD_BYTES;
728 if ((driveflags[toupper(*szDirBase) - 'A'] & DRIVE_REMOTE) && fRemoteBug)
729 ulM = 1; /* file system gets confused */
730 else if (driveflags[toupper(*szDirBase) - 'A'] & DRIVE_ZIPSTREAM)
731 ulM = min(FilesToGet, 225); /* anything more is wasted */
732 else
733 ulM = FilesToGet; /* full-out */
734 }
735 else {
736 ulExtraBytes = EXTRA_RECORD_BYTES;
737 ulM = FilesToGet;
738 }
739 if (OS2ver[0] == 20 && OS2ver[1] < 30)
740 ulM = min(ulM, (65535 / sizeof(FILEFINDBUF4)));
741
742 ulFileCnt = ulM;
743 pszFileSpec = xmalloc(CCHMAXPATH + 2, pszSrcFile, __LINE__);
744 paffbFound =
745 xmalloc((ulM + 1) * sizeof(FILEFINDBUF4), pszSrcFile, __LINE__);
746 papffbSelected =
747 xmalloc((ulM + 1) * sizeof(PFILEFINDBUF4), pszSrcFile, __LINE__);
748 if (paffbFound && papffbSelected && pszFileSpec) {
749 t = strlen(szDirBase);
750 memcpy(pszFileSpec, szDirBase, t + 1);
751 pchEndPath = pszFileSpec + t;
752 if (*(pchEndPath - 1) != '\\') {
753 memcpy(pchEndPath, "\\", 2);
754 pchEndPath++;
755 }
756 memcpy(pchEndPath, "*", 2);
757 DosError(FERR_DISABLEHARDERR);
758 rc = DosFindFirst(pszFileSpec, &hdir,
759 FILE_NORMAL | ((filestoo) ? FILE_DIRECTORY :
760 MUST_HAVE_DIRECTORY) | FILE_READONLY |
761 FILE_ARCHIVED | FILE_SYSTEM | FILE_HIDDEN,
762 paffbFound, ulM * sizeof(FILEFINDBUF4),
763 &ulFileCnt, FIL_QUERYEASIZE);
764 priority_normal();
765 *pchEndPath = 0;
766 if (!rc) {
767 while (!rc) {
768 /*
769 * remove . and .. from list if present
770 * also counter file system bugs that sometimes
771 * allows normal files to slip through when
772 * only directories should appear (only a few
773 * network file systems exhibit such a problem).
774 */
775 register ULONG x;
776
777 if (stopflag && *stopflag)
778 goto Abort;
779 pByte = (PBYTE) paffbFound;
780 for (x = 0; x < ulFileCnt;) {
781 pffbFile = (PFILEFINDBUF4) pByte;
782 if (!*pffbFile->achName ||
783 (!filestoo && !(pffbFile->attrFile & FILE_DIRECTORY)) ||
784 ((pffbFile->attrFile & FILE_DIRECTORY) &&
785 pffbFile->achName[0] == '.' &&
786 (!pffbFile->achName[1] ||
787 (pffbFile->achName[1] == '.' && !pffbFile->achName[2])))) {
788 ulFileCnt--; // Got . or ..
789 }
790 else
791 papffbSelected[x++] = pffbFile; // Count file
792 if (!pffbFile->oNextEntryOffset) {
793 ulFileCnt = x; // Adjust count
794 break;
795 }
796 pByte += pffbFile->oNextEntryOffset;
797 } // for
798 if (ulFileCnt) {
799 if (stopflag && *stopflag)
800 goto Abort;
801 if (fSyncUpdates) {
802 pciFirst = WinSendMsg(hwndCnr, CM_ALLOCRECORD,
803 MPFROMLONG(ulExtraBytes),
804 MPFROMLONG(ulFileCnt));
805 if (!pciFirst) {
806 Win_Error2(hwndCnr, HWND_DESKTOP, pszSrcFile, __LINE__,
807 IDS_CMALLOCRECERRTEXT);
808 ok = FALSE;
809 ullTotalBytes = 0;
810 }
811 else {
812 register INT i;
813
814 pci = pciFirst;
815 ullTotalBytes = 0;
816 for (i = 0; i < ulFileCnt; i++) {
817 pffbFile = papffbSelected[i];
818 ullBytes = FillInRecordFromFFB(hwndCnr, pci, pszFileSpec,
819 pffbFile, partial, dcd);
820 pci = (PCNRITEM) pci->rc.preccNextRecord;
821 ullTotalBytes += ullBytes;
822 } // for
823 if (ulFileCnt) {
824 memset(&ri, 0, sizeof(RECORDINSERT));
825 ri.cb = sizeof(RECORDINSERT);
826 ri.pRecordOrder = (PRECORDCORE) CMA_END;
827 ri.pRecordParent = (PRECORDCORE) pciParent;
828 ri.zOrder = (ULONG) CMA_TOP;
829 ri.cRecordsInsert = ulFileCnt;
830 ri.fInvalidateRecord = (!fSyncUpdates && dcd &&
831 dcd->type == DIR_FRAME) ?
832 FALSE : TRUE;
833 if (!WinSendMsg(hwndCnr,
834 CM_INSERTRECORD,
835 MPFROMP(pciFirst), MPFROMP(&ri))) {
836 DosSleep(10);
837 WinSetFocus(HWND_DESKTOP, hwndCnr);
838 if (!WinSendMsg(hwndCnr,
839 CM_INSERTRECORD,
840 MPFROMP(pciFirst), MPFROMP(&ri))) {
841 Win_Error2(hwndCnr, HWND_DESKTOP, pszSrcFile, __LINE__,
842 IDS_CMINSERTERRTEXT);
843 ok = FALSE;
844 ullTotalBytes = 0;
845 if (WinIsWindow((HAB) 0, hwndCnr))
846 FreeCnrItemList(hwndCnr, pciFirst);
847 }
848 }
849 }
850 }
851 if (ok) {
852 ullReturnBytes += ullTotalBytes;
853 ulReturnFiles += ulFileCnt;
854 }
855 }
856 else {
857 paffbTemp = xrealloc(paffbTotal,
858 sizeof(FILEFINDBUF4) * (ulFileCnt + ulTotal),
859 pszSrcFile, __LINE__);
860 if (paffbTemp) {
861 paffbTotal = paffbTemp;
862 for (x = 0; x < ulFileCnt; x++)
863 paffbTotal[x + ulTotal] = *papffbSelected[x];
864 ulTotal += ulFileCnt;
865 }
866 else {
867 saymsg(MB_ENTER,
868 HWND_DESKTOP,
869 GetPString(IDS_ERRORTEXT), GetPString(IDS_OUTOFMEMORY));
870 break;
871 }
872 }
873 }
874 if (stopflag && *stopflag)
875 goto Abort;
876 ulFileCnt = ulM;
877 DosError(FERR_DISABLEHARDERR);
878 rc = DosFindNext(hdir, paffbFound, ulM * sizeof(FILEFINDBUF4),
879 &ulFileCnt);
880 priority_normal();
881 if (rc)
882 DosError(FERR_DISABLEHARDERR);
883 }
884 DosFindClose(hdir);
885
886 if (paffbFound || papffbSelected) {
887 if (paffbFound)
888 free(paffbFound);
889 if (papffbSelected)
890 free(papffbSelected);
891 papffbSelected = NULL;
892 paffbFound = NULL;
893 }
894
895 if (ulTotal && paffbTotal) {
896
897 if (stopflag && *stopflag)
898 goto Abort;
899
900 pciFirst = WinSendMsg(hwndCnr, CM_ALLOCRECORD,
901 MPFROMLONG(ulExtraBytes), MPFROMLONG(ulTotal));
902 if (!pciFirst) {
903 Win_Error2(hwndCnr, HWND_DESKTOP, pszSrcFile, __LINE__,
904 IDS_CMALLOCRECERRTEXT);
905 ok = FALSE;
906 ullTotalBytes = 0;
907 }
908 else {
909 register INT i;
910
911 pci = pciFirst;
912 ullTotalBytes = 0;
913 pByte2 = (PBYTE) paffbTotal;
914 for (i = 0; i < ulTotal; i++) {
915 pffbFile = (PFILEFINDBUF4) pByte2;
916 ullBytes = FillInRecordFromFFB(hwndCnr, pci, pszFileSpec,
917 pffbFile, partial, dcd);
918 pci = (PCNRITEM) pci->rc.preccNextRecord;
919 ullTotalBytes += ullBytes;
920
921 pByte2 += sizeof(FILEFINDBUF4);
922 }
923 if (ulTotal) {
924 memset(&ri, 0, sizeof(RECORDINSERT));
925 ri.cb = sizeof(RECORDINSERT);
926 ri.pRecordOrder = (PRECORDCORE) CMA_END;
927 ri.pRecordParent = (PRECORDCORE) pciParent;
928 ri.zOrder = (ULONG) CMA_TOP;
929 ri.cRecordsInsert = ulTotal;
930 ri.fInvalidateRecord = (!fSyncUpdates && dcd &&
931 dcd->type == DIR_FRAME) ? FALSE : TRUE;
932 if (!WinSendMsg(hwndCnr, CM_INSERTRECORD,
933 MPFROMP(pciFirst), MPFROMP(&ri))) {
934 DosSleep(10);
935 WinSetFocus(HWND_DESKTOP, hwndCnr);
936 if (!WinSendMsg(hwndCnr, CM_INSERTRECORD,
937 MPFROMP(pciFirst), MPFROMP(&ri))) {
938 Win_Error2(hwndCnr, HWND_DESKTOP, pszSrcFile, __LINE__,
939 IDS_CMINSERTERRTEXT);
940 ok = FALSE;
941 ullTotalBytes = 0;
942 if (WinIsWindow((HAB) 0, hwndCnr))
943 FreeCnrItemList(hwndCnr, pciFirst);
944 }
945 }
946 }
947 }
948 if (ok) {
949 ullReturnBytes += ullTotalBytes;
950 ulReturnFiles += ulFileCnt;
951 }
952 }
953 }
954
955 if (!fSyncUpdates && dcd && dcd->type == DIR_FRAME)
956 WinSendMsg(hwndCnr, CM_INVALIDATERECORD, MPVOID,
957 MPFROM2SHORT(0, CMA_ERASE));
958 }
959Abort:
960 if (paffbTotal || papffbSelected || paffbFound || pszFileSpec) {
961 if (paffbTotal)
962 free(paffbTotal);
963 if (pszFileSpec)
964 free(pszFileSpec);
965 if (paffbFound)
966 free(paffbFound);
967 if (papffbSelected)
968 free(papffbSelected);
969 }
970 if (recurse) {
971 pci = WinSendMsg(hwndCnr, CM_QUERYRECORD, MPFROMP(pciParent),
972 MPFROM2SHORT(CMA_FIRSTCHILD, CMA_ITEMORDER));
973 while (pci && (INT)pci != -1) {
974 if (pci->attrFile & FILE_DIRECTORY)
975 Stubby(hwndCnr, pci);
976 pci = WinSendMsg(hwndCnr, CM_QUERYRECORD, MPFROMP(pci),
977 MPFROM2SHORT(CMA_NEXT, CMA_ITEMORDER));
978 }
979 }
980
981 if (pulTotalFiles)
982 *pulTotalFiles = ulReturnFiles;
983
984 if (pullTotalBytes)
985 *pullTotalBytes = ullReturnBytes;
986
987} // ProcessDirectory
988
989VOID FillDirCnr(HWND hwndCnr,
990 CHAR * pszDirectory,
991 DIRCNRDATA * dcd, PULONGLONG pullTotalBytes)
992{
993 ProcessDirectory(hwndCnr,
994 (PCNRITEM) NULL,
995 pszDirectory,
996 TRUE, // filestoo
997 FALSE, // recurse
998 TRUE, // partial
999 dcd ? &dcd->stopflag : NULL,
1000 dcd,
1001 NULL,
1002 pullTotalBytes);
1003 DosPostEventSem(CompactSem);
1004
1005#if 0 // fixme to be gone or to be configurable
1006 {
1007 int state = _heapchk();
1008 if (state != _HEAPOK)
1009 Runtime_Error(pszSrcFile, __LINE__, "heap corrupted %d", state);
1010 else
1011 DbgMsg(pszSrcFile, __LINE__, "_memavl %u", _memavl());
1012 }
1013#endif
1014
1015} // FillDirCnr
1016
1017VOID FillTreeCnr(HWND hwndCnr, HWND hwndParent)
1018{
1019 ULONG ulCurDriveNum, ulDriveMap, numtoinsert = 0, drvtype;
1020 PCNRITEM pci, pciFirst = NULL, pciNext, pciParent = NULL;
1021 INT x, removable;
1022 CHAR suggest[32];
1023 CHAR szDrive[] = " :\\";
1024 CHAR szFileSystem[CCHMAXPATH];
1025 FILESTATUS4 fsa4;
1026 APIRET rc;
1027 BOOL drivesbuilt = FALSE;
1028 ULONG startdrive = 3;
1029
1030 static BOOL didonce = FALSE;
1031
1032 fDummy = TRUE;
1033 *suggest = 0;
1034 for (x = 0; x < 26; x++) {
1035 driveflags[x] &= (DRIVE_IGNORE | DRIVE_NOPRESCAN | DRIVE_NOLOADICONS |
1036 DRIVE_NOLOADSUBJS | DRIVE_NOLOADLONGS |
1037 DRIVE_INCLUDEFILES | DRIVE_SLOW | DRIVE_NOSTATS);
1038 }
1039 memset(driveserial, -1, sizeof(driveserial));
1040
1041 DosError(FERR_DISABLEHARDERR);
1042 if (!DosQuerySysInfo(QSV_BOOT_DRIVE,
1043 QSV_BOOT_DRIVE,
1044 (PVOID) &startdrive,
1045 (ULONG) sizeof(ULONG)) &&
1046 startdrive)
1047 {
1048 driveflags[startdrive - 1] |= DRIVE_BOOT;
1049 }
1050
1051 DosError(FERR_DISABLEHARDERR);
1052 rc = DosQCurDisk(&ulCurDriveNum, &ulDriveMap);
1053 if (rc) {
1054 Dos_Error(MB_CANCEL,
1055 rc,
1056 HWND_DESKTOP,
1057 pszSrcFile, __LINE__, GetPString(IDS_FILLDIRQCURERRTEXT));
1058 exit(0);
1059 }
1060
1061 // Calc number of drive items to create
1062 for (x = 0; x < 26; x++) {
1063 if ((ulDriveMap & (1L << x)) && !(driveflags[x] & DRIVE_IGNORE))
1064 numtoinsert++;
1065 }
1066
1067 if (numtoinsert) {
1068 pciFirst = WinSendMsg(hwndCnr,
1069 CM_ALLOCRECORD,
1070 MPFROMLONG(EXTRA_RECORD_BYTES),
1071 MPFROMLONG((ULONG) numtoinsert));
1072 }
1073
1074 if (!pciFirst) {
1075 Win_Error2(hwndCnr, HWND_DESKTOP, pszSrcFile, __LINE__, IDS_CMALLOCRECERRTEXT);
1076 exit(0);
1077 }
1078
1079 pci = pciFirst;
1080 for (x = 0; x < 26; x++) {
1081 if ((ulDriveMap & (1L << x)) && !(driveflags[x] & DRIVE_IGNORE)) {
1082
1083 CHAR s[80];
1084 ULONG flags = 0;
1085 ULONG size = sizeof(ULONG);
1086
1087 *szDrive = (CHAR)x + 'A'; // Build path spec
1088
1089 sprintf(s, "%c.DriveFlags", toupper(*szDrive));
1090 if (PrfQueryProfileData(fmprof, appname, s, &flags, &size) &&
1091 size == sizeof(ULONG)) {
1092 driveflags[toupper(*szDrive) - 'A'] |= flags;
1093 }
1094
1095 if (x > 1) {
1096 // Hard drive (2..N)
1097 if (!(driveflags[x] & DRIVE_NOPRESCAN)) {
1098 *szFileSystem = 0;
1099 drvtype = 0;
1100 removable = CheckDrive(*szDrive, szFileSystem, &drvtype);
1101 driveserial[x] = -1;
1102 if (removable != -1) {
1103 struct {
1104 ULONG serial;
1105 CHAR volumelength;
1106 CHAR volumelabel[CCHMAXPATH];
1107 } volser;
1108
1109 DosError(FERR_DISABLEHARDERR);
1110 if (!DosQueryFSInfo((ULONG) x,
1111 FSIL_VOLSER, &volser, sizeof(volser))) {
1112 driveserial[x] = volser.serial;
1113 }
1114 }
1115 else
1116 driveflags[x] |= DRIVE_INVALID;
1117
1118 memset(&fsa4, 0, sizeof(FILESTATUS4));
1119 driveflags[x] |= removable == -1 || removable == 1 ?
1120 DRIVE_REMOVABLE : 0;
1121 if (drvtype & DRIVE_REMOTE)
1122 driveflags[x] |= DRIVE_REMOTE;
1123 if (!stricmp(szFileSystem,RAMFS)) {
1124 driveflags[x] |= DRIVE_RAMDISK;
1125 driveflags[x] &= ~DRIVE_REMOTE;
1126 }
1127 if (!stricmp(szFileSystem,NDFS32)) {
1128 driveflags[x] |= DRIVE_VIRTUAL;
1129 driveflags[x] &= ~DRIVE_REMOTE;
1130 }
1131 if (!stricmp(szFileSystem,NTFS))
1132 driveflags[x] |= DRIVE_NOTWRITEABLE;
1133 if (strcmp(szFileSystem, HPFS) &&
1134 strcmp(szFileSystem, JFS) &&
1135 strcmp(szFileSystem, ISOFS) &&
1136 strcmp(szFileSystem, CDFS) &&
1137 strcmp(szFileSystem, FAT32) &&
1138 strcmp(szFileSystem, NDFS32) &&
1139 strcmp(szFileSystem, RAMFS) &&
1140 strcmp(szFileSystem, NTFS) &&
1141 strcmp(szFileSystem, HPFS386)) {
1142 driveflags[x] |= DRIVE_NOLONGNAMES;
1143 }
1144
1145 if (!strcmp(szFileSystem, CDFS) || !strcmp(szFileSystem,ISOFS)) {
1146 removable = 1;
1147 driveflags[x] |= DRIVE_REMOVABLE | DRIVE_NOTWRITEABLE |
1148 DRIVE_CDROM;
1149 }
1150 else if (!stricmp(szFileSystem, CBSIFS)) {
1151 driveflags[x] |= DRIVE_ZIPSTREAM;
1152 driveflags[x] &= ~DRIVE_REMOTE;
1153 if (drvtype & DRIVE_REMOVABLE)
1154 driveflags[x] |= DRIVE_REMOVABLE;
1155 if (!(drvtype & DRIVE_NOLONGNAMES))
1156 driveflags[x] &= ~DRIVE_NOLONGNAMES;
1157 }
1158
1159 pci->rc.flRecordAttr |= CRA_RECORDREADONLY;
1160 // if ((ULONG) (toupper(*pci->pszFileName) - '@') == ulCurDriveNum) // 23 Jul 07 SHL
1161 if ((ULONG)(toupper(*szDrive) - '@') == ulCurDriveNum)
1162 pci->rc.flRecordAttr |= (CRA_CURSORED | CRA_SELECTED);
1163
1164 if (removable == 0) {
1165 // Fixed volume
1166 pci->attrFile |= FILE_DIRECTORY;
1167 DosError(FERR_DISABLEHARDERR);
1168 rc = DosQueryPathInfo(szDrive,
1169 FIL_QUERYEASIZE,
1170 &fsa4, (ULONG) sizeof(FILESTATUS4));
1171 // ERROR_BAD_NET_RSP = 58
1172 if (rc == 58) {
1173 DosError(FERR_DISABLEHARDERR);
1174 rc = DosQueryPathInfo(szDrive,
1175 FIL_STANDARD,
1176 &fsa4, (ULONG) sizeof(FILESTATUS3));
1177 fsa4.cbList = 0;
1178 }
1179 if (rc && !didonce) {
1180 // Guess drive letter
1181 if (!*suggest) {
1182 *suggest = '/';
1183 suggest[1] = 0;
1184 }
1185 sprintf(suggest + strlen(suggest), "%c" , toupper(*szDrive));
1186 pci->pszFileName = xstrdup(szDrive, pszSrcFile, __LINE__);
1187 strcpy(pci->pszFileName, szDrive);
1188 pci->pszDisplayName = pci->pszFileName;
1189 pci->rc.pszIcon = pci->pszDisplayName;
1190 pci->attrFile = FILE_DIRECTORY;
1191 pci->pszDispAttr = FileAttrToString(pci->attrFile);
1192 driveserial[x] = -1;
1193 }
1194 else
1195 FillInRecordFromFSA(hwndCnr, pci, szDrive, &fsa4, TRUE, NULL);
1196 }
1197 else {
1198 // Removable volume
1199 pci->pszFileName = xstrdup(szDrive, pszSrcFile, __LINE__);
1200 strcpy(pci->pszFileName, szDrive);
1201 pci->pszDisplayName = pci->pszFileName;
1202 pci->rc.pszIcon = pci->pszDisplayName;
1203 pci->attrFile = FILE_DIRECTORY;
1204 pci->pszDispAttr = FileAttrToString(pci->attrFile);
1205 }
1206 SelectDriveIcon(pci);
1207 }
1208 else {
1209 pci->rc.hptrIcon = hptrDunno;
1210 pci->pszFileName = xstrdup(szDrive, pszSrcFile, __LINE__);
1211 strcpy(pci->pszFileName, szDrive);
1212 pci->pszDisplayName = pci->pszFileName;
1213 pci->rc.pszIcon = pci->pszFileName;
1214 pci->attrFile = FILE_DIRECTORY;
1215 pci->pszDispAttr = FileAttrToString(pci->attrFile);
1216 driveserial[x] = -1;
1217 }
1218 }
1219 else {
1220 // diskette drive (A or B)
1221 pci->rc.hptrIcon = hptrFloppy;
1222 pci->pszFileName = xstrdup(szDrive, pszSrcFile, __LINE__);
1223 strcpy(pci->pszFileName, szDrive);
1224 pci->pszDisplayName = pci->pszFileName;
1225 pci->rc.pszIcon = pci->pszDisplayName;
1226 pci->attrFile = FILE_DIRECTORY;
1227 pci->pszDispAttr = FileAttrToString(pci->attrFile);
1228 driveflags[x] |= (DRIVE_REMOVABLE | DRIVE_NOLONGNAMES);
1229 driveserial[x] = -1;
1230 }
1231 pci->rc.flRecordAttr |= CRA_RECORDREADONLY;
1232 pci = (PCNRITEM) pci->rc.preccNextRecord; /* next rec */
1233 }
1234 else if (!(ulDriveMap & (1L << x)))
1235 driveflags[x] |= DRIVE_INVALID;
1236 } // for drives
1237
1238 PostMsg(hwndMain, UM_BUILDDRIVEBAR, MPVOID, MPVOID);
1239 drivesbuilt = TRUE;
1240
1241 /* insert the drives */
1242 if (numtoinsert && pciFirst) {
1243 RECORDINSERT ri;
1244
1245 memset(&ri, 0, sizeof(RECORDINSERT));
1246 ri.cb = sizeof(RECORDINSERT);
1247 ri.pRecordOrder = (PRECORDCORE) CMA_END;
1248 ri.pRecordParent = (PRECORDCORE) NULL;
1249 ri.zOrder = (ULONG) CMA_TOP;
1250 ri.cRecordsInsert = numtoinsert;
1251 ri.fInvalidateRecord = FALSE;
1252 if (!WinSendMsg(hwndCnr,
1253 CM_INSERTRECORD, MPFROMP(pciFirst), MPFROMP(&ri)))
1254 {
1255 Win_Error2(hwndCnr, HWND_DESKTOP, pszSrcFile, __LINE__,
1256 IDS_CMINSERTERRTEXT);
1257 }
1258 }
1259
1260 /* move cursor onto the default drive rather than the first drive */
1261 if (!fSwitchTree) {
1262 pci = (PCNRITEM) WinSendMsg(hwndCnr,
1263 CM_QUERYRECORD,
1264 MPVOID,
1265 MPFROM2SHORT(CMA_FIRST, CMA_ITEMORDER));
1266 while (pci && (INT)pci != -1) {
1267 if ((ULONG) (toupper(*pci->pszFileName) - '@') == ulCurDriveNum) {
1268 WinSendMsg(hwndCnr,
1269 CM_SETRECORDEMPHASIS,
1270 MPFROMP(pci), MPFROM2SHORT(TRUE, CRA_CURSORED));
1271 break;
1272 }
1273 pci = (PCNRITEM) WinSendMsg(hwndCnr,
1274 CM_QUERYRECORD,
1275 MPFROMP(pci),
1276 MPFROM2SHORT(CMA_NEXT, CMA_ITEMORDER));
1277 }
1278 }
1279
1280 if (hwndParent) {
1281 WinSendMsg(WinWindowFromID(WinQueryWindow(hwndParent, QW_PARENT),
1282 MAIN_DRIVELIST),
1283 LM_DELETEALL, MPVOID, MPVOID);
1284 }
1285
1286 if (fShowEnv) {
1287 RECORDINSERT ri;
1288
1289 pciParent = WinSendMsg(hwndCnr,
1290 CM_ALLOCRECORD,
1291 MPFROMLONG(EXTRA_RECORD_BYTES), MPFROMLONG(1));
1292 if (pciParent) {
1293 pciParent->flags |= RECFLAGS_ENV;
1294 pciParent->pszFileName = xstrdup(GetPString(IDS_ENVVARSTEXT), pszSrcFile, __LINE__);
1295 strcpy(pciParent->pszFileName, GetPString(IDS_ENVVARSTEXT));
1296 pciParent->pszDisplayName = pciParent->pszFileName; // 03 Aug 07 SHL
1297 pciParent->rc.hptrIcon = hptrEnv;
1298 pciParent->rc.pszIcon = pciParent->pszFileName;
1299 pciParent->pszDispAttr = FileAttrToString(0);
1300 memset(&ri, 0, sizeof(RECORDINSERT));
1301 ri.cb = sizeof(RECORDINSERT);
1302 ri.pRecordOrder = (PRECORDCORE) CMA_END;
1303 ri.pRecordParent = (PRECORDCORE) NULL;
1304 ri.zOrder = (ULONG) CMA_TOP;
1305 ri.cRecordsInsert = 1;
1306 ri.fInvalidateRecord = FALSE;
1307 if (WinSendMsg(hwndCnr,
1308 CM_INSERTRECORD, MPFROMP(pciParent), MPFROMP(&ri))) {
1309
1310 char *p, *pp;
1311
1312 p = GetPString(IDS_ENVVARNAMES);
1313 while (*p == ' ')
1314 p++;
1315 while (*p) {
1316 *szFileSystem = 0;
1317 pp = szFileSystem;
1318 while (*p && *p != ' ')
1319 *pp++ = *p++;
1320 *pp = 0;
1321 while (*p == ' ')
1322 p++;
1323 if (*szFileSystem &&
1324 (!stricmp(szFileSystem, "LIBPATH") || getenv(szFileSystem))) {
1325 pci = WinSendMsg(hwndCnr,
1326 CM_ALLOCRECORD,
1327 MPFROMLONG(EXTRA_RECORD_BYTES),
1328 MPFROMLONG(1));
1329 if (pci) {
1330 CHAR fname[CCHMAXPATH];
1331 pci->flags |= RECFLAGS_ENV;
1332 sprintf(fname, "%%%s%%", szFileSystem);
1333 pci->pszFileName = xstrdup(fname, pszSrcFile, __LINE__);
1334 pci->rc.hptrIcon = hptrEnv;
1335 pci->rc.pszIcon = pci->pszFileName;
1336 pci->pszDispAttr = FileAttrToString(0);
1337 memset(&ri, 0, sizeof(RECORDINSERT));
1338 ri.cb = sizeof(RECORDINSERT);
1339 ri.pRecordOrder = (PRECORDCORE) CMA_END;
1340 ri.pRecordParent = (PRECORDCORE) pciParent;
1341 ri.zOrder = (ULONG) CMA_TOP;
1342 ri.cRecordsInsert = 1;
1343 ri.fInvalidateRecord = FALSE;
1344 if (!WinSendMsg(hwndCnr,
1345 CM_INSERTRECORD,
1346 MPFROMP(pci), MPFROMP(&ri))) {
1347 Win_Error2(hwndCnr, HWND_DESKTOP, pszSrcFile, __LINE__,
1348 IDS_CMINSERTERRTEXT);
1349 FreeCnrItem(hwndCnr, pci);
1350 }
1351 }
1352 }
1353 }
1354 WinSendMsg(hwndCnr,
1355 CM_INVALIDATERECORD,
1356 MPFROMP(&pciParent),
1357 MPFROM2SHORT(1, CMA_ERASE | CMA_REPOSITION));
1358 }
1359 else
1360 FreeCnrItem(hwndCnr, pciParent);
1361 }
1362 } // if show env
1363
1364 x = 0;
1365 pci = (PCNRITEM) WinSendMsg(hwndCnr,
1366 CM_QUERYRECORD,
1367 MPVOID,
1368 MPFROM2SHORT(CMA_FIRST, CMA_ITEMORDER));
1369 while (pci && (INT)pci != -1) {
1370 pciNext = (PCNRITEM) WinSendMsg(hwndCnr,
1371 CM_QUERYRECORD,
1372 MPFROMP(pci),
1373 MPFROM2SHORT(CMA_NEXT, CMA_ITEMORDER));
1374 if (!(pci->flags & RECFLAGS_ENV)) {
1375 if ((ULONG) (toupper(*pci->pszFileName) - '@') == ulCurDriveNum ||
1376 toupper(*pci->pszFileName) > 'B')
1377 {
1378 if (!(driveflags[toupper(*pci->pszFileName) - 'A'] & DRIVE_INVALID) &&
1379 !(driveflags[toupper(*pci->pszFileName) - 'A'] & DRIVE_NOPRESCAN) &&
1380 (!fNoRemovableScan ||
1381 !(driveflags[toupper(*pci->pszFileName) - 'A'] & DRIVE_REMOVABLE)))
1382 {
1383 if (!Stubby(hwndCnr, pci) && !DRIVE_RAMDISK) {
1384 WinSendMsg(hwndCnr,
1385 CM_INVALIDATERECORD,
1386 MPFROMP(&pci),
1387 MPFROM2SHORT(1, CMA_ERASE | CMA_REPOSITION));
1388 goto SkipBadRec;
1389 }
1390 }
1391 }
1392 else {
1393 WinSendMsg(hwndCnr,
1394 CM_INVALIDATERECORD,
1395 MPFROMP(&pci),
1396 MPFROM2SHORT(1, CMA_ERASE | CMA_REPOSITION));
1397 }
1398
1399 WinSendMsg(WinWindowFromID(WinQueryWindow(hwndParent, QW_PARENT),
1400 MAIN_DRIVELIST),
1401 LM_INSERTITEM,
1402 MPFROM2SHORT(LIT_SORTASCENDING, 0),
1403 MPFROMP(pci->pszFileName));
1404 }
1405 SkipBadRec:
1406 x++;
1407 pci = pciNext;
1408 }
1409 if (hwndParent)
1410 WinSendMsg(WinWindowFromID(WinQueryWindow(hwndParent, QW_PARENT),
1411 MAIN_DRIVELIST), LM_SELECTITEM,
1412 MPFROM2SHORT(0, 0), MPFROMLONG(TRUE));
1413
1414 pci = (PCNRITEM) WinSendMsg(hwndCnr,
1415 CM_QUERYRECORD,
1416 MPVOID,
1417 MPFROM2SHORT(CMA_FIRST, CMA_ITEMORDER));
1418 while (pci && (INT)pci != -1) {
1419 pciNext = (PCNRITEM) WinSendMsg(hwndCnr,
1420 CM_QUERYRECORD,
1421 MPFROMP(pci),
1422 MPFROM2SHORT(CMA_NEXT, CMA_ITEMORDER));
1423 if (pci->flags & RECFLAGS_ENV) {
1424 pci = (PCNRITEM) WinSendMsg(hwndCnr,
1425 CM_QUERYRECORD,
1426 MPFROMP(pci),
1427 MPFROM2SHORT(CMA_FIRSTCHILD,
1428 CMA_ITEMORDER));
1429 while (pci && (INT)pci != -1) {
1430 if (pci->flags & RECFLAGS_ENV)
1431 FleshEnv(hwndCnr, pci);
1432 pci = (PCNRITEM) WinSendMsg(hwndCnr,
1433 CM_QUERYRECORD,
1434 MPFROMP(pci),
1435 MPFROM2SHORT(CMA_NEXT, CMA_ITEMORDER));
1436 }
1437 break;
1438 }
1439 pci = (PCNRITEM) WinSendMsg(hwndCnr,
1440 CM_QUERYRECORD,
1441 MPFROMP(pci),
1442 MPFROM2SHORT(CMA_NEXT, CMA_ITEMORDER));
1443 }
1444
1445 if (!drivesbuilt && hwndMain)
1446 PostMsg(hwndMain, UM_BUILDDRIVEBAR, MPVOID, MPVOID);
1447 DosSleep(16);//05 Aug 07 GKY 33
1448 fDummy = FALSE;
1449 DosPostEventSem(CompactSem);
1450
1451 {
1452 BYTE info;
1453 BOOL includesyours = FALSE;
1454
1455 if (*suggest || (!(driveflags[1] & DRIVE_IGNORE) && fFirstTime)) {
1456 if (!DosDevConfig(&info, DEVINFO_FLOPPY) && info == 1) {
1457 if (!*suggest) {
1458 *suggest = '/';
1459 suggest[1] = 0;
1460 }
1461 else
1462 memmove(suggest + 2, suggest + 1, strlen(suggest));
1463 suggest[1] = 'B';
1464 }
1465 }
1466 if (*suggest) {
1467 for (x = 2; x < 26; x++) {
1468 if (driveflags[x] & DRIVE_IGNORE) {
1469 includesyours = TRUE;
1470 sprintf(suggest + strlen(suggest), "%c", (char)(x + 'A'));
1471 }
1472 }
1473 strcat(suggest, " %*");
1474 if (saymsg(MB_YESNO | MB_ICONEXCLAMATION,
1475 (hwndParent) ? hwndParent : hwndCnr,
1476 GetPString(IDS_SUGGESTTITLETEXT),
1477 GetPString(IDS_SUGGEST1TEXT),
1478 (includesyours) ? GetPString(IDS_SUGGEST2TEXT) : NullStr,
1479 suggest) == MBID_YES) {
1480 char s[64];
1481
1482 sprintf(s, "PARAMETERS=%s", suggest);
1483 WinCreateObject(WPProgram, "FM/2", s, FM3Folder, CO_UPDATEIFEXISTS);
1484 WinCreateObject(WPProgram,
1485 "FM/2 Lite", s, FM3Folder, CO_UPDATEIFEXISTS);
1486 WinCreateObject(WPProgram,
1487 "Archive Viewer/2", s, FM3Tools, CO_UPDATEIFEXISTS);
1488 WinCreateObject(WPProgram,
1489 "Dir Sizes", s, FM3Tools, CO_UPDATEIFEXISTS);
1490 WinCreateObject(WPProgram,
1491 "Visual Tree", s, FM3Tools, CO_UPDATEIFEXISTS);
1492 WinCreateObject(WPProgram,
1493 "Visual Directory", s, FM3Tools, CO_UPDATEIFEXISTS);
1494 WinCreateObject(WPProgram,
1495 "Global File Viewer", s, FM3Tools, CO_UPDATEIFEXISTS);
1496 WinCreateObject(WPProgram, "Databar", s, FM3Tools, CO_UPDATEIFEXISTS);
1497 }
1498 }
1499 }
1500
1501 didonce = TRUE;
1502
1503} // FillTreeCnr
1504
1505
1506/**
1507 * Empty all records from a container and free associated storage and
1508 * Free up field infos
1509 */
1510
1511VOID EmptyCnr(HWND hwnd)
1512{
1513 PFIELDINFO pfi;
1514
1515#if 0 // fixme to be gone or to be configurable
1516 {
1517 int state = _heapchk();
1518 if (state != _HEAPOK)
1519 Runtime_Error(pszSrcFile, __LINE__, "heap corrupted %d", state);
1520 }
1521#endif
1522
1523 // Remove all records
1524 RemoveCnrItems(hwnd, NULL, 0, CMA_FREE);
1525
1526 // Remove field info descriptors
1527 pfi = (PFIELDINFO) WinSendMsg(hwnd, CM_QUERYDETAILFIELDINFO, MPVOID,
1528 MPFROMSHORT(CMA_FIRST));
1529 if (pfi &&
1530 (INT)WinSendMsg(hwnd, CM_REMOVEDETAILFIELDINFO, MPVOID,
1531 MPFROM2SHORT(0, CMA_FREE)) == -1) {
1532 Win_Error(hwnd, HWND_DESKTOP, pszSrcFile, __LINE__,"CM_REMOVEDETAILFIELDINFO hwnd %x", hwnd);
1533 }
1534}
1535
1536/**
1537 * Free storage associated with container item
1538 */
1539
1540VOID FreeCnrItemData(PCNRITEM pci)
1541{
1542 PSZ psz;
1543 // DbgMsg(pszSrcFile, __LINE__, "FreeCnrItemData %p", pci);
1544
1545 if (pci->pszSubject && pci->pszSubject != NullStr) {
1546
1547 psz = pci->pszSubject;
1548 pci->pszSubject = NullStr;
1549 free(psz);
1550 }
1551
1552 // +1 in case long name pointing after last backslash
1553 if (pci->pszLongName &&
1554 pci->pszLongName != NullStr &&
1555 pci->pszLongName != pci->pszFileName &&
1556 pci->pszLongName != pci->pszDisplayName &&
1557 pci->pszLongName != pci->pszDisplayName + 1) {
1558 psz = pci->pszLongName;
1559 pci->pszLongName = NullStr;
1560 free(psz);
1561 }
1562
1563 if (pci->pszFileName && pci->pszFileName != NullStr) {
1564 psz = pci->pszFileName;
1565 pci->pszFileName = NullStr;
1566 free(psz);
1567 }
1568}
1569
1570/**
1571 * Free single container item and associated storage
1572 */
1573
1574VOID FreeCnrItem(HWND hwnd, PCNRITEM pci)
1575{
1576 // DbgMsg(pszSrcFile, __LINE__, "FreeCnrItem hwnd %x pci %p", hwnd, pci);
1577
1578 FreeCnrItemData(pci);
1579
1580 if (!WinSendMsg(hwnd, CM_FREERECORD, MPFROMP(&pci), MPFROMSHORT(1))) {
1581 // Win_Error2(hwnd, HWND_DESKTOP, pszSrcFile, __LINE__,IDS_CMFREEERRTEXT);
1582 Win_Error(hwnd, HWND_DESKTOP, pszSrcFile, __LINE__,
1583 "CM_FREERECORD hwnd %x pci %p file %s",
1584 hwnd, pci,
1585 pci && pci->pszFileName ? pci->pszFileName : "n/a");
1586 }
1587}
1588
1589/**
1590 * Free container item list and associated storage
1591 */
1592
1593VOID FreeCnrItemList(HWND hwnd, PCNRITEM pciFirst)
1594{
1595 PCNRITEM pci = pciFirst;
1596 PCNRITEM pciNext;
1597 USHORT usCount;
1598
1599 for (usCount = 0; pci; usCount++) {
1600 pciNext = (PCNRITEM) pci->rc.preccNextRecord;
1601 FreeCnrItemData(pci);
1602 pci = pciNext;
1603 }
1604
1605 if (usCount) {
1606 if (!WinSendMsg(hwnd, CM_FREERECORD, MPFROMP(&pci), MPFROMSHORT(usCount))) {
1607 // Win_Error2(hwnd, HWND_DESKTOP, pszSrcFile, __LINE__,IDS_CMFREEERRTEXT);
1608 Win_Error(hwnd, HWND_DESKTOP, pszSrcFile, __LINE__,"CM_FREERECORD hwnd %x pci %p cnt %u", hwnd, pci, usCount);
1609 }
1610 }
1611}
1612
1613/**
1614 * Remove item(s) from container and free associated storage if requested
1615 * @param pciFirst points to first item to remove or NULL to remove all
1616 * @param usCnt is remove count or 0 to remove all
1617 * @returns count of items remaining in container or -1 if error
1618 */
1619
1620INT RemoveCnrItems(HWND hwnd, PCNRITEM pciFirst, USHORT usCnt, USHORT usFlags)
1621{
1622 INT remaining = usCnt;
1623 PCNRITEM pci;
1624
1625 if ((usCnt && !pciFirst) || (!usCnt && pciFirst)) {
1626 Runtime_Error(pszSrcFile, __LINE__, "pciFirst %p usCnt %u mismatch", pciFirst, usCnt);
1627 remaining = -1;
1628 }
1629 else {
1630 // Free our buffers if free requested
1631 if (usFlags & CMA_FREE) {
1632 if (pciFirst)
1633 pci = pciFirst;
1634 else {
1635 pci = (PCNRITEM)WinSendMsg(hwnd, CM_QUERYRECORD, MPVOID,
1636 MPFROM2SHORT(CMA_FIRST, CMA_ITEMORDER));
1637 if ((INT)pci == -1) {
1638 Win_Error(hwnd, HWND_DESKTOP, pszSrcFile, __LINE__,"CM_QUERYRECORD");
1639 remaining = -1;
1640 pci = NULL;
1641 }
1642 }
1643 while (pci) {
1644 FreeCnrItemData(pci);
1645 pci = (PCNRITEM)pci->rc.preccNextRecord;
1646 if (remaining && --remaining == 0)
1647 break;
1648 }
1649 }
1650 }
1651
1652 // DbgMsg(pszSrcFile, __LINE__, "RemoveCnrItems %p %u %s", pci, usCnt, pci->pszFileName);
1653
1654 if (remaining != - 1) {
1655 remaining = (INT)WinSendMsg(hwnd, CM_REMOVERECORD, MPFROMP(&pciFirst), MPFROM2SHORT(usCnt, usFlags));
1656 if (remaining == -1) {
1657 // Win_Error2(hwnd, HWND_DESKTOP, pszSrcFile, __LINE__,IDS_CMREMOVEERRTEXT);
1658 Win_Error(hwnd, HWND_DESKTOP, pszSrcFile, __LINE__,"CM_REMOVERECORD hwnd %x pci %p cnt %u", hwnd, pciFirst, usCnt);
1659 }
1660 }
1661
1662 return remaining;
1663}
1664
Note: See TracBrowser for help on using the repository browser.