xref: /reactos/base/applications/cmdutils/tree/tree.c (revision 84344399)
1 /*
2  * PROJECT:     ReactOS
3  * LICENSE:     GNU GPLv2 only as published by the Free Software Foundation
4  * PURPOSE:     Implements tree.com command similar to Windows
5  * PROGRAMMERS: Asif Bahrainwala (asif_bahrainwala@hotmail.com)
6  */
7 
8 #include <stdio.h>
9 #include <stdlib.h>
10 
11 #include <windef.h>
12 #include <winbase.h>
13 
14 #include <conutils.h>
15 
16 #include "resource.h"
17 
18 #define STR_MAX 2048
19 
20 static VOID GetDirectoryStructure(PWSTR strPath, UINT width, PCWSTR prevLine);
21 
22 /* If this flag is set to true, files will also be listed within the folder structure */
23 BOOL bShowFiles = FALSE;
24 
25 /* If this flag is true, ASCII characters will be used instead of UNICODE ones */
26 BOOL bUseAscii = FALSE;
27 
28 /**
29 * @name: HasSubFolder
30 *
31 * @param strPath
32 * Must specify folder name
33 *
34 * @return
35 * true if folder has sub-folders, else will return false
36 */
37 static BOOL HasSubFolder(PCWSTR strPath1)
38 {
39     BOOL ret = FALSE;
40     WIN32_FIND_DATAW FindFileData;
41     HANDLE hFind = NULL;
42     static WCHAR strPath[STR_MAX] = L"";
43     ZeroMemory(strPath, sizeof(strPath));
44 
45     wcscat(strPath, strPath1);
46     wcscat(strPath, L"\\*.");
47 
48     hFind = FindFirstFileW(strPath, &FindFileData);
49     do
50     {
51         if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
52         {
53             if (wcscmp(FindFileData.cFileName, L".") == 0 ||
54                 wcscmp(FindFileData.cFileName, L"..") == 0 )
55             {
56                 continue;
57             }
58 
59             ret = TRUE;  // Sub-folder found
60             break;
61         }
62     }
63     while (FindNextFileW(hFind, &FindFileData));
64 
65     FindClose(hFind);
66     return ret;
67 }
68 
69 /**
70  * @name: DrawTree
71  *
72  * @param strPath
73  * Must specify folder name
74  *
75  * @param arrFolder
76  * must be a list of folder names to be drawn in tree format
77  *
78  * @param width
79  * specifies drawing distance for correct formatting of tree structure being drawn on console screen
80  * used internally for adding spaces
81  *
82  * @param prevLine
83  * used internally for formatting reasons
84  *
85  * @return
86  * void
87  */
88 static VOID DrawTree(PCWSTR strPath,
89                      const WIN32_FIND_DATAW* arrFolder,
90                      const size_t szArr,
91                      UINT width,
92                      PCWSTR prevLine,
93                      BOOL drawfolder)
94 {
95     BOOL bHasSubFolder = HasSubFolder(strPath);
96     UINT i = 0;
97 
98     /* This will format the spaces required for correct formatting */
99     for (i = 0; i < szArr; ++i)
100     {
101         PWSTR consoleOut = (PWSTR)malloc(STR_MAX * sizeof(WCHAR));
102         UINT j = 0;
103         static WCHAR str[STR_MAX];
104 
105         /* As we do not seem to have the _s functions properly set up, use the non-secure version for now */
106         //wcscpy_s(consoleOut, STR_MAX, L"");
107         //wcscpy_s(str, STR_MAX, L"");
108         wcscpy(consoleOut, L"");
109         wcscpy(str, L"");
110 
111         for (j = 0; j < width - 1; ++j)
112         {
113             /* If the previous line has '├' or '│' then the current line will
114                add '│' to continue the connecting line */
115             if (prevLine[j] == L'\x251C' || prevLine[j] == L'\x2502' ||
116                 prevLine[j] == L'+' || prevLine[j] == L'|')
117             {
118                 if (!bUseAscii)
119                 {
120                     wcscat(consoleOut, L"\x2502");
121                 }
122                 else
123                 {
124                     wcscat(consoleOut, L"|");
125                 }
126             }
127             else
128             {
129                 wcscat(consoleOut, L" ");
130             }
131         }
132 
133         if (szArr - 1 != i)
134         {
135             if (drawfolder)
136             {
137                 /* Add '├───Folder name' (\xC3\xC4\xC4\xC4 or \x251C\x2500\x2500\x2500) */
138                 if (bUseAscii)
139                     swprintf(str, L"+---%s", arrFolder[i].cFileName);
140                 else
141                     swprintf(str, L"\x251C\x2500\x2500\x2500%s", arrFolder[i].cFileName);
142             }
143             else
144             {
145                 if (bHasSubFolder)
146                 {
147                     /* Add '│   FileName' (\xB3 or \x2502) */
148                     // This line is added to connect the below-folder sub-structure
149                     if (bUseAscii)
150                         swprintf(str, L"|   %s", arrFolder[i].cFileName);
151                     else
152                         swprintf(str, L"\x2502   %s", arrFolder[i].cFileName);
153                 }
154                 else
155                 {
156                     /* Add '    FileName' */
157                     swprintf(str, L"    %s", arrFolder[i].cFileName);
158                 }
159             }
160         }
161         else
162         {
163             if (drawfolder)
164             {
165                 /* '└───Folder name' (\xC0\xC4\xC4\xC4 or \x2514\x2500\x2500\x2500) */
166                 if (bUseAscii)
167                     swprintf(str, L"\\---%s", arrFolder[i].cFileName);
168                 else
169                     swprintf(str, L"\x2514\x2500\x2500\x2500%s", arrFolder[i].cFileName);
170             }
171             else
172             {
173                 if (bHasSubFolder)
174                 {
175                     /* '│   FileName' (\xB3 or \x2502) */
176                     if (bUseAscii)
177                         swprintf(str, L"|   %s", arrFolder[i].cFileName);
178                     else
179                         swprintf(str, L"\x2502   %s", arrFolder[i].cFileName);
180                 }
181                 else
182                 {
183                     /* '    FileName' */
184                     swprintf(str, L"    %s", arrFolder[i].cFileName);
185                 }
186             }
187         }
188 
189         wcscat(consoleOut, str);
190         ConPrintf(StdOut, L"%s\n", consoleOut);
191 
192         if (drawfolder)
193         {
194             PWSTR str = (PWSTR)malloc(STR_MAX * sizeof(WCHAR));
195             ZeroMemory(str, STR_MAX * sizeof(WCHAR));
196 
197             wcscat(str, strPath);
198             wcscat(str, L"\\");
199             wcscat(str, arrFolder[i].cFileName);
200             GetDirectoryStructure(str, width + 4, consoleOut);
201 
202             free(str);
203         }
204         free(consoleOut);
205     }
206 }
207 
208 /**
209  * @name: GetDirectoryStructure
210  *
211  * @param strPath
212  * Must specify folder name
213  *
214  * @param width
215  * specifies drawing distance for correct formatting of tree structure being drawn on console screen
216  *
217  * @param prevLine
218  * specifies the previous line written on console, is used for correct formatting
219  * @return
220  * void
221  */
222 static VOID
223 GetDirectoryStructure(PWSTR strPath, UINT width, PCWSTR prevLine)
224 {
225     WIN32_FIND_DATAW FindFileData;
226     HANDLE hFind = NULL;
227     //DWORD err = 0;
228     /* Fill up with names of all sub-folders */
229     WIN32_FIND_DATAW *arrFolder = NULL;
230     UINT arrFoldersz = 0;
231     /* Fill up with names of all sub-folders */
232     WIN32_FIND_DATAW *arrFile = NULL;
233     UINT arrFilesz = 0;
234 
235     ZeroMemory(&FindFileData, sizeof(FindFileData));
236 
237     {
238         static WCHAR tmp[STR_MAX] = L"";
239         ZeroMemory(tmp, sizeof(tmp));
240         wcscat(tmp, strPath);
241         wcscat(tmp, L"\\*.*");
242         hFind = FindFirstFileW(tmp, &FindFileData);
243         //err = GetLastError();
244     }
245 
246     if (hFind == INVALID_HANDLE_VALUE)
247         return;
248 
249     do
250     {
251         if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
252         {
253             if (wcscmp(FindFileData.cFileName, L".") == 0 ||
254                 wcscmp(FindFileData.cFileName, L"..") == 0)
255                 continue;
256 
257             ++arrFoldersz;
258             arrFolder = (WIN32_FIND_DATAW*)realloc(arrFolder, arrFoldersz * sizeof(FindFileData));
259 
260             if (arrFolder == NULL)
261                 exit(-1);
262 
263             arrFolder[arrFoldersz - 1] = FindFileData;
264 
265         }
266         else
267         {
268             ++arrFilesz;
269             arrFile = (WIN32_FIND_DATAW*)realloc(arrFile, arrFilesz * sizeof(FindFileData));
270 
271             if(arrFile == NULL)
272                 exit(-1);
273 
274             arrFile[arrFilesz - 1] = FindFileData;
275         }
276     }
277     while (FindNextFileW(hFind, &FindFileData));
278 
279     FindClose(hFind);
280 
281     if (bShowFiles)
282     {
283         /* Will free(arrFile) */
284         DrawTree(strPath, arrFile, arrFilesz, width, prevLine, FALSE);
285     }
286 
287     /* Will free(arrFile) */
288     DrawTree(strPath, arrFolder, arrFoldersz, width, prevLine, TRUE);
289 
290     free(arrFolder);
291     free(arrFile);
292 }
293 
294 /**
295 * @name: main
296 * standard main functionality as required by C/C++ for application startup
297 *
298 * @return
299 * error /success value
300 */
301 int wmain(int argc, WCHAR* argv[])
302 {
303     DWORD dwSerial = 0;
304     WCHAR t = 0;
305     PWSTR strPath = NULL;
306     DWORD sz = 0;
307     //PWSTR context = NULL;
308     PWSTR driveLetter = NULL;
309 
310     int i;
311 
312     /* Initialize the Console Standard Streams */
313     ConInitStdStreams();
314 
315     /* Parse the command line */
316     for (i = 1; i < argc; ++i)
317     {
318         if (argv[i][0] == L'-' || argv[i][0] == L'/')
319         {
320             switch (towlower(argv[i][1]))
321             {
322                 case L'?':
323                     /* Print help and exit after */
324                     ConResPuts(StdOut, IDS_USAGE);
325                     return 0;
326 
327                 case L'f':
328                     bShowFiles = TRUE;
329                     break;
330 
331                 case L'a':
332                     bUseAscii = TRUE;
333                     break;
334 
335                 default:
336                     break;
337             }
338         }
339         else
340         {
341             /* This must be path to some folder */
342 
343             /* Set the current directory for this executable */
344             BOOL b = SetCurrentDirectoryW(argv[i]);
345             if (b == FALSE)
346             {
347                 ConResPuts(StdOut, IDS_NO_SUBDIRECTORIES);
348                 return 1;
349             }
350         }
351     }
352 
353     ConResPuts(StdOut, IDS_FOLDER_PATH);
354 
355     GetVolumeInformationW(NULL, NULL, 0, &dwSerial, NULL, NULL, NULL, 0);
356     ConResPrintf(StdOut, IDS_VOL_SERIAL, dwSerial >> 16, dwSerial & 0xffff);
357 
358     /* get the buffer size */
359     sz = GetCurrentDirectoryW(1, &t);
360     /* must not return before calling delete[] */
361     strPath = (PWSTR)malloc(sz * sizeof(WCHAR));
362 
363     /* get the current directory */
364     GetCurrentDirectoryW(sz, strPath);
365 
366     /* get the drive letter , must not return before calling delete[] */
367     driveLetter = (PWSTR)malloc(sz * sizeof(WCHAR));
368 
369     /* As we do not seem to have the _s functions properly set up, use the non-secure version for now */
370     //wcscpy_s(driveLetter,sz,strPath);
371     //wcstok_s(driveLetter,L":", &context); //parse for the drive letter
372     wcscpy(driveLetter, strPath);
373     wcstok(driveLetter, L":");
374 
375     ConPrintf(StdOut, L"%s:.\n", driveLetter);
376 
377     free(driveLetter);
378 
379     /* get the sub-directories within this current folder */
380     GetDirectoryStructure(strPath, 1, L"          ");
381 
382     free(strPath);
383     ConPuts(StdOut, L"\n");
384 
385     return 0;
386 }
387