1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/volume.cpp
3 // Purpose: wxFSVolume - encapsulates system volume information
4 // Author: George Policello
5 // Modified by:
6 // Created: 28 Jan 02
7 // Copyright: (c) 2002 George Policello
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
10
11 // ============================================================================
12 // declarations
13 // ============================================================================
14
15 // ----------------------------------------------------------------------------
16 // headers
17 // ----------------------------------------------------------------------------
18
19 #include "wx/wxprec.h"
20
21
22 #if wxUSE_FSVOLUME
23
24 #include "wx/volume.h"
25
26 #ifndef WX_PRECOMP
27 #if wxUSE_GUI
28 #include "wx/icon.h"
29 #endif
30 #include "wx/intl.h"
31 #include "wx/log.h"
32 #include "wx/hashmap.h"
33 #include "wx/filefn.h"
34 #endif // WX_PRECOMP
35
36 #include "wx/dir.h"
37 #include "wx/dynlib.h"
38 #include "wx/arrimpl.cpp"
39
40 // some compilers require including <windows.h> before <shellapi.h> so do it
41 // even if this is not necessary with most of them
42 #include "wx/msw/wrapwin.h"
43 #include <shellapi.h>
44 #include "wx/msw/wrapshl.h"
45 #include "wx/msw/missing.h"
46
47 #if wxUSE_BASE
48
49 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
50 // Dynamic library function defs.
51 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
52
53 #if wxUSE_DYNLIB_CLASS
54 static wxDynamicLibrary s_mprLib;
55 #endif
56
57 typedef DWORD (WINAPI* WNetOpenEnumPtr)(DWORD, DWORD, DWORD, LPNETRESOURCE, LPHANDLE);
58 typedef DWORD (WINAPI* WNetEnumResourcePtr)(HANDLE, LPDWORD, LPVOID, LPDWORD);
59 typedef DWORD (WINAPI* WNetCloseEnumPtr)(HANDLE);
60
61 static WNetOpenEnumPtr s_pWNetOpenEnum;
62 static WNetEnumResourcePtr s_pWNetEnumResource;
63 static WNetCloseEnumPtr s_pWNetCloseEnum;
64
65 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
66 // Globals/Statics
67 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
68
69 #if defined(__CYGWIN__) && defined(__LP64__)
70 // We can't use long in 64 bit Cygwin build because Cygwin uses LP64 model
71 // (unlike all the other MSW compilers) and long is 64 bits, while
72 // InterlockedExchange(), with which this variable is used, requires a 32
73 // bit-sized value, so use Cygwin-specific type with the right size.
74 typedef __LONG32 wxInterlockedArg_t;
75 #else
76 typedef long wxInterlockedArg_t;
77 #endif
78
79 static wxInterlockedArg_t s_cancelSearch = FALSE;
80
81 struct FileInfo
82 {
FileInfoFileInfo83 FileInfo(unsigned flag=0, wxFSVolumeKind type=wxFS_VOL_OTHER) :
84 m_flags(flag), m_type(type) {}
85
FileInfoFileInfo86 FileInfo(const FileInfo& other) { *this = other; }
operator =FileInfo87 FileInfo& operator=(const FileInfo& other)
88 {
89 m_flags = other.m_flags;
90 m_type = other.m_type;
91 return *this;
92 }
93
94 unsigned m_flags;
95 wxFSVolumeKind m_type;
96 };
97 WX_DECLARE_STRING_HASH_MAP(FileInfo, FileInfoMap);
98 // Cygwin bug (?) destructor for global s_fileInfo is called twice...
GetFileInfoMap()99 static FileInfoMap& GetFileInfoMap()
100 {
101 static FileInfoMap s_fileInfo(25);
102
103 return s_fileInfo;
104 }
105 #define s_fileInfo (GetFileInfoMap())
106
107 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
108 // Local helper functions.
109 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
110
111 //=============================================================================
112 // Function: GetBasicFlags
113 // Purpose: Set basic flags, primarily wxFS_VOL_REMOTE and wxFS_VOL_REMOVABLE.
114 // Notes: - Local and mapped drives are mounted by definition. We have no
115 // way to determine mounted status of network drives, so assume that
116 // all drives are mounted, and let the caller decide otherwise.
117 // - Other flags are 'best guess' from type of drive. The system will
118 // not report the file attributes with any degree of accuracy.
119 //=============================================================================
GetBasicFlags(const wxChar * filename)120 static unsigned GetBasicFlags(const wxChar* filename)
121 {
122 unsigned flags = wxFS_VOL_MOUNTED;
123
124 //----------------------------------
125 // 'Best Guess' based on drive type.
126 //----------------------------------
127 wxFSVolumeKind type;
128 switch(GetDriveType(filename))
129 {
130 case DRIVE_FIXED:
131 type = wxFS_VOL_DISK;
132 break;
133
134 case DRIVE_REMOVABLE:
135 flags |= wxFS_VOL_REMOVABLE;
136 type = wxFS_VOL_FLOPPY;
137 break;
138
139 case DRIVE_CDROM:
140 flags |= wxFS_VOL_REMOVABLE | wxFS_VOL_READONLY;
141 type = wxFS_VOL_CDROM;
142 break;
143
144 case DRIVE_REMOTE:
145 flags |= wxFS_VOL_REMOTE;
146 type = wxFS_VOL_NETWORK;
147 break;
148
149 case DRIVE_NO_ROOT_DIR:
150 flags &= ~wxFS_VOL_MOUNTED;
151 type = wxFS_VOL_OTHER;
152 break;
153
154 default:
155 type = wxFS_VOL_OTHER;
156 break;
157 }
158
159 //-----------------------------------------------------------------------
160 // The following most likely will not modify anything not set above,
161 // and will not work at all for network shares or empty CD ROM drives.
162 // But it is a good check if the Win API ever gets better about reporting
163 // this information.
164 //-----------------------------------------------------------------------
165 SHFILEINFO fi;
166 long rc = SHGetFileInfo(filename, 0, &fi, sizeof(fi), SHGFI_ATTRIBUTES);
167 if (!rc)
168 {
169 // this error is not fatal, so don't show a message to the user about
170 // it, otherwise it would appear every time a generic directory picker
171 // dialog is used and there is a connected network drive
172 wxLogLastError(wxT("SHGetFileInfo"));
173 }
174 else
175 {
176 if (fi.dwAttributes & SFGAO_READONLY)
177 flags |= wxFS_VOL_READONLY;
178 if (fi.dwAttributes & SFGAO_REMOVABLE)
179 flags |= wxFS_VOL_REMOVABLE;
180 }
181
182 //------------------
183 // Flags are cached.
184 //------------------
185 s_fileInfo[filename] = FileInfo(flags, type);
186
187 return flags;
188 } // GetBasicFlags
189
190 //=============================================================================
191 // Function: FilteredAdd
192 // Purpose: Add a file to the list if it meets the filter requirement.
193 // Notes: - See GetBasicFlags for remarks about the Mounted flag.
194 //=============================================================================
FilteredAdd(wxArrayString & list,const wxChar * filename,unsigned flagsSet,unsigned flagsUnset)195 static bool FilteredAdd(wxArrayString& list, const wxChar* filename,
196 unsigned flagsSet, unsigned flagsUnset)
197 {
198 bool accept = true;
199 unsigned flags = GetBasicFlags(filename);
200
201 if (flagsSet & wxFS_VOL_MOUNTED && !(flags & wxFS_VOL_MOUNTED))
202 accept = false;
203 else if (flagsUnset & wxFS_VOL_MOUNTED && (flags & wxFS_VOL_MOUNTED))
204 accept = false;
205 else if (flagsSet & wxFS_VOL_REMOVABLE && !(flags & wxFS_VOL_REMOVABLE))
206 accept = false;
207 else if (flagsUnset & wxFS_VOL_REMOVABLE && (flags & wxFS_VOL_REMOVABLE))
208 accept = false;
209 else if (flagsSet & wxFS_VOL_READONLY && !(flags & wxFS_VOL_READONLY))
210 accept = false;
211 else if (flagsUnset & wxFS_VOL_READONLY && (flags & wxFS_VOL_READONLY))
212 accept = false;
213 else if (flagsSet & wxFS_VOL_REMOTE && !(flags & wxFS_VOL_REMOTE))
214 accept = false;
215 else if (flagsUnset & wxFS_VOL_REMOTE && (flags & wxFS_VOL_REMOTE))
216 accept = false;
217
218 // Add to the list if passed the filter.
219 if (accept)
220 list.Add(filename);
221
222 return accept;
223 } // FilteredAdd
224
225 //=============================================================================
226 // Function: BuildListFromNN
227 // Purpose: Append or remove items from the list
228 // Notes: - There is no way to find all disconnected NN items, or even to find
229 // all items while determining which are connected and not. So this
230 // function will find either all items or connected items.
231 //=============================================================================
BuildListFromNN(wxArrayString & list,NETRESOURCE * pResSrc,unsigned flagsSet,unsigned flagsUnset)232 static void BuildListFromNN(wxArrayString& list, NETRESOURCE* pResSrc,
233 unsigned flagsSet, unsigned flagsUnset)
234 {
235 HANDLE hEnum;
236 int rc;
237
238 //-----------------------------------------------
239 // Scope may be all drives or all mounted drives.
240 //-----------------------------------------------
241 unsigned scope = RESOURCE_GLOBALNET;
242 if (flagsSet & wxFS_VOL_MOUNTED)
243 scope = RESOURCE_CONNECTED;
244
245 //----------------------------------------------------------------------
246 // Enumerate all items, adding only non-containers (ie. network shares).
247 // Containers cause a recursive call to this function for their own
248 // enumeration.
249 //----------------------------------------------------------------------
250 if (rc = s_pWNetOpenEnum(scope, RESOURCETYPE_DISK, 0, pResSrc, &hEnum), rc == NO_ERROR)
251 {
252 DWORD count = 1;
253 DWORD size = 256;
254 NETRESOURCE* pRes = (NETRESOURCE*)malloc(size);
255 memset(pRes, 0, sizeof(NETRESOURCE));
256 while (rc = s_pWNetEnumResource(hEnum, &count, pRes, &size), rc == NO_ERROR || rc == ERROR_MORE_DATA)
257 {
258 if (s_cancelSearch)
259 break;
260
261 if (rc == ERROR_MORE_DATA)
262 {
263 pRes = (NETRESOURCE*)realloc(pRes, size);
264 count = 1;
265 }
266 else if (count == 1)
267 {
268 // Enumerate the container.
269 if (pRes->dwUsage & RESOURCEUSAGE_CONTAINER)
270 {
271 BuildListFromNN(list, pRes, flagsSet, flagsUnset);
272 }
273
274 // Add the network share.
275 else
276 {
277 wxString filename(pRes->lpRemoteName);
278
279 // if the drive is unavailable, FilteredAdd() can hang for
280 // a long time and, moreover, its failure appears to be not
281 // cached so this will happen every time we use it, so try
282 // a much quicker wxDirExists() test (which still hangs but
283 // for much shorter time) for locally mapped drives first
284 // to try to avoid this
285 if ( pRes->lpLocalName &&
286 *pRes->lpLocalName &&
287 !wxDirExists(pRes->lpLocalName) )
288 continue;
289
290 if (!filename.empty())
291 {
292 if (filename.Last() != '\\')
293 filename.Append('\\');
294
295 // The filter function will not know mounted from unmounted, and neither do we unless
296 // we are iterating using RESOURCE_CONNECTED, in which case they all are mounted.
297 // Volumes on disconnected servers, however, will correctly show as unmounted.
298 FilteredAdd(list, filename.t_str(), flagsSet, flagsUnset&~wxFS_VOL_MOUNTED);
299 if (scope == RESOURCE_GLOBALNET)
300 s_fileInfo[filename].m_flags &= ~wxFS_VOL_MOUNTED;
301 }
302 }
303 }
304 else if (count == 0)
305 break;
306 }
307 free(pRes);
308 s_pWNetCloseEnum(hEnum);
309 }
310 } // BuildListFromNN
311
312 //=============================================================================
313 // Function: CompareFcn
314 // Purpose: Used to sort the NN list alphabetically, case insensitive.
315 //=============================================================================
CompareFcn(const wxString & first,const wxString & second)316 static int CompareFcn(const wxString& first, const wxString& second)
317 {
318 return wxStricmp(first.c_str(), second.c_str());
319 } // CompareFcn
320
321 //=============================================================================
322 // Function: BuildRemoteList
323 // Purpose: Append Network Neighborhood items to the list.
324 // Notes: - Mounted gets transalated into Connected. FilteredAdd is told
325 // to ignore the Mounted flag since we need to handle it in a weird
326 // way manually.
327 // - The resulting list is sorted alphabetically.
328 //=============================================================================
BuildRemoteList(wxArrayString & list,NETRESOURCE * pResSrc,unsigned flagsSet,unsigned flagsUnset)329 static bool BuildRemoteList(wxArrayString& list, NETRESOURCE* pResSrc,
330 unsigned flagsSet, unsigned flagsUnset)
331 {
332 // NN query depends on dynamically loaded library.
333 if (!s_pWNetOpenEnum || !s_pWNetEnumResource || !s_pWNetCloseEnum)
334 {
335 wxLogError(_("Failed to load mpr.dll."));
336 return false;
337 }
338
339 // Don't waste time doing the work if the flags conflict.
340 if (flagsSet & wxFS_VOL_MOUNTED && flagsUnset & wxFS_VOL_MOUNTED)
341 return false;
342
343 //----------------------------------------------
344 // Generate the list according to the flags set.
345 //----------------------------------------------
346 BuildListFromNN(list, pResSrc, flagsSet, flagsUnset);
347 list.Sort(CompareFcn);
348
349 //-------------------------------------------------------------------------
350 // If mounted only is requested, then we only need one simple pass.
351 // Otherwise, we need to build a list of all NN volumes and then apply the
352 // list of mounted drives to it.
353 //-------------------------------------------------------------------------
354 if (!(flagsSet & wxFS_VOL_MOUNTED))
355 {
356 // generate.
357 wxArrayString mounted;
358 BuildListFromNN(mounted, pResSrc, flagsSet | wxFS_VOL_MOUNTED, flagsUnset & ~wxFS_VOL_MOUNTED);
359 mounted.Sort(CompareFcn);
360
361 // apply list from bottom to top to preserve indexes if removing items.
362 ssize_t iList = list.GetCount()-1;
363 for (ssize_t iMounted = mounted.GetCount()-1; iMounted >= 0 && iList >= 0; iMounted--)
364 {
365 int compare;
366 wxString all(list[iList]);
367 wxString mount(mounted[iMounted]);
368
369 while (compare =
370 wxStricmp(list[iList].c_str(), mounted[iMounted].c_str()),
371 compare > 0 && iList >= 0)
372 {
373 iList--;
374 all = list[iList];
375 }
376
377
378 if (compare == 0)
379 {
380 // Found the element. Remove it or mark it mounted.
381 if (flagsUnset & wxFS_VOL_MOUNTED)
382 list.RemoveAt(iList);
383 else
384 s_fileInfo[list[iList]].m_flags |= wxFS_VOL_MOUNTED;
385
386 }
387
388 iList--;
389 }
390 }
391
392 return true;
393 } // BuildRemoteList
394
395 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
396 // wxFSVolume
397 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
398
399 //=============================================================================
400 // Function: GetVolumes
401 // Purpose: Generate and return a list of all volumes (drives) available.
402 // Notes:
403 //=============================================================================
GetVolumes(int flagsSet,int flagsUnset)404 wxArrayString wxFSVolumeBase::GetVolumes(int flagsSet, int flagsUnset)
405 {
406 ::InterlockedExchange(&s_cancelSearch, FALSE); // reset
407
408 #if wxUSE_DYNLIB_CLASS
409 if (!s_mprLib.IsLoaded() && s_mprLib.Load(wxT("mpr.dll")))
410 {
411 #ifdef UNICODE
412 s_pWNetOpenEnum = (WNetOpenEnumPtr)s_mprLib.GetSymbol(wxT("WNetOpenEnumW"));
413 s_pWNetEnumResource = (WNetEnumResourcePtr)s_mprLib.GetSymbol(wxT("WNetEnumResourceW"));
414 #else
415 s_pWNetOpenEnum = (WNetOpenEnumPtr)s_mprLib.GetSymbol(wxT("WNetOpenEnumA"));
416 s_pWNetEnumResource = (WNetEnumResourcePtr)s_mprLib.GetSymbol(wxT("WNetEnumResourceA"));
417 #endif
418 s_pWNetCloseEnum = (WNetCloseEnumPtr)s_mprLib.GetSymbol(wxT("WNetCloseEnum"));
419 }
420 #endif
421
422 wxArrayString list;
423
424 //-------------------------------
425 // Local and mapped drives first.
426 //-------------------------------
427 // Allocate the required space for the API call.
428 const DWORD chars = GetLogicalDriveStrings(0, NULL);
429 TCHAR* buf = new TCHAR[chars+1];
430
431 // Get the list of drives.
432 GetLogicalDriveStrings(chars, buf);
433
434 // Parse the list into an array, applying appropriate filters.
435 TCHAR *pVol;
436 pVol = buf;
437 while (*pVol)
438 {
439 FilteredAdd(list, pVol, flagsSet, flagsUnset);
440 pVol = pVol + wxStrlen(pVol) + 1;
441 }
442
443 // Cleanup.
444 delete[] buf;
445
446 //---------------------------
447 // Network Neighborhood next.
448 //---------------------------
449
450 // not exclude remote and not removable
451 if (!(flagsUnset & wxFS_VOL_REMOTE) &&
452 !(flagsSet & wxFS_VOL_REMOVABLE)
453 )
454 {
455 // The returned list will be sorted alphabetically. We don't pass
456 // our in since we don't want to change to order of the local drives.
457 wxArrayString nn;
458 if (BuildRemoteList(nn, 0, flagsSet, flagsUnset))
459 {
460 for (size_t idx = 0; idx < nn.GetCount(); idx++)
461 list.Add(nn[idx]);
462 }
463 }
464
465 return list;
466 } // GetVolumes
467
468 //=============================================================================
469 // Function: CancelSearch
470 // Purpose: Instruct an active search to stop.
471 // Notes: - This will only sensibly be called by a thread other than the one
472 // performing the search. This is the only thread-safe function
473 // provided by the class.
474 //=============================================================================
CancelSearch()475 void wxFSVolumeBase::CancelSearch()
476 {
477 ::InterlockedExchange(&s_cancelSearch, TRUE);
478 } // CancelSearch
479
480 //=============================================================================
481 // Function: constructor
482 // Purpose: default constructor
483 //=============================================================================
wxFSVolumeBase()484 wxFSVolumeBase::wxFSVolumeBase()
485 {
486 m_isOk = false;
487 } // wxVolume
488
489 //=============================================================================
490 // Function: constructor
491 // Purpose: constructor that calls Create
492 //=============================================================================
wxFSVolumeBase(const wxString & name)493 wxFSVolumeBase::wxFSVolumeBase(const wxString& name)
494 {
495 Create(name);
496 } // wxVolume
497
498 //=============================================================================
499 // Function: Create
500 // Purpose: Finds, logs in, etc. to the request volume.
501 //=============================================================================
Create(const wxString & name)502 bool wxFSVolumeBase::Create(const wxString& name)
503 {
504 // assume fail.
505 m_isOk = false;
506
507 // supplied.
508 m_volName = name;
509
510 // Display name.
511 SHFILEINFO fi;
512 long rc = SHGetFileInfo(m_volName.t_str(), 0, &fi, sizeof(fi), SHGFI_DISPLAYNAME);
513 if (!rc)
514 {
515 wxLogError(_("Cannot read typename from '%s'!"), m_volName.c_str());
516 return false;
517 }
518 m_dispName = fi.szDisplayName;
519
520 // all tests passed.
521 m_isOk = true;
522 return true;
523 } // Create
524
525 //=============================================================================
526 // Function: IsOk
527 // Purpose: returns true if the volume is legal.
528 // Notes: For fixed disks, it must exist. For removable disks, it must also
529 // be present. For Network Shares, it must also be logged in, etc.
530 //=============================================================================
IsOk() const531 bool wxFSVolumeBase::IsOk() const
532 {
533 return m_isOk;
534 } // IsOk
535
536 //=============================================================================
537 // Function: GetKind
538 // Purpose: Return the type of the volume.
539 //=============================================================================
GetKind() const540 wxFSVolumeKind wxFSVolumeBase::GetKind() const
541 {
542 if (!m_isOk)
543 return wxFS_VOL_OTHER;
544
545 FileInfoMap::iterator itr = s_fileInfo.find(m_volName);
546 if (itr == s_fileInfo.end())
547 return wxFS_VOL_OTHER;
548
549 return itr->second.m_type;
550 }
551
552 //=============================================================================
553 // Function: GetFlags
554 // Purpose: Return the caches flags for this volume.
555 // Notes: - Returns -1 if no flags were cached.
556 //=============================================================================
GetFlags() const557 int wxFSVolumeBase::GetFlags() const
558 {
559 if (!m_isOk)
560 return -1;
561
562 FileInfoMap::iterator itr = s_fileInfo.find(m_volName);
563 if (itr == s_fileInfo.end())
564 return -1;
565
566 return itr->second.m_flags;
567 } // GetFlags
568
569 #endif // wxUSE_BASE
570
571 // ============================================================================
572 // wxFSVolume
573 // ============================================================================
574
575 #if wxUSE_GUI
576
InitIcons()577 void wxFSVolume::InitIcons()
578 {
579 m_icons.Alloc(wxFS_VOL_ICO_MAX);
580 wxIcon null;
581 for (int idx = 0; idx < wxFS_VOL_ICO_MAX; idx++)
582 m_icons.Add(null);
583 }
584
585 //=============================================================================
586 // Function: GetIcon
587 // Purpose: return the requested icon.
588 //=============================================================================
589
GetIcon(wxFSIconType type) const590 wxIcon wxFSVolume::GetIcon(wxFSIconType type) const
591 {
592 wxCHECK_MSG( type >= 0 && (size_t)type < m_icons.GetCount(), wxNullIcon,
593 wxT("wxFSIconType::GetIcon(): invalid icon index") );
594
595 #ifdef __WXMSW__
596 // Load on demand.
597 if (m_icons[type].IsNull())
598 {
599 UINT flags = SHGFI_ICON;
600 switch (type)
601 {
602 case wxFS_VOL_ICO_SMALL:
603 flags |= SHGFI_SMALLICON;
604 break;
605
606 case wxFS_VOL_ICO_LARGE:
607 flags |= SHGFI_SHELLICONSIZE;
608 break;
609
610 case wxFS_VOL_ICO_SEL_SMALL:
611 flags |= SHGFI_SMALLICON | SHGFI_OPENICON;
612 break;
613
614 case wxFS_VOL_ICO_SEL_LARGE:
615 flags |= SHGFI_SHELLICONSIZE | SHGFI_OPENICON;
616 break;
617
618 case wxFS_VOL_ICO_MAX:
619 wxFAIL_MSG(wxT("wxFS_VOL_ICO_MAX is not valid icon type"));
620 break;
621 }
622
623 SHFILEINFO fi;
624 long rc = SHGetFileInfo(m_volName.t_str(), 0, &fi, sizeof(fi), flags);
625 if (!rc || !fi.hIcon)
626 {
627 wxLogError(_("Cannot load icon from '%s'."), m_volName.c_str());
628 }
629 else
630 {
631 m_icons[type].CreateFromHICON((WXHICON)fi.hIcon);
632 }
633 }
634
635 return m_icons[type];
636 #else
637 wxFAIL_MSG(wxS("Can't convert HICON to wxIcon in this port."));
638 return wxNullIcon;
639 #endif
640 } // GetIcon
641
642 #endif // wxUSE_GUI
643
644 #endif // wxUSE_FSVOLUME
645