rlm@1
|
1 // Windows/FileDir.cpp
|
rlm@1
|
2
|
rlm@1
|
3 #include "StdAfx.h"
|
rlm@1
|
4
|
rlm@1
|
5 #include "FileDir.h"
|
rlm@1
|
6 #include "FileName.h"
|
rlm@1
|
7 #include "FileFind.h"
|
rlm@1
|
8 #include "Defs.h"
|
rlm@1
|
9 #ifndef _UNICODE
|
rlm@1
|
10 #include "../Common/StringConvert.h"
|
rlm@1
|
11 #endif
|
rlm@1
|
12
|
rlm@1
|
13 #ifndef _UNICODE
|
rlm@1
|
14 extern bool g_IsNT;
|
rlm@1
|
15 #endif
|
rlm@1
|
16
|
rlm@1
|
17 namespace NWindows {
|
rlm@1
|
18 namespace NFile {
|
rlm@1
|
19
|
rlm@1
|
20 #if defined(WIN_LONG_PATH) && defined(_UNICODE)
|
rlm@1
|
21 #define WIN_LONG_PATH2
|
rlm@1
|
22 #endif
|
rlm@1
|
23
|
rlm@1
|
24 // SetCurrentDirectory doesn't support \\?\ prefix
|
rlm@1
|
25
|
rlm@1
|
26 #ifdef WIN_LONG_PATH
|
rlm@1
|
27 bool GetLongPathBase(LPCWSTR fileName, UString &res);
|
rlm@1
|
28 bool GetLongPath(LPCWSTR fileName, UString &res);
|
rlm@1
|
29 #endif
|
rlm@1
|
30
|
rlm@1
|
31 namespace NDirectory {
|
rlm@1
|
32
|
rlm@1
|
33 #ifndef _UNICODE
|
rlm@1
|
34 static inline UINT GetCurrentCodePage() { return ::AreFileApisANSI() ? CP_ACP : CP_OEMCP; }
|
rlm@1
|
35 static UString GetUnicodePath(const CSysString &sysPath)
|
rlm@1
|
36 { return MultiByteToUnicodeString(sysPath, GetCurrentCodePage()); }
|
rlm@1
|
37 static CSysString GetSysPath(LPCWSTR sysPath)
|
rlm@1
|
38 { return UnicodeStringToMultiByte(sysPath, GetCurrentCodePage()); }
|
rlm@1
|
39 #endif
|
rlm@1
|
40
|
rlm@1
|
41 bool MyGetWindowsDirectory(CSysString &path)
|
rlm@1
|
42 {
|
rlm@1
|
43 UINT needLength = ::GetWindowsDirectory(path.GetBuffer(MAX_PATH + 1), MAX_PATH + 1);
|
rlm@1
|
44 path.ReleaseBuffer();
|
rlm@1
|
45 return (needLength > 0 && needLength <= MAX_PATH);
|
rlm@1
|
46 }
|
rlm@1
|
47
|
rlm@1
|
48 bool MyGetSystemDirectory(CSysString &path)
|
rlm@1
|
49 {
|
rlm@1
|
50 UINT needLength = ::GetSystemDirectory(path.GetBuffer(MAX_PATH + 1), MAX_PATH + 1);
|
rlm@1
|
51 path.ReleaseBuffer();
|
rlm@1
|
52 return (needLength > 0 && needLength <= MAX_PATH);
|
rlm@1
|
53 }
|
rlm@1
|
54
|
rlm@1
|
55 #ifndef _UNICODE
|
rlm@1
|
56 bool MyGetWindowsDirectory(UString &path)
|
rlm@1
|
57 {
|
rlm@1
|
58 if (g_IsNT)
|
rlm@1
|
59 {
|
rlm@1
|
60 UINT needLength = ::GetWindowsDirectoryW(path.GetBuffer(MAX_PATH + 1), MAX_PATH + 1);
|
rlm@1
|
61 path.ReleaseBuffer();
|
rlm@1
|
62 return (needLength > 0 && needLength <= MAX_PATH);
|
rlm@1
|
63 }
|
rlm@1
|
64 CSysString sysPath;
|
rlm@1
|
65 if (!MyGetWindowsDirectory(sysPath))
|
rlm@1
|
66 return false;
|
rlm@1
|
67 path = GetUnicodePath(sysPath);
|
rlm@1
|
68 return true;
|
rlm@1
|
69 }
|
rlm@1
|
70
|
rlm@1
|
71 bool MyGetSystemDirectory(UString &path)
|
rlm@1
|
72 {
|
rlm@1
|
73 if (g_IsNT)
|
rlm@1
|
74 {
|
rlm@1
|
75 UINT needLength = ::GetSystemDirectoryW(path.GetBuffer(MAX_PATH + 1), MAX_PATH + 1);
|
rlm@1
|
76 path.ReleaseBuffer();
|
rlm@1
|
77 return (needLength > 0 && needLength <= MAX_PATH);
|
rlm@1
|
78 }
|
rlm@1
|
79 CSysString sysPath;
|
rlm@1
|
80 if (!MyGetSystemDirectory(sysPath))
|
rlm@1
|
81 return false;
|
rlm@1
|
82 path = GetUnicodePath(sysPath);
|
rlm@1
|
83 return true;
|
rlm@1
|
84 }
|
rlm@1
|
85 #endif
|
rlm@1
|
86
|
rlm@1
|
87 bool SetDirTime(LPCWSTR fileName, const FILETIME *cTime, const FILETIME *aTime, const FILETIME *mTime)
|
rlm@1
|
88 {
|
rlm@1
|
89 #ifndef _UNICODE
|
rlm@1
|
90 if (!g_IsNT)
|
rlm@1
|
91 {
|
rlm@1
|
92 ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
|
rlm@1
|
93 return false;
|
rlm@1
|
94 }
|
rlm@1
|
95 #endif
|
rlm@1
|
96 HANDLE hDir = ::CreateFileW(fileName, GENERIC_WRITE,
|
rlm@1
|
97 FILE_SHARE_READ | FILE_SHARE_WRITE,
|
rlm@1
|
98 NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
rlm@1
|
99 #ifdef WIN_LONG_PATH
|
rlm@1
|
100 if (hDir == INVALID_HANDLE_VALUE)
|
rlm@1
|
101 {
|
rlm@1
|
102 UString longPath;
|
rlm@1
|
103 if (GetLongPath(fileName, longPath))
|
rlm@1
|
104 hDir = ::CreateFileW(longPath, GENERIC_WRITE,
|
rlm@1
|
105 FILE_SHARE_READ | FILE_SHARE_WRITE,
|
rlm@1
|
106 NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
rlm@1
|
107 }
|
rlm@1
|
108 #endif
|
rlm@1
|
109
|
rlm@1
|
110 bool res = false;
|
rlm@1
|
111 if (hDir != INVALID_HANDLE_VALUE)
|
rlm@1
|
112 {
|
rlm@1
|
113 res = BOOLToBool(::SetFileTime(hDir, cTime, aTime, mTime));
|
rlm@1
|
114 ::CloseHandle(hDir);
|
rlm@1
|
115 }
|
rlm@1
|
116 return res;
|
rlm@1
|
117 }
|
rlm@1
|
118
|
rlm@1
|
119 bool MySetFileAttributes(LPCTSTR fileName, DWORD fileAttributes)
|
rlm@1
|
120 {
|
rlm@1
|
121 if (::SetFileAttributes(fileName, fileAttributes))
|
rlm@1
|
122 return true;
|
rlm@1
|
123 #ifdef WIN_LONG_PATH2
|
rlm@1
|
124 UString longPath;
|
rlm@1
|
125 if (GetLongPath(fileName, longPath))
|
rlm@1
|
126 return BOOLToBool(::SetFileAttributesW(longPath, fileAttributes));
|
rlm@1
|
127 #endif
|
rlm@1
|
128 return false;
|
rlm@1
|
129 }
|
rlm@1
|
130
|
rlm@1
|
131 bool MyRemoveDirectory(LPCTSTR pathName)
|
rlm@1
|
132 {
|
rlm@1
|
133 if (::RemoveDirectory(pathName))
|
rlm@1
|
134 return true;
|
rlm@1
|
135 #ifdef WIN_LONG_PATH2
|
rlm@1
|
136 UString longPath;
|
rlm@1
|
137 if (GetLongPath(pathName, longPath))
|
rlm@1
|
138 return BOOLToBool(::RemoveDirectoryW(longPath));
|
rlm@1
|
139 #endif
|
rlm@1
|
140 return false;
|
rlm@1
|
141 }
|
rlm@1
|
142
|
rlm@1
|
143 #ifdef WIN_LONG_PATH
|
rlm@1
|
144 bool GetLongPaths(LPCWSTR s1, LPCWSTR s2, UString &d1, UString &d2)
|
rlm@1
|
145 {
|
rlm@1
|
146 if (!GetLongPathBase(s1, d1) || !GetLongPathBase(s2, d2))
|
rlm@1
|
147 return false;
|
rlm@1
|
148 if (d1.IsEmpty() && d2.IsEmpty()) return false;
|
rlm@1
|
149 if (d1.IsEmpty()) d1 = s1;
|
rlm@1
|
150 if (d2.IsEmpty()) d2 = s2;
|
rlm@1
|
151 return true;
|
rlm@1
|
152 }
|
rlm@1
|
153 #endif
|
rlm@1
|
154
|
rlm@1
|
155 bool MyMoveFile(LPCTSTR existFileName, LPCTSTR newFileName)
|
rlm@1
|
156 {
|
rlm@1
|
157 if (::MoveFile(existFileName, newFileName))
|
rlm@1
|
158 return true;
|
rlm@1
|
159 #ifdef WIN_LONG_PATH2
|
rlm@1
|
160 UString d1, d2;
|
rlm@1
|
161 if (GetLongPaths(existFileName, newFileName, d1, d2))
|
rlm@1
|
162 return BOOLToBool(::MoveFileW(d1, d2));
|
rlm@1
|
163 #endif
|
rlm@1
|
164 return false;
|
rlm@1
|
165 }
|
rlm@1
|
166
|
rlm@1
|
167 #ifndef _UNICODE
|
rlm@1
|
168 bool MySetFileAttributes(LPCWSTR fileName, DWORD fileAttributes)
|
rlm@1
|
169 {
|
rlm@1
|
170 if (!g_IsNT)
|
rlm@1
|
171 return MySetFileAttributes(GetSysPath(fileName), fileAttributes);
|
rlm@1
|
172 if (::SetFileAttributesW(fileName, fileAttributes))
|
rlm@1
|
173 return true;
|
rlm@1
|
174 #ifdef WIN_LONG_PATH
|
rlm@1
|
175 UString longPath;
|
rlm@1
|
176 if (GetLongPath(fileName, longPath))
|
rlm@1
|
177 return BOOLToBool(::SetFileAttributesW(longPath, fileAttributes));
|
rlm@1
|
178 #endif
|
rlm@1
|
179 return false;
|
rlm@1
|
180 }
|
rlm@1
|
181
|
rlm@1
|
182
|
rlm@1
|
183 bool MyRemoveDirectory(LPCWSTR pathName)
|
rlm@1
|
184 {
|
rlm@1
|
185 if (!g_IsNT)
|
rlm@1
|
186 return MyRemoveDirectory(GetSysPath(pathName));
|
rlm@1
|
187 if (::RemoveDirectoryW(pathName))
|
rlm@1
|
188 return true;
|
rlm@1
|
189 #ifdef WIN_LONG_PATH
|
rlm@1
|
190 UString longPath;
|
rlm@1
|
191 if (GetLongPath(pathName, longPath))
|
rlm@1
|
192 return BOOLToBool(::RemoveDirectoryW(longPath));
|
rlm@1
|
193 #endif
|
rlm@1
|
194 return false;
|
rlm@1
|
195 }
|
rlm@1
|
196
|
rlm@1
|
197 bool MyMoveFile(LPCWSTR existFileName, LPCWSTR newFileName)
|
rlm@1
|
198 {
|
rlm@1
|
199 if (!g_IsNT)
|
rlm@1
|
200 return MyMoveFile(GetSysPath(existFileName), GetSysPath(newFileName));
|
rlm@1
|
201 if (::MoveFileW(existFileName, newFileName))
|
rlm@1
|
202 return true;
|
rlm@1
|
203 #ifdef WIN_LONG_PATH
|
rlm@1
|
204 UString d1, d2;
|
rlm@1
|
205 if (GetLongPaths(existFileName, newFileName, d1, d2))
|
rlm@1
|
206 return BOOLToBool(::MoveFileW(d1, d2));
|
rlm@1
|
207 #endif
|
rlm@1
|
208 return false;
|
rlm@1
|
209 }
|
rlm@1
|
210 #endif
|
rlm@1
|
211
|
rlm@1
|
212 bool MyCreateDirectory(LPCTSTR pathName)
|
rlm@1
|
213 {
|
rlm@1
|
214 if (::CreateDirectory(pathName, NULL))
|
rlm@1
|
215 return true;
|
rlm@1
|
216 #ifdef WIN_LONG_PATH2
|
rlm@1
|
217 if (::GetLastError() != ERROR_ALREADY_EXISTS)
|
rlm@1
|
218 {
|
rlm@1
|
219 UString longPath;
|
rlm@1
|
220 if (GetLongPath(pathName, longPath))
|
rlm@1
|
221 return BOOLToBool(::CreateDirectoryW(longPath, NULL));
|
rlm@1
|
222 }
|
rlm@1
|
223 #endif
|
rlm@1
|
224 return false;
|
rlm@1
|
225 }
|
rlm@1
|
226
|
rlm@1
|
227 #ifndef _UNICODE
|
rlm@1
|
228 bool MyCreateDirectory(LPCWSTR pathName)
|
rlm@1
|
229 {
|
rlm@1
|
230 if (!g_IsNT)
|
rlm@1
|
231 return MyCreateDirectory(GetSysPath(pathName));
|
rlm@1
|
232 if (::CreateDirectoryW(pathName, NULL))
|
rlm@1
|
233 return true;
|
rlm@1
|
234 #ifdef WIN_LONG_PATH
|
rlm@1
|
235 if (::GetLastError() != ERROR_ALREADY_EXISTS)
|
rlm@1
|
236 {
|
rlm@1
|
237 UString longPath;
|
rlm@1
|
238 if (GetLongPath(pathName, longPath))
|
rlm@1
|
239 return BOOLToBool(::CreateDirectoryW(longPath, NULL));
|
rlm@1
|
240 }
|
rlm@1
|
241 #endif
|
rlm@1
|
242 return false;
|
rlm@1
|
243 }
|
rlm@1
|
244 #endif
|
rlm@1
|
245
|
rlm@1
|
246 /*
|
rlm@1
|
247 bool CreateComplexDirectory(LPCTSTR pathName)
|
rlm@1
|
248 {
|
rlm@1
|
249 NName::CParsedPath path;
|
rlm@1
|
250 path.ParsePath(pathName);
|
rlm@1
|
251 CSysString fullPath = path.Prefix;
|
rlm@1
|
252 DWORD errorCode = ERROR_SUCCESS;
|
rlm@1
|
253 for (int i = 0; i < path.PathParts.Size(); i++)
|
rlm@1
|
254 {
|
rlm@1
|
255 const CSysString &string = path.PathParts[i];
|
rlm@1
|
256 if (string.IsEmpty())
|
rlm@1
|
257 {
|
rlm@1
|
258 if (i != path.PathParts.Size() - 1)
|
rlm@1
|
259 return false;
|
rlm@1
|
260 return true;
|
rlm@1
|
261 }
|
rlm@1
|
262 fullPath += path.PathParts[i];
|
rlm@1
|
263 if (!MyCreateDirectory(fullPath))
|
rlm@1
|
264 {
|
rlm@1
|
265 DWORD errorCode = GetLastError();
|
rlm@1
|
266 if (errorCode != ERROR_ALREADY_EXISTS)
|
rlm@1
|
267 return false;
|
rlm@1
|
268 }
|
rlm@1
|
269 fullPath += NName::kDirDelimiter;
|
rlm@1
|
270 }
|
rlm@1
|
271 return true;
|
rlm@1
|
272 }
|
rlm@1
|
273 */
|
rlm@1
|
274
|
rlm@1
|
275 bool CreateComplexDirectory(LPCTSTR _aPathName)
|
rlm@1
|
276 {
|
rlm@1
|
277 CSysString pathName = _aPathName;
|
rlm@1
|
278 int pos = pathName.ReverseFind(TEXT(CHAR_PATH_SEPARATOR));
|
rlm@1
|
279 if (pos > 0 && pos == pathName.Length() - 1)
|
rlm@1
|
280 {
|
rlm@1
|
281 if (pathName.Length() == 3 && pathName[1] == ':')
|
rlm@1
|
282 return true; // Disk folder;
|
rlm@1
|
283 pathName.Delete(pos);
|
rlm@1
|
284 }
|
rlm@1
|
285 CSysString pathName2 = pathName;
|
rlm@1
|
286 pos = pathName.Length();
|
rlm@1
|
287 for (;;)
|
rlm@1
|
288 {
|
rlm@1
|
289 if (MyCreateDirectory(pathName))
|
rlm@1
|
290 break;
|
rlm@1
|
291 if (::GetLastError() == ERROR_ALREADY_EXISTS)
|
rlm@1
|
292 {
|
rlm@1
|
293 NFind::CFileInfo fileInfo;
|
rlm@1
|
294 if (!NFind::FindFile(pathName, fileInfo)) // For network folders
|
rlm@1
|
295 return true;
|
rlm@1
|
296 if (!fileInfo.IsDir())
|
rlm@1
|
297 return false;
|
rlm@1
|
298 break;
|
rlm@1
|
299 }
|
rlm@1
|
300 pos = pathName.ReverseFind(TEXT(CHAR_PATH_SEPARATOR));
|
rlm@1
|
301 if (pos < 0 || pos == 0)
|
rlm@1
|
302 return false;
|
rlm@1
|
303 if (pathName[pos - 1] == ':')
|
rlm@1
|
304 return false;
|
rlm@1
|
305 pathName = pathName.Left(pos);
|
rlm@1
|
306 }
|
rlm@1
|
307 pathName = pathName2;
|
rlm@1
|
308 while (pos < pathName.Length())
|
rlm@1
|
309 {
|
rlm@1
|
310 pos = pathName.Find(TEXT(CHAR_PATH_SEPARATOR), pos + 1);
|
rlm@1
|
311 if (pos < 0)
|
rlm@1
|
312 pos = pathName.Length();
|
rlm@1
|
313 if (!MyCreateDirectory(pathName.Left(pos)))
|
rlm@1
|
314 return false;
|
rlm@1
|
315 }
|
rlm@1
|
316 return true;
|
rlm@1
|
317 }
|
rlm@1
|
318
|
rlm@1
|
319 #ifndef _UNICODE
|
rlm@1
|
320
|
rlm@1
|
321 bool CreateComplexDirectory(LPCWSTR _aPathName)
|
rlm@1
|
322 {
|
rlm@1
|
323 UString pathName = _aPathName;
|
rlm@1
|
324 int pos = pathName.ReverseFind(WCHAR_PATH_SEPARATOR);
|
rlm@1
|
325 if (pos > 0 && pos == pathName.Length() - 1)
|
rlm@1
|
326 {
|
rlm@1
|
327 if (pathName.Length() == 3 && pathName[1] == L':')
|
rlm@1
|
328 return true; // Disk folder;
|
rlm@1
|
329 pathName.Delete(pos);
|
rlm@1
|
330 }
|
rlm@1
|
331 UString pathName2 = pathName;
|
rlm@1
|
332 pos = pathName.Length();
|
rlm@1
|
333 for (;;)
|
rlm@1
|
334 {
|
rlm@1
|
335 if (MyCreateDirectory(pathName))
|
rlm@1
|
336 break;
|
rlm@1
|
337 if (::GetLastError() == ERROR_ALREADY_EXISTS)
|
rlm@1
|
338 {
|
rlm@1
|
339 NFind::CFileInfoW fileInfo;
|
rlm@1
|
340 if (!NFind::FindFile(pathName, fileInfo)) // For network folders
|
rlm@1
|
341 return true;
|
rlm@1
|
342 if (!fileInfo.IsDir())
|
rlm@1
|
343 return false;
|
rlm@1
|
344 break;
|
rlm@1
|
345 }
|
rlm@1
|
346 pos = pathName.ReverseFind(WCHAR_PATH_SEPARATOR);
|
rlm@1
|
347 if (pos < 0 || pos == 0)
|
rlm@1
|
348 return false;
|
rlm@1
|
349 if (pathName[pos - 1] == L':')
|
rlm@1
|
350 return false;
|
rlm@1
|
351 pathName = pathName.Left(pos);
|
rlm@1
|
352 }
|
rlm@1
|
353 pathName = pathName2;
|
rlm@1
|
354 while (pos < pathName.Length())
|
rlm@1
|
355 {
|
rlm@1
|
356 pos = pathName.Find(WCHAR_PATH_SEPARATOR, pos + 1);
|
rlm@1
|
357 if (pos < 0)
|
rlm@1
|
358 pos = pathName.Length();
|
rlm@1
|
359 if (!MyCreateDirectory(pathName.Left(pos)))
|
rlm@1
|
360 return false;
|
rlm@1
|
361 }
|
rlm@1
|
362 return true;
|
rlm@1
|
363 }
|
rlm@1
|
364
|
rlm@1
|
365 #endif
|
rlm@1
|
366
|
rlm@1
|
367 bool DeleteFileAlways(LPCTSTR name)
|
rlm@1
|
368 {
|
rlm@1
|
369 if (!MySetFileAttributes(name, 0))
|
rlm@1
|
370 return false;
|
rlm@1
|
371 if (::DeleteFile(name))
|
rlm@1
|
372 return true;
|
rlm@1
|
373 #ifdef WIN_LONG_PATH2
|
rlm@1
|
374 UString longPath;
|
rlm@1
|
375 if (GetLongPath(name, longPath))
|
rlm@1
|
376 return BOOLToBool(::DeleteFileW(longPath));
|
rlm@1
|
377 #endif
|
rlm@1
|
378 return false;
|
rlm@1
|
379 }
|
rlm@1
|
380
|
rlm@1
|
381 #ifndef _UNICODE
|
rlm@1
|
382 bool DeleteFileAlways(LPCWSTR name)
|
rlm@1
|
383 {
|
rlm@1
|
384 if (!g_IsNT)
|
rlm@1
|
385 return DeleteFileAlways(GetSysPath(name));
|
rlm@1
|
386 if (!MySetFileAttributes(name, 0))
|
rlm@1
|
387 return false;
|
rlm@1
|
388 if (::DeleteFileW(name))
|
rlm@1
|
389 return true;
|
rlm@1
|
390 #ifdef WIN_LONG_PATH
|
rlm@1
|
391 UString longPath;
|
rlm@1
|
392 if (GetLongPath(name, longPath))
|
rlm@1
|
393 return BOOLToBool(::DeleteFileW(longPath));
|
rlm@1
|
394 #endif
|
rlm@1
|
395 return false;
|
rlm@1
|
396 }
|
rlm@1
|
397 #endif
|
rlm@1
|
398
|
rlm@1
|
399 static bool RemoveDirectorySubItems2(const CSysString pathPrefix, const NFind::CFileInfo &fileInfo)
|
rlm@1
|
400 {
|
rlm@1
|
401 if (fileInfo.IsDir())
|
rlm@1
|
402 return RemoveDirectoryWithSubItems(pathPrefix + fileInfo.Name);
|
rlm@1
|
403 return DeleteFileAlways(pathPrefix + fileInfo.Name);
|
rlm@1
|
404 }
|
rlm@1
|
405
|
rlm@1
|
406 bool RemoveDirectoryWithSubItems(const CSysString &path)
|
rlm@1
|
407 {
|
rlm@1
|
408 NFind::CFileInfo fileInfo;
|
rlm@1
|
409 CSysString pathPrefix = path + NName::kDirDelimiter;
|
rlm@1
|
410 {
|
rlm@1
|
411 NFind::CEnumerator enumerator(pathPrefix + TCHAR(NName::kAnyStringWildcard));
|
rlm@1
|
412 while (enumerator.Next(fileInfo))
|
rlm@1
|
413 if (!RemoveDirectorySubItems2(pathPrefix, fileInfo))
|
rlm@1
|
414 return false;
|
rlm@1
|
415 }
|
rlm@1
|
416 if (!MySetFileAttributes(path, 0))
|
rlm@1
|
417 return false;
|
rlm@1
|
418 return MyRemoveDirectory(path);
|
rlm@1
|
419 }
|
rlm@1
|
420
|
rlm@1
|
421 #ifndef _UNICODE
|
rlm@1
|
422 static bool RemoveDirectorySubItems2(const UString pathPrefix, const NFind::CFileInfoW &fileInfo)
|
rlm@1
|
423 {
|
rlm@1
|
424 if (fileInfo.IsDir())
|
rlm@1
|
425 return RemoveDirectoryWithSubItems(pathPrefix + fileInfo.Name);
|
rlm@1
|
426 return DeleteFileAlways(pathPrefix + fileInfo.Name);
|
rlm@1
|
427 }
|
rlm@1
|
428 bool RemoveDirectoryWithSubItems(const UString &path)
|
rlm@1
|
429 {
|
rlm@1
|
430 NFind::CFileInfoW fileInfo;
|
rlm@1
|
431 UString pathPrefix = path + UString(NName::kDirDelimiter);
|
rlm@1
|
432 {
|
rlm@1
|
433 NFind::CEnumeratorW enumerator(pathPrefix + UString(NName::kAnyStringWildcard));
|
rlm@1
|
434 while (enumerator.Next(fileInfo))
|
rlm@1
|
435 if (!RemoveDirectorySubItems2(pathPrefix, fileInfo))
|
rlm@1
|
436 return false;
|
rlm@1
|
437 }
|
rlm@1
|
438 if (!MySetFileAttributes(path, 0))
|
rlm@1
|
439 return false;
|
rlm@1
|
440 return MyRemoveDirectory(path);
|
rlm@1
|
441 }
|
rlm@1
|
442 #endif
|
rlm@1
|
443
|
rlm@1
|
444 #ifndef _WIN32_WCE
|
rlm@1
|
445
|
rlm@1
|
446 bool MyGetShortPathName(LPCTSTR longPath, CSysString &shortPath)
|
rlm@1
|
447 {
|
rlm@1
|
448 DWORD needLength = ::GetShortPathName(longPath, shortPath.GetBuffer(MAX_PATH + 1), MAX_PATH + 1);
|
rlm@1
|
449 shortPath.ReleaseBuffer();
|
rlm@1
|
450 return (needLength > 0 && needLength < MAX_PATH);
|
rlm@1
|
451 }
|
rlm@1
|
452
|
rlm@1
|
453 bool MyGetFullPathName(LPCTSTR fileName, CSysString &resultPath, int &fileNamePartStartIndex)
|
rlm@1
|
454 {
|
rlm@1
|
455 resultPath.Empty();
|
rlm@1
|
456 LPTSTR fileNamePointer = 0;
|
rlm@1
|
457 LPTSTR buffer = resultPath.GetBuffer(MAX_PATH);
|
rlm@1
|
458 DWORD needLength = ::GetFullPathName(fileName, MAX_PATH + 1, buffer, &fileNamePointer);
|
rlm@1
|
459 resultPath.ReleaseBuffer();
|
rlm@1
|
460 if (needLength == 0)
|
rlm@1
|
461 return false;
|
rlm@1
|
462 if (needLength >= MAX_PATH)
|
rlm@1
|
463 {
|
rlm@1
|
464 #ifdef WIN_LONG_PATH2
|
rlm@1
|
465 needLength++;
|
rlm@1
|
466 buffer = resultPath.GetBuffer(needLength + 1);
|
rlm@1
|
467 DWORD needLength2 = ::GetFullPathNameW(fileName, needLength, buffer, &fileNamePointer);
|
rlm@1
|
468 resultPath.ReleaseBuffer();
|
rlm@1
|
469 if (needLength2 == 0 || needLength2 > needLength)
|
rlm@1
|
470 #endif
|
rlm@1
|
471 return false;
|
rlm@1
|
472 }
|
rlm@1
|
473 if (fileNamePointer == 0)
|
rlm@1
|
474 fileNamePartStartIndex = lstrlen(fileName);
|
rlm@1
|
475 else
|
rlm@1
|
476 fileNamePartStartIndex = (int)(fileNamePointer - buffer);
|
rlm@1
|
477 return true;
|
rlm@1
|
478 }
|
rlm@1
|
479
|
rlm@1
|
480 #ifndef _UNICODE
|
rlm@1
|
481 bool MyGetFullPathName(LPCWSTR fileName, UString &resultPath, int &fileNamePartStartIndex)
|
rlm@1
|
482 {
|
rlm@1
|
483 resultPath.Empty();
|
rlm@1
|
484 if (g_IsNT)
|
rlm@1
|
485 {
|
rlm@1
|
486 LPWSTR fileNamePointer = 0;
|
rlm@1
|
487 LPWSTR buffer = resultPath.GetBuffer(MAX_PATH);
|
rlm@1
|
488 DWORD needLength = ::GetFullPathNameW(fileName, MAX_PATH + 1, buffer, &fileNamePointer);
|
rlm@1
|
489 resultPath.ReleaseBuffer();
|
rlm@1
|
490 if (needLength == 0)
|
rlm@1
|
491 return false;
|
rlm@1
|
492 if (needLength >= MAX_PATH)
|
rlm@1
|
493 {
|
rlm@1
|
494 #ifdef WIN_LONG_PATH
|
rlm@1
|
495 needLength++;
|
rlm@1
|
496 buffer = resultPath.GetBuffer(needLength + 1);
|
rlm@1
|
497 DWORD needLength2 = ::GetFullPathNameW(fileName, needLength, buffer, &fileNamePointer);
|
rlm@1
|
498 resultPath.ReleaseBuffer();
|
rlm@1
|
499 if (needLength2 == 0 || needLength2 > needLength)
|
rlm@1
|
500 #endif
|
rlm@1
|
501 return false;
|
rlm@1
|
502 }
|
rlm@1
|
503 if (fileNamePointer == 0)
|
rlm@1
|
504 fileNamePartStartIndex = MyStringLen(fileName);
|
rlm@1
|
505 else
|
rlm@1
|
506 fileNamePartStartIndex = (int)(fileNamePointer - buffer);
|
rlm@1
|
507 }
|
rlm@1
|
508 else
|
rlm@1
|
509 {
|
rlm@1
|
510 CSysString sysPath;
|
rlm@1
|
511 if (!MyGetFullPathName(GetSysPath(fileName), sysPath, fileNamePartStartIndex))
|
rlm@1
|
512 return false;
|
rlm@1
|
513 UString resultPath1 = GetUnicodePath(sysPath.Left(fileNamePartStartIndex));
|
rlm@1
|
514 UString resultPath2 = GetUnicodePath(sysPath.Mid(fileNamePartStartIndex));
|
rlm@1
|
515 fileNamePartStartIndex = resultPath1.Length();
|
rlm@1
|
516 resultPath = resultPath1 + resultPath2;
|
rlm@1
|
517 }
|
rlm@1
|
518 return true;
|
rlm@1
|
519 }
|
rlm@1
|
520 #endif
|
rlm@1
|
521
|
rlm@1
|
522
|
rlm@1
|
523 bool MyGetFullPathName(LPCTSTR fileName, CSysString &path)
|
rlm@1
|
524 {
|
rlm@1
|
525 int index;
|
rlm@1
|
526 return MyGetFullPathName(fileName, path, index);
|
rlm@1
|
527 }
|
rlm@1
|
528
|
rlm@1
|
529 #ifndef _UNICODE
|
rlm@1
|
530 bool MyGetFullPathName(LPCWSTR fileName, UString &path)
|
rlm@1
|
531 {
|
rlm@1
|
532 int index;
|
rlm@1
|
533 return MyGetFullPathName(fileName, path, index);
|
rlm@1
|
534 }
|
rlm@1
|
535 #endif
|
rlm@1
|
536
|
rlm@1
|
537 bool GetOnlyName(LPCTSTR fileName, CSysString &resultName)
|
rlm@1
|
538 {
|
rlm@1
|
539 int index;
|
rlm@1
|
540 if (!MyGetFullPathName(fileName, resultName, index))
|
rlm@1
|
541 return false;
|
rlm@1
|
542 resultName = resultName.Mid(index);
|
rlm@1
|
543 return true;
|
rlm@1
|
544 }
|
rlm@1
|
545
|
rlm@1
|
546 #ifndef _UNICODE
|
rlm@1
|
547 bool GetOnlyName(LPCWSTR fileName, UString &resultName)
|
rlm@1
|
548 {
|
rlm@1
|
549 int index;
|
rlm@1
|
550 if (!MyGetFullPathName(fileName, resultName, index))
|
rlm@1
|
551 return false;
|
rlm@1
|
552 resultName = resultName.Mid(index);
|
rlm@1
|
553 return true;
|
rlm@1
|
554 }
|
rlm@1
|
555 #endif
|
rlm@1
|
556
|
rlm@1
|
557 bool GetOnlyDirPrefix(LPCTSTR fileName, CSysString &resultName)
|
rlm@1
|
558 {
|
rlm@1
|
559 int index;
|
rlm@1
|
560 if (!MyGetFullPathName(fileName, resultName, index))
|
rlm@1
|
561 return false;
|
rlm@1
|
562 resultName = resultName.Left(index);
|
rlm@1
|
563 return true;
|
rlm@1
|
564 }
|
rlm@1
|
565
|
rlm@1
|
566 #ifndef _UNICODE
|
rlm@1
|
567 bool GetOnlyDirPrefix(LPCWSTR fileName, UString &resultName)
|
rlm@1
|
568 {
|
rlm@1
|
569 int index;
|
rlm@1
|
570 if (!MyGetFullPathName(fileName, resultName, index))
|
rlm@1
|
571 return false;
|
rlm@1
|
572 resultName = resultName.Left(index);
|
rlm@1
|
573 return true;
|
rlm@1
|
574 }
|
rlm@1
|
575 #endif
|
rlm@1
|
576
|
rlm@1
|
577 bool MyGetCurrentDirectory(CSysString &path)
|
rlm@1
|
578 {
|
rlm@1
|
579 DWORD needLength = ::GetCurrentDirectory(MAX_PATH + 1, path.GetBuffer(MAX_PATH + 1));
|
rlm@1
|
580 path.ReleaseBuffer();
|
rlm@1
|
581 return (needLength > 0 && needLength <= MAX_PATH);
|
rlm@1
|
582 }
|
rlm@1
|
583
|
rlm@1
|
584 #ifndef _UNICODE
|
rlm@1
|
585 bool MySetCurrentDirectory(LPCWSTR path)
|
rlm@1
|
586 {
|
rlm@1
|
587 if (g_IsNT)
|
rlm@1
|
588 return BOOLToBool(::SetCurrentDirectoryW(path));
|
rlm@1
|
589 return MySetCurrentDirectory(GetSysPath(path));
|
rlm@1
|
590 }
|
rlm@1
|
591 bool MyGetCurrentDirectory(UString &path)
|
rlm@1
|
592 {
|
rlm@1
|
593 if (g_IsNT)
|
rlm@1
|
594 {
|
rlm@1
|
595 DWORD needLength = ::GetCurrentDirectoryW(MAX_PATH + 1, path.GetBuffer(MAX_PATH + 1));
|
rlm@1
|
596 path.ReleaseBuffer();
|
rlm@1
|
597 return (needLength > 0 && needLength <= MAX_PATH);
|
rlm@1
|
598 }
|
rlm@1
|
599 CSysString sysPath;
|
rlm@1
|
600 if (!MyGetCurrentDirectory(sysPath))
|
rlm@1
|
601 return false;
|
rlm@1
|
602 path = GetUnicodePath(sysPath);
|
rlm@1
|
603 return true;
|
rlm@1
|
604 }
|
rlm@1
|
605 #endif
|
rlm@1
|
606 #endif
|
rlm@1
|
607
|
rlm@1
|
608 bool MySearchPath(LPCTSTR path, LPCTSTR fileName, LPCTSTR extension,
|
rlm@1
|
609 CSysString &resultPath, UINT32 &filePart)
|
rlm@1
|
610 {
|
rlm@1
|
611 LPTSTR filePartPointer;
|
rlm@1
|
612 DWORD value = ::SearchPath(path, fileName, extension,
|
rlm@1
|
613 MAX_PATH, resultPath.GetBuffer(MAX_PATH + 1), &filePartPointer);
|
rlm@1
|
614 filePart = (UINT32)(filePartPointer - (LPCTSTR)resultPath);
|
rlm@1
|
615 resultPath.ReleaseBuffer();
|
rlm@1
|
616 return (value > 0 && value <= MAX_PATH);
|
rlm@1
|
617 }
|
rlm@1
|
618
|
rlm@1
|
619 #ifndef _UNICODE
|
rlm@1
|
620 bool MySearchPath(LPCWSTR path, LPCWSTR fileName, LPCWSTR extension,
|
rlm@1
|
621 UString &resultPath, UINT32 &filePart)
|
rlm@1
|
622 {
|
rlm@1
|
623 if (g_IsNT)
|
rlm@1
|
624 {
|
rlm@1
|
625 LPWSTR filePartPointer = 0;
|
rlm@1
|
626 DWORD value = ::SearchPathW(path, fileName, extension,
|
rlm@1
|
627 MAX_PATH, resultPath.GetBuffer(MAX_PATH + 1), &filePartPointer);
|
rlm@1
|
628 filePart = (UINT32)(filePartPointer - (LPCWSTR)resultPath);
|
rlm@1
|
629 resultPath.ReleaseBuffer();
|
rlm@1
|
630 return (value > 0 && value <= MAX_PATH);
|
rlm@1
|
631 }
|
rlm@1
|
632
|
rlm@1
|
633 CSysString sysPath;
|
rlm@1
|
634 if (!MySearchPath(
|
rlm@1
|
635 path != 0 ? (LPCTSTR)GetSysPath(path): 0,
|
rlm@1
|
636 fileName != 0 ? (LPCTSTR)GetSysPath(fileName): 0,
|
rlm@1
|
637 extension != 0 ? (LPCTSTR)GetSysPath(extension): 0,
|
rlm@1
|
638 sysPath, filePart))
|
rlm@1
|
639 return false;
|
rlm@1
|
640 UString resultPath1 = GetUnicodePath(sysPath.Left(filePart));
|
rlm@1
|
641 UString resultPath2 = GetUnicodePath(sysPath.Mid(filePart));
|
rlm@1
|
642 filePart = resultPath1.Length();
|
rlm@1
|
643 resultPath = resultPath1 + resultPath2;
|
rlm@1
|
644 return true;
|
rlm@1
|
645 }
|
rlm@1
|
646 #endif
|
rlm@1
|
647
|
rlm@1
|
648 bool MyGetTempPath(CSysString &path)
|
rlm@1
|
649 {
|
rlm@1
|
650 DWORD needLength = ::GetTempPath(MAX_PATH + 1, path.GetBuffer(MAX_PATH + 1));
|
rlm@1
|
651 path.ReleaseBuffer();
|
rlm@1
|
652 return (needLength > 0 && needLength <= MAX_PATH);
|
rlm@1
|
653 }
|
rlm@1
|
654
|
rlm@1
|
655 #ifndef _UNICODE
|
rlm@1
|
656 bool MyGetTempPath(UString &path)
|
rlm@1
|
657 {
|
rlm@1
|
658 path.Empty();
|
rlm@1
|
659 if (g_IsNT)
|
rlm@1
|
660 {
|
rlm@1
|
661 DWORD needLength = ::GetTempPathW(MAX_PATH + 1, path.GetBuffer(MAX_PATH + 1));
|
rlm@1
|
662 path.ReleaseBuffer();
|
rlm@1
|
663 return (needLength > 0 && needLength <= MAX_PATH);
|
rlm@1
|
664 }
|
rlm@1
|
665 CSysString sysPath;
|
rlm@1
|
666 if (!MyGetTempPath(sysPath))
|
rlm@1
|
667 return false;
|
rlm@1
|
668 path = GetUnicodePath(sysPath);
|
rlm@1
|
669 return true;
|
rlm@1
|
670 }
|
rlm@1
|
671 #endif
|
rlm@1
|
672
|
rlm@1
|
673 UINT MyGetTempFileName(LPCTSTR dirPath, LPCTSTR prefix, CSysString &path)
|
rlm@1
|
674 {
|
rlm@1
|
675 UINT number = ::GetTempFileName(dirPath, prefix, 0, path.GetBuffer(MAX_PATH + 1));
|
rlm@1
|
676 path.ReleaseBuffer();
|
rlm@1
|
677 return number;
|
rlm@1
|
678 }
|
rlm@1
|
679
|
rlm@1
|
680 #ifndef _UNICODE
|
rlm@1
|
681 UINT MyGetTempFileName(LPCWSTR dirPath, LPCWSTR prefix, UString &path)
|
rlm@1
|
682 {
|
rlm@1
|
683 if (g_IsNT)
|
rlm@1
|
684 {
|
rlm@1
|
685 UINT number = ::GetTempFileNameW(dirPath, prefix, 0, path.GetBuffer(MAX_PATH));
|
rlm@1
|
686 path.ReleaseBuffer();
|
rlm@1
|
687 return number;
|
rlm@1
|
688 }
|
rlm@1
|
689 CSysString sysPath;
|
rlm@1
|
690 UINT number = MyGetTempFileName(
|
rlm@1
|
691 dirPath ? (LPCTSTR)GetSysPath(dirPath): 0,
|
rlm@1
|
692 prefix ? (LPCTSTR)GetSysPath(prefix): 0,
|
rlm@1
|
693 sysPath);
|
rlm@1
|
694 path = GetUnicodePath(sysPath);
|
rlm@1
|
695 return number;
|
rlm@1
|
696 }
|
rlm@1
|
697 #endif
|
rlm@1
|
698
|
rlm@1
|
699 UINT CTempFile::Create(LPCTSTR dirPath, LPCTSTR prefix, CSysString &resultPath)
|
rlm@1
|
700 {
|
rlm@1
|
701 Remove();
|
rlm@1
|
702 UINT number = MyGetTempFileName(dirPath, prefix, resultPath);
|
rlm@1
|
703 if (number != 0)
|
rlm@1
|
704 {
|
rlm@1
|
705 _fileName = resultPath;
|
rlm@1
|
706 _mustBeDeleted = true;
|
rlm@1
|
707 }
|
rlm@1
|
708 return number;
|
rlm@1
|
709 }
|
rlm@1
|
710
|
rlm@1
|
711 bool CTempFile::Create(LPCTSTR prefix, CSysString &resultPath)
|
rlm@1
|
712 {
|
rlm@1
|
713 CSysString tempPath;
|
rlm@1
|
714 if (!MyGetTempPath(tempPath))
|
rlm@1
|
715 return false;
|
rlm@1
|
716 if (Create(tempPath, prefix, resultPath) != 0)
|
rlm@1
|
717 return true;
|
rlm@1
|
718 if (!MyGetWindowsDirectory(tempPath))
|
rlm@1
|
719 return false;
|
rlm@1
|
720 return (Create(tempPath, prefix, resultPath) != 0);
|
rlm@1
|
721 }
|
rlm@1
|
722
|
rlm@1
|
723 bool CTempFile::Remove()
|
rlm@1
|
724 {
|
rlm@1
|
725 if (!_mustBeDeleted)
|
rlm@1
|
726 return true;
|
rlm@1
|
727 _mustBeDeleted = !DeleteFileAlways(_fileName);
|
rlm@1
|
728 return !_mustBeDeleted;
|
rlm@1
|
729 }
|
rlm@1
|
730
|
rlm@1
|
731 #ifndef _UNICODE
|
rlm@1
|
732
|
rlm@1
|
733 UINT CTempFileW::Create(LPCWSTR dirPath, LPCWSTR prefix, UString &resultPath)
|
rlm@1
|
734 {
|
rlm@1
|
735 Remove();
|
rlm@1
|
736 UINT number = MyGetTempFileName(dirPath, prefix, resultPath);
|
rlm@1
|
737 if (number != 0)
|
rlm@1
|
738 {
|
rlm@1
|
739 _fileName = resultPath;
|
rlm@1
|
740 _mustBeDeleted = true;
|
rlm@1
|
741 }
|
rlm@1
|
742 return number;
|
rlm@1
|
743 }
|
rlm@1
|
744
|
rlm@1
|
745 bool CTempFileW::Create(LPCWSTR prefix, UString &resultPath)
|
rlm@1
|
746 {
|
rlm@1
|
747 UString tempPath;
|
rlm@1
|
748 if (!MyGetTempPath(tempPath))
|
rlm@1
|
749 return false;
|
rlm@1
|
750 if (Create(tempPath, prefix, resultPath) != 0)
|
rlm@1
|
751 return true;
|
rlm@1
|
752 if (!MyGetWindowsDirectory(tempPath))
|
rlm@1
|
753 return false;
|
rlm@1
|
754 return (Create(tempPath, prefix, resultPath) != 0);
|
rlm@1
|
755 }
|
rlm@1
|
756
|
rlm@1
|
757 bool CTempFileW::Remove()
|
rlm@1
|
758 {
|
rlm@1
|
759 if (!_mustBeDeleted)
|
rlm@1
|
760 return true;
|
rlm@1
|
761 _mustBeDeleted = !DeleteFileAlways(_fileName);
|
rlm@1
|
762 return !_mustBeDeleted;
|
rlm@1
|
763 }
|
rlm@1
|
764
|
rlm@1
|
765 #endif
|
rlm@1
|
766
|
rlm@1
|
767 bool CreateTempDirectory(LPCTSTR prefix, CSysString &dirName)
|
rlm@1
|
768 {
|
rlm@1
|
769 /*
|
rlm@1
|
770 CSysString prefix = tempPath + prefixChars;
|
rlm@1
|
771 CRandom random;
|
rlm@1
|
772 random.Init();
|
rlm@1
|
773 */
|
rlm@1
|
774 for (;;)
|
rlm@1
|
775 {
|
rlm@1
|
776 CTempFile tempFile;
|
rlm@1
|
777 if (!tempFile.Create(prefix, dirName))
|
rlm@1
|
778 return false;
|
rlm@1
|
779 if (!::DeleteFile(dirName))
|
rlm@1
|
780 return false;
|
rlm@1
|
781 /*
|
rlm@1
|
782 UINT32 randomNumber = random.Generate();
|
rlm@1
|
783 TCHAR randomNumberString[32];
|
rlm@1
|
784 _stprintf(randomNumberString, _T("%04X"), randomNumber);
|
rlm@1
|
785 dirName = prefix + randomNumberString;
|
rlm@1
|
786 */
|
rlm@1
|
787 if (NFind::DoesFileExist(dirName))
|
rlm@1
|
788 continue;
|
rlm@1
|
789 if (MyCreateDirectory(dirName))
|
rlm@1
|
790 return true;
|
rlm@1
|
791 if (::GetLastError() != ERROR_ALREADY_EXISTS)
|
rlm@1
|
792 return false;
|
rlm@1
|
793 }
|
rlm@1
|
794 }
|
rlm@1
|
795
|
rlm@1
|
796 bool CTempDirectory::Create(LPCTSTR prefix)
|
rlm@1
|
797 {
|
rlm@1
|
798 Remove();
|
rlm@1
|
799 return (_mustBeDeleted = CreateTempDirectory(prefix, _tempDir));
|
rlm@1
|
800 }
|
rlm@1
|
801
|
rlm@1
|
802 #ifndef _UNICODE
|
rlm@1
|
803
|
rlm@1
|
804 bool CreateTempDirectory(LPCWSTR prefix, UString &dirName)
|
rlm@1
|
805 {
|
rlm@1
|
806 /*
|
rlm@1
|
807 CSysString prefix = tempPath + prefixChars;
|
rlm@1
|
808 CRandom random;
|
rlm@1
|
809 random.Init();
|
rlm@1
|
810 */
|
rlm@1
|
811 for (;;)
|
rlm@1
|
812 {
|
rlm@1
|
813 CTempFileW tempFile;
|
rlm@1
|
814 if (!tempFile.Create(prefix, dirName))
|
rlm@1
|
815 return false;
|
rlm@1
|
816 if (!DeleteFileAlways(dirName))
|
rlm@1
|
817 return false;
|
rlm@1
|
818 /*
|
rlm@1
|
819 UINT32 randomNumber = random.Generate();
|
rlm@1
|
820 TCHAR randomNumberString[32];
|
rlm@1
|
821 _stprintf(randomNumberString, _T("%04X"), randomNumber);
|
rlm@1
|
822 dirName = prefix + randomNumberString;
|
rlm@1
|
823 */
|
rlm@1
|
824 if (NFind::DoesFileExist(dirName))
|
rlm@1
|
825 continue;
|
rlm@1
|
826 if (MyCreateDirectory(dirName))
|
rlm@1
|
827 return true;
|
rlm@1
|
828 if (::GetLastError() != ERROR_ALREADY_EXISTS)
|
rlm@1
|
829 return false;
|
rlm@1
|
830 }
|
rlm@1
|
831 }
|
rlm@1
|
832
|
rlm@1
|
833 bool CTempDirectoryW::Create(LPCWSTR prefix)
|
rlm@1
|
834 {
|
rlm@1
|
835 Remove();
|
rlm@1
|
836 return (_mustBeDeleted = CreateTempDirectory(prefix, _tempDir));
|
rlm@1
|
837 }
|
rlm@1
|
838
|
rlm@1
|
839 #endif
|
rlm@1
|
840
|
rlm@1
|
841 }}}
|