DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/dos/drive_cache.cpp
00001 /*
00002  *  Copyright (C) 2002-2015  The DOSBox Team
00003  *
00004  *  This program is free software; you can redistribute it and/or modify
00005  *  it under the terms of the GNU General Public License as published by
00006  *  the Free Software Foundation; either version 2 of the License, or
00007  *  (at your option) any later version.
00008  *
00009  *  This program is distributed in the hope that it will be useful,
00010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *  GNU General Public License for more details.
00013  *
00014  *  You should have received a copy of the GNU General Public License
00015  *  along with this program; if not, write to the Free Software
00016  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00017  */
00018 
00019 
00020 #include "drives.h"
00021 #include "dos_inc.h"
00022 #include "support.h"
00023 #include "cross.h"
00024 
00025 // STL stuff
00026 #include <vector>
00027 #include <iterator>
00028 #include <algorithm>
00029 
00030 #if defined (WIN32)   /* Win 32 */
00031 #define WIN32_LEAN_AND_MEAN        // Exclude rarely-used stuff from 
00032 #include <windows.h>
00033 #endif
00034 
00035 #if defined (OS2)
00036 #define INCL_DOSERRORS
00037 #define INCL_DOSFILEMGR
00038 #include <os2.h>
00039 #endif
00040 
00041 int fileInfoCounter = 0;
00042 
00043 bool SortByName(DOS_Drive_Cache::CFileInfo* const &a, DOS_Drive_Cache::CFileInfo* const &b) {
00044     return strcmp(a->shortname,b->shortname)<0;
00045 }
00046 
00047 bool SortByNameRev(DOS_Drive_Cache::CFileInfo* const &a, DOS_Drive_Cache::CFileInfo* const &b) {
00048     return strcmp(a->shortname,b->shortname)>0;
00049 }
00050 
00051 bool SortByDirName(DOS_Drive_Cache::CFileInfo* const &a, DOS_Drive_Cache::CFileInfo* const &b) {
00052     // Directories first...
00053     if (a->isDir!=b->isDir) return (a->isDir>b->isDir); 
00054     return strcmp(a->shortname,b->shortname)<0;
00055 }
00056 
00057 bool SortByDirNameRev(DOS_Drive_Cache::CFileInfo* const &a, DOS_Drive_Cache::CFileInfo* const &b) {
00058     // Directories first...
00059     if (a->isDir!=b->isDir) return (a->isDir>b->isDir); 
00060     return strcmp(a->shortname,b->shortname)>0;
00061 }
00062 
00063 DOS_Drive_Cache::DOS_Drive_Cache(void) {
00064     dirBase         = new CFileInfo;
00065     save_dir        = 0;
00066     srchNr          = 0;
00067     label[0]        = 0;
00068     nextFreeFindFirst   = 0;
00069     for (Bit32u i=0; i<MAX_OPENDIRS; i++) { dirSearch[i] = 0; dirFindFirst[i] = 0; };
00070     SetDirSort(DIRALPHABETICAL);
00071     updatelabel = true;
00072 }
00073 
00074 DOS_Drive_Cache::DOS_Drive_Cache(const char* path, DOS_Drive *drive) {
00075     dirBase         = new CFileInfo;
00076     save_dir        = 0;
00077     srchNr          = 0;
00078     label[0]        = 0;
00079     nextFreeFindFirst   = 0;
00080     for (Bit32u i=0; i<MAX_OPENDIRS; i++) { dirSearch[i] = 0; dirFindFirst[i] = 0; };
00081     SetDirSort(DIRALPHABETICAL);
00082     SetBaseDir(path,drive);
00083     updatelabel = true;
00084 }
00085 
00086 DOS_Drive_Cache::~DOS_Drive_Cache(void) {
00087     Clear();
00088     for (Bit32u i=0; i<MAX_OPENDIRS; i++) { DeleteFileInfo(dirFindFirst[i]); dirFindFirst[i]=0; };
00089 }
00090 
00091 void DOS_Drive_Cache::Clear(void) {
00092     DeleteFileInfo(dirBase); dirBase = 0;
00093     nextFreeFindFirst   = 0;
00094     for (Bit32u i=0; i<MAX_OPENDIRS; i++) dirSearch[i] = 0;
00095 }
00096 
00097 void DOS_Drive_Cache::EmptyCache(void) {
00098     // Empty Cache and reinit
00099     Clear();
00100     dirBase     = new CFileInfo;
00101     save_dir    = 0;
00102     srchNr      = 0;
00103     SetBaseDir(basePath,drive);
00104 }
00105 
00106 void DOS_Drive_Cache::SetLabel(const char* vname,bool cdrom,bool allowupdate) {
00107 /* allowupdate defaults to true. if mount sets a label then allowupdate is 
00108  * false and will this function return at once after the first call.
00109  * The label will be set at the first call. */
00110 
00111     if(!this->updatelabel) return;
00112     this->updatelabel = allowupdate;
00113     Set_Label(vname,label,cdrom);
00114     LOG(LOG_DOSMISC,LOG_NORMAL)("DIRCACHE: Set volume label to %s",label);
00115 }
00116 
00117 Bit16u DOS_Drive_Cache::GetFreeID(CFileInfo* dir) {
00118     if (dir->id != MAX_OPENDIRS)
00119         return dir->id;
00120     for (Bit16u i=0; i<MAX_OPENDIRS; i++) {
00121         if (!dirSearch[i]) {
00122             dir->id = i;
00123             return i;
00124         }
00125     }
00126     LOG(LOG_FILES,LOG_NORMAL)("DIRCACHE: Too many open directories!");
00127     dir->id=0;
00128     return 0;
00129 }
00130 
00131 void DOS_Drive_Cache::SetBaseDir(const char* baseDir, DOS_Drive *drive) {
00132     Bit16u id;
00133     strcpy(basePath,baseDir);
00134     this->drive = drive;
00135     if (OpenDir(baseDir,id)) {
00136         char* result = 0;
00137         ReadDir(id,result);
00138     };
00139     // Get Volume Label
00140 #if defined (WIN32) || defined (OS2)
00141     bool cdrom = false;
00142     char labellocal[256]={ 0 };
00143     char drives[4] = "C:\\";
00144     drives[0] = basePath[0];
00145 #if defined (WIN32)
00146     if (GetVolumeInformation(drives,labellocal,256,NULL,NULL,NULL,NULL,0)) {
00147     UINT test = GetDriveType(drives);
00148     if(test == DRIVE_CDROM) cdrom = true;
00149 #else // OS2
00150     //TODO determine wether cdrom or not!
00151     FSINFO fsinfo;
00152     ULONG drivenumber = drive[0];
00153     if (drivenumber > 26) { // drive letter was lowercase
00154         drivenumber = drive[0] - 'a' + 1;
00155     }
00156     APIRET rc = DosQueryFSInfo(drivenumber, FSIL_VOLSER, &fsinfo, sizeof(FSINFO));
00157     if (rc == NO_ERROR) {
00158 #endif
00159         /* Set label and allow being updated */
00160         SetLabel(labellocal,cdrom,true);
00161     }
00162 #endif
00163 }
00164 
00165 void DOS_Drive_Cache::ExpandName(char* path) {
00166     strcpy(path,GetExpandName(path));
00167 }
00168 
00169 char* DOS_Drive_Cache::GetExpandName(const char* path) {
00170     static char work [CROSS_LEN] = { 0 };
00171     char dir [CROSS_LEN]; 
00172 
00173     work[0] = 0;
00174     strcpy (dir,path);
00175 
00176     const char* pos = strrchr(path,CROSS_FILESPLIT);
00177 
00178     if (pos) dir[pos-path+1] = 0;
00179     CFileInfo* dirInfo = FindDirInfo(dir, work);
00180         
00181     if (pos) {
00182         // Last Entry = File
00183         strcpy(dir,pos+1); 
00184         GetLongName(dirInfo, dir);
00185         strcat(work,dir);
00186     }
00187 
00188     if (*work) {
00189         size_t len = strlen(work);
00190 #if defined (WIN32) 
00191         if((work[len-1] == CROSS_FILESPLIT ) && (len >= 2) && (work[len-2] != ':')) {
00192 #else
00193         if((len > 1) && (work[len-1] == CROSS_FILESPLIT )) {
00194 #endif       
00195             work[len-1] = 0; // Remove trailing slashes except when in root
00196         }
00197     }
00198     return work;
00199 }
00200 
00201 void DOS_Drive_Cache::AddEntry(const char* path, bool checkExists) {
00202     // Get Last part...
00203     char file   [CROSS_LEN];
00204     char expand [CROSS_LEN];
00205 
00206     CFileInfo* dir = FindDirInfo(path,expand);
00207     const char* pos = strrchr(path,CROSS_FILESPLIT);
00208 
00209     if (pos) {
00210         strcpy(file,pos+1); 
00211         // Check if file already exists, then don't add new entry...
00212         if (checkExists) {
00213             if (GetLongName(dir,file)>=0) return;
00214         }
00215 
00216         CreateEntry(dir,file,false);
00217 
00218         Bits index = GetLongName(dir,file);
00219         if (index>=0) {
00220             Bit32u i;
00221             // Check if there are any open search dir that are affected by this...
00222             if (dir) for (i=0; i<MAX_OPENDIRS; i++) {
00223                 if ((dirSearch[i]==dir) && ((Bit32u)index<=dirSearch[i]->nextEntry)) 
00224                     dirSearch[i]->nextEntry++;
00225             }
00226         }
00227         //      LOG_DEBUG("DIR: Added Entry %s",path);
00228     } else {
00229 //      LOG_DEBUG("DIR: Error: Failed to add %s",path); 
00230     }
00231 }
00232 
00233 void DOS_Drive_Cache::DeleteEntry(const char* path, bool ignoreLastDir) {
00234     CacheOut(path,ignoreLastDir);
00235     if (dirSearch[srchNr] && (dirSearch[srchNr]->nextEntry>0)) dirSearch[srchNr]->nextEntry--;
00236 
00237     if (!ignoreLastDir) {
00238         // Check if there are any open search dir that are affected by this...
00239         Bit32u i;
00240         char expand [CROSS_LEN];
00241         CFileInfo* dir = FindDirInfo(path,expand);
00242         if (dir) for (i=0; i<MAX_OPENDIRS; i++) {
00243             if ((dirSearch[i]==dir) && (dirSearch[i]->nextEntry>0)) 
00244                 dirSearch[i]->nextEntry--;
00245         }   
00246     }
00247 }
00248 
00249 void DOS_Drive_Cache::CacheOut(const char* path, bool ignoreLastDir) {
00250     char expand[CROSS_LEN] = { 0 };
00251     CFileInfo* dir;
00252     
00253     if (ignoreLastDir) {
00254         char tmp[CROSS_LEN] = { 0 };
00255         Bit32s len=0;
00256         const char* pos = strrchr(path,CROSS_FILESPLIT);
00257         if (pos) len = (Bit32s)(pos - path);
00258         if (len>0) { 
00259             safe_strncpy(tmp,path,len+1); 
00260         } else  {
00261             strcpy(tmp,path);
00262         }
00263         dir = FindDirInfo(tmp,expand);
00264     } else {
00265         dir = FindDirInfo(path,expand); 
00266     }
00267 
00268 //  LOG_DEBUG("DIR: Caching out %s : dir %s",expand,dir->orgname);
00269 //  clear cache first?
00270     for (Bit32u i=0; i<MAX_OPENDIRS; i++) {
00271         dirSearch[i] = 0; //free[i] = true;    
00272     }
00273     // delete file objects...
00274     for(Bit32u i=0; i<dir->fileList.size(); i++) {
00275         if (dirSearch[srchNr]==dir->fileList[i]) dirSearch[srchNr] = 0;
00276         DeleteFileInfo(dir->fileList[i]); dir->fileList[i] = 0;
00277     }
00278     // clear lists
00279     dir->fileList.clear();
00280     dir->longNameList.clear();
00281     save_dir = 0;
00282 }
00283 
00284 bool DOS_Drive_Cache::IsCachedIn(CFileInfo* curDir) {
00285     return (curDir->fileList.size()>0);
00286 }
00287 
00288 
00289 bool DOS_Drive_Cache::GetShortName(const char* fullname, char* shortname) {
00290     // Get Dir Info
00291     char expand[CROSS_LEN] = {0};
00292     CFileInfo* curDir = FindDirInfo(fullname,expand);
00293 
00294     std::vector<CFileInfo*>::size_type filelist_size = curDir->longNameList.size();
00295     if (GCC_UNLIKELY(filelist_size<=0)) return false;
00296 
00297     Bits low        = 0;
00298     Bits high       = (Bits)(filelist_size-1);
00299     Bits mid, res;
00300 
00301     while (low<=high) {
00302         mid = (low+high)/2;
00303         res = strcmp(fullname,curDir->longNameList[(size_t)mid]->orgname);
00304         if (res>0)  low  = mid+1; else
00305         if (res<0)  high = mid-1; 
00306         else {
00307             strcpy(shortname,curDir->longNameList[(size_t)mid]->shortname);
00308             return true;
00309         };
00310     }
00311     return false;
00312 }
00313 
00314 int DOS_Drive_Cache::CompareShortname(const char* compareName, const char* shortName) {
00315     char const* cpos = strchr(shortName,'~');
00316     if (cpos) {
00317 /* the following code is replaced as it's not safe when char* is 64 bits */
00318 /*      Bits compareCount1  = (int)cpos - (int)shortName;
00319         char* endPos        = strchr(cpos,'.');
00320         Bitu numberSize     = endPos ? int(endPos)-int(cpos) : strlen(cpos);
00321         
00322         char* lpos          = strchr(compareName,'.');
00323         Bits compareCount2  = lpos ? int(lpos)-int(compareName) : strlen(compareName);
00324         if (compareCount2>8) compareCount2 = 8;
00325 
00326         compareCount2 -= numberSize;
00327         if (compareCount2>compareCount1) compareCount1 = compareCount2;
00328 */
00329         size_t compareCount1 = strcspn(shortName,"~");
00330         size_t numberSize    = strcspn(cpos,".");
00331         size_t compareCount2 = strcspn(compareName,".");
00332         if(compareCount2 > 8) compareCount2 = 8;
00333         /* We want 
00334          * compareCount2 -= numberSize;
00335          * if (compareCount2>compareCount1) compareCount1 = compareCount2;
00336          * but to prevent negative numbers: 
00337          */
00338         if(compareCount2 > compareCount1 + numberSize)
00339             compareCount1 = compareCount2 - numberSize;
00340         return strncmp(compareName,shortName,compareCount1);
00341     }
00342     return strcmp(compareName,shortName);
00343 }
00344 
00345 Bitu DOS_Drive_Cache::CreateShortNameID(CFileInfo* curDir, const char* name) {
00346     std::vector<CFileInfo*>::size_type filelist_size = curDir->longNameList.size();
00347     if (GCC_UNLIKELY(filelist_size<=0)) return 1;   // shortener IDs start with 1
00348 
00349     Bitu foundNr    = 0;    
00350     Bits low        = 0;
00351     Bits high       = (Bits)(filelist_size-1);
00352     Bits mid, res;
00353 
00354     while (low<=high) {
00355         mid = (low+high)/2;
00356         res = CompareShortname(name,curDir->longNameList[(size_t)mid]->shortname);
00357         
00358         if (res>0)  low  = mid+1; else
00359         if (res<0)  high = mid-1; 
00360         else {
00361             // any more same x chars in next entries ?  
00362             do {
00363                 foundNr = curDir->longNameList[(size_t)mid]->shortNr;
00364                 mid++;
00365             } while((Bitu)mid<curDir->longNameList.size() && (CompareShortname(name,curDir->longNameList[(size_t)mid]->shortname)==0));
00366             break;
00367         };
00368     }
00369     return foundNr+1;
00370 }
00371 
00372 bool DOS_Drive_Cache::RemoveTrailingDot(char* shortname) {
00373 // remove trailing '.' if no extension is available (Linux compatibility)
00374     size_t len = strlen(shortname);
00375     if (len && (shortname[len-1]=='.')) {
00376         if (len==1) return false;
00377         if ((len==2) && (shortname[0]=='.')) return false;
00378         shortname[len-1] = 0;   
00379         return true;
00380     }   
00381     return false;
00382 }
00383 
00384 #define WINE_DRIVE_SUPPORT 1
00385 #if WINE_DRIVE_SUPPORT
00386 //Changes to interact with WINE by supporting their namemangling.
00387 //The code is rather slow, because orglist is unordered, so it needs to be avoided if possible.
00388 //Hence the tests in GetLongFileName
00389 
00390 
00391 // From the Wine project
00392 static Bits wine_hash_short_file_name( char* name, char* buffer )
00393 {
00394     static const char hash_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
00395     static const char invalid_chars[] = { '*','?','<','>','|','"','+','=',',',';','[',']',' ','\345','~','.',0 };
00396     char* p;
00397     char* ext;
00398     char* end = name + strlen(name);
00399     char* dst;
00400     unsigned short hash;
00401     int i;
00402 
00403     // Compute the hash code of the file name
00404     for (p = name, hash = 0xbeef; p < end - 1; p++)
00405         hash = (hash<<3) ^ (hash>>5) ^ tolower(*p) ^ (tolower(p[1]) << 8);
00406     hash = (hash<<3) ^ (hash>>5) ^ tolower(*p); // Last character
00407 
00408 
00409     // Find last dot for start of the extension
00410     for (p = name + 1, ext = NULL; p < end - 1; p++) if (*p == '.') ext = p;
00411 
00412     // Copy first 4 chars, replacing invalid chars with '_'
00413     for (i = 4, p = name, dst = buffer; i > 0; i--, p++)
00414     {
00415         if (p == end || p == ext) break;
00416         *dst++ = (*p < 0 || strchr( invalid_chars, *p ) != NULL) ? '_' : toupper(*p);
00417     }
00418     // Pad to 5 chars with '~'
00419     while (i-- >= 0) *dst++ = '~';
00420 
00421     // Insert hash code converted to 3 ASCII chars
00422     *dst++ = hash_chars[(hash >> 10) & 0x1f];
00423     *dst++ = hash_chars[(hash >> 5) & 0x1f];
00424     *dst++ = hash_chars[hash & 0x1f];
00425 
00426     // Copy the first 3 chars of the extension (if any)
00427     if (ext)
00428     {
00429         *dst++ = '.';
00430         for (i = 3, ext++; (i > 0) && ext < end; i--, ext++)
00431             *dst++ = (*ext < 0 || strchr( invalid_chars, *ext ) != NULL) ? '_' : toupper(*ext);
00432     }
00433 
00434     return (Bits)(dst - buffer);
00435 }
00436 #endif
00437 
00438 Bits DOS_Drive_Cache::GetLongName(CFileInfo* curDir, char* shortName) {
00439     std::vector<CFileInfo*>::size_type filelist_size = curDir->fileList.size();
00440     if (GCC_UNLIKELY(filelist_size<=0)) return -1;
00441 
00442     // Remove dot, if no extension...
00443     RemoveTrailingDot(shortName);
00444     // Search long name and return array number of element
00445     Bits low    = 0;
00446     Bits high   = (Bits)(filelist_size-1);
00447     Bits mid,res;
00448     while (low<=high) {
00449         mid = (low+high)/2;
00450         res = strcmp(shortName,curDir->fileList[(size_t)mid]->shortname);
00451         if (res>0)  low  = mid+1; else
00452         if (res<0)  high = mid-1; else
00453         {   // Found
00454             strcpy(shortName,curDir->fileList[(size_t)mid]->orgname);
00455             return mid;
00456         };
00457     }
00458 #ifdef WINE_DRIVE_SUPPORT
00459     if (strlen(shortName) < 8 || shortName[4] != '~' || shortName[5] == '.' || shortName[6] == '.' || shortName[7] == '.') return -1; // not available
00460     // else it's most likely a Wine style short name ABCD~###, # = not dot  (length at least 8) 
00461     // The above test is rather strict as the following loop can be really slow if filelist_size is large.
00462     char buff[CROSS_LEN];
00463     for (Bitu i = 0; i < filelist_size; i++) {
00464         res = wine_hash_short_file_name(curDir->fileList[i]->orgname,buff);
00465         buff[res] = 0;
00466         if (!strcmp(shortName,buff)) {  
00467             // Found
00468             strcpy(shortName,curDir->fileList[i]->orgname);
00469             return (Bits)i;
00470         }
00471     }
00472 #endif
00473     // not available
00474     return -1;
00475 }
00476 
00477 bool DOS_Drive_Cache::RemoveSpaces(char* str) {
00478 // Removes all spaces
00479     char*   curpos  = str;
00480     char*   chkpos  = str;
00481     while (*chkpos!=0) { 
00482         if (*chkpos==' ') chkpos++; else *curpos++ = *chkpos++; 
00483     }
00484     *curpos = 0;
00485     return (curpos!=chkpos);
00486 }
00487 
00488 char * shiftjis_upcase(char * str);
00489 
00490 void DOS_Drive_Cache::CreateShortName(CFileInfo* curDir, CFileInfo* info) {
00491     Bits    len         = 0;
00492     bool    createShort = false;
00493 
00494     char tmpNameBuffer[CROSS_LEN];
00495 
00496     char* tmpName = tmpNameBuffer;
00497 
00498     // Remove Spaces
00499     strcpy(tmpName,info->orgname);
00500     if (IS_PC98_ARCH)
00501         shiftjis_upcase(tmpName);
00502     else
00503         upcase(tmpName);
00504 
00505     createShort = RemoveSpaces(tmpName);
00506 
00507     // Get Length of filename
00508     char* pos = strchr(tmpName,'.');
00509     if (pos) {
00510         // ignore preceding '.' if extension is longer than "3"
00511         if (strlen(pos)>4) {
00512             while (*tmpName=='.') tmpName++;
00513             createShort = true;
00514         }
00515         pos = strchr(tmpName,'.');
00516         if (pos)    len = (Bits)(pos - tmpName);
00517         else        len = (Bits)strlen(tmpName);
00518     } else {
00519         len = (Bits)strlen(tmpName);
00520     }
00521 
00522     // Should shortname version be created ?
00523     createShort = createShort || (len>8);
00524     if (!createShort) {
00525         char buffer[CROSS_LEN];
00526         strcpy(buffer,tmpName);
00527         createShort = (GetLongName(curDir,buffer)>=0);
00528     }
00529 
00530     if (createShort) {
00531         // Create number
00532         char buffer[8];
00533         info->shortNr = CreateShortNameID(curDir,tmpName);
00534         sprintf(buffer,"%d",(int)info->shortNr);
00535         // Copy first letters
00536         Bits tocopy = 0;
00537         size_t buflen = strlen(buffer);
00538         if ((size_t)len+buflen+1u>8u) tocopy = (Bits)(8u - buflen - 1u);
00539         else                          tocopy = len;
00540         safe_strncpy(info->shortname,tmpName,tocopy+1);
00541         // Copy number
00542         strcat(info->shortname,"~");
00543         strcat(info->shortname,buffer);
00544         // Add (and cut) Extension, if available
00545         if (pos) {
00546             // Step to last extension...
00547             pos = strrchr(tmpName, '.');
00548             // add extension
00549             strncat(info->shortname,pos,4);
00550             info->shortname[DOS_NAMELENGTH] = 0;
00551         }
00552 
00553         // keep list sorted for CreateShortNameID to work correctly
00554         if (curDir->longNameList.size()>0) {
00555             if (!(strcmp(info->shortname,curDir->longNameList.back()->shortname)<0)) {
00556                 // append at end of list
00557                 curDir->longNameList.push_back(info);
00558             } else {
00559                 // look for position where to insert this element
00560                 bool found=false;
00561                 std::vector<CFileInfo*>::iterator it;
00562                 for (it=curDir->longNameList.begin(); it!=curDir->longNameList.end(); ++it) {
00563                     if (strcmp(info->shortname,(*it)->shortname)<0) {
00564                         found = true;
00565                         break;
00566                     }
00567                 }
00568                 // Put it in longname list...
00569                 if (found) curDir->longNameList.insert(it,info);
00570                 else curDir->longNameList.push_back(info);
00571             }
00572         } else {
00573             // empty file list, append
00574             curDir->longNameList.push_back(info);
00575         }
00576     } else {
00577         strcpy(info->shortname,tmpName);
00578     }
00579     RemoveTrailingDot(info->shortname);
00580 }
00581 
00582 DOS_Drive_Cache::CFileInfo* DOS_Drive_Cache::FindDirInfo(const char* path, char* expandedPath) {
00583     // statics
00584     static char split[2] = { CROSS_FILESPLIT,0 };
00585     
00586     char        dir  [CROSS_LEN]; 
00587     char        work [CROSS_LEN];
00588     const char* start = path;
00589     const char*     pos;
00590     CFileInfo*  curDir = dirBase;
00591     Bit16u      id;
00592 
00593     if (save_dir && (strcmp(path,save_path)==0)) {
00594         strcpy(expandedPath,save_expanded);
00595         return save_dir;
00596     };
00597 
00598 //  LOG_DEBUG("DIR: Find %s",path);
00599 
00600     // Remove base dir path
00601     start += strlen(basePath);
00602     strcpy(expandedPath,basePath);
00603 
00604     // hehe, baseDir should be cached in... 
00605     if (!IsCachedIn(curDir)) {
00606         strcpy(work,basePath);
00607         if (OpenDir(curDir,work,id)) {
00608             char buffer[CROSS_LEN];
00609             char* result = 0;
00610             strcpy(buffer,dirPath);
00611             ReadDir(id,result);
00612             strcpy(dirPath,buffer);
00613             if (dirSearch[id]) {
00614                 dirSearch[id]->id = MAX_OPENDIRS;
00615                 dirSearch[id] = 0;
00616             }
00617         };
00618     };
00619 
00620     do {
00621 // TODO: In PC-98 mode, use a Shift-JIS aware version of strchr() to find the path separator.
00622 //       It's possible for the host path separator to appear in the trailing end of a double-byte character.
00623 //      bool errorcheck = false;
00624         pos = strchr(start,CROSS_FILESPLIT);
00625         if (pos) { safe_strncpy(dir,start,pos-start+1); /*errorcheck = true;*/ }
00626         else     { strcpy(dir,start); };
00627  
00628         // Path found
00629         Bits nextDir = GetLongName(curDir,dir);
00630         strcat(expandedPath,dir);
00631 
00632         // Error check
00633 /*      if ((errorcheck) && (nextDir<0)) {
00634             LOG_DEBUG("DIR: Error: %s not found.",expandedPath);
00635         };
00636 */
00637         // Follow Directory
00638         if ((nextDir>=0) && curDir->fileList[(size_t)nextDir]->isDir) {
00639             curDir = curDir->fileList[(size_t)nextDir];
00640             strcpy (curDir->orgname,dir);
00641             if (!IsCachedIn(curDir)) {
00642                 if (OpenDir(curDir,expandedPath,id)) {
00643                     char buffer[CROSS_LEN];
00644                     char* result = 0;
00645                     strcpy(buffer,dirPath);
00646                     ReadDir(id,result);
00647                     strcpy(dirPath,buffer);
00648                     if (dirSearch[id]) {
00649                         dirSearch[id]->id = MAX_OPENDIRS;
00650                         dirSearch[id] = 0;
00651                     }
00652                 };
00653             }
00654         };
00655         if (pos) {
00656             strcat(expandedPath,split);
00657             start = pos+1;
00658         }
00659     } while (pos);
00660 
00661     // Save last result for faster access next time
00662     strcpy(save_path,path);
00663     strcpy(save_expanded,expandedPath);
00664     save_dir = curDir;
00665 
00666     return curDir;
00667 }
00668 
00669 bool DOS_Drive_Cache::OpenDir(const char* path, Bit16u& id) {
00670     char expand[CROSS_LEN] = {0};
00671     CFileInfo* dir = FindDirInfo(path,expand);
00672     if (OpenDir(dir,expand,id)) {
00673         dirSearch[id]->nextEntry = 0;
00674         return true;
00675     }
00676     return false;
00677 }
00678 
00679 bool DOS_Drive_Cache::OpenDir(CFileInfo* dir, const char* expand, Bit16u& id) {
00680     id = GetFreeID(dir);
00681     dirSearch[id] = dir;
00682     char expandcopy [CROSS_LEN];
00683     strcpy(expandcopy,expand);   
00684     // Add "/"
00685     char end[2]={CROSS_FILESPLIT,0};
00686     if (expandcopy[strlen(expandcopy)-1]!=CROSS_FILESPLIT) strcat(expandcopy,end);
00687     // open dir
00688     if (dirSearch[id]) {
00689         // open dir
00690         void* dirp = drive->opendir(expandcopy);
00691         if (dirp) { 
00692             // Reset it..
00693             drive->closedir(dirp);
00694             strcpy(dirPath,expandcopy);
00695             return true;
00696         }
00697         if (dirSearch[id]) {
00698             dirSearch[id]->id = MAX_OPENDIRS;
00699             dirSearch[id] = 0;
00700         }
00701     };
00702     return false;
00703 }
00704 
00705 void DOS_Drive_Cache::CreateEntry(CFileInfo* dir, const char* name, bool is_directory) {
00706     CFileInfo* info = new CFileInfo;
00707     strcpy(info->orgname, name);                
00708     info->shortNr = 0;
00709     info->isDir = is_directory;
00710 
00711     // Check for long filenames...
00712     CreateShortName(dir, info);     
00713 
00714     bool found = false;
00715 
00716     // keep list sorted (so GetLongName works correctly, used by CreateShortName in this routine)
00717     if (dir->fileList.size()>0) {
00718         if (!(strcmp(info->shortname,dir->fileList.back()->shortname)<0)) {
00719             // append at end of list
00720             dir->fileList.push_back(info);
00721         } else {
00722             // look for position where to insert this element
00723             std::vector<CFileInfo*>::iterator it;
00724             for (it=dir->fileList.begin(); it!=dir->fileList.end(); ++it) {
00725                 if (strcmp(info->shortname,(*it)->shortname)<0) {
00726                     found = true;
00727                     break;
00728                 }
00729             }
00730             // Put file in lists
00731             if (found) dir->fileList.insert(it,info);
00732             else dir->fileList.push_back(info);
00733         }
00734     } else {
00735         // empty file list, append
00736         dir->fileList.push_back(info);
00737     }
00738 }
00739 
00740 void DOS_Drive_Cache::CopyEntry(CFileInfo* dir, CFileInfo* from) {
00741     CFileInfo* info = new CFileInfo;
00742     // just copy things into new fileinfo
00743     strcpy(info->orgname, from->orgname);               
00744     strcpy(info->shortname, from->shortname);               
00745     info->shortNr = from->shortNr;
00746     info->isDir = from->isDir;
00747 
00748     dir->fileList.push_back(info);
00749 }
00750 
00751 bool DOS_Drive_Cache::ReadDir(Bit16u id, char* &result) {
00752     // shouldnt happen...
00753     if (id>MAX_OPENDIRS) return false;
00754 
00755     if (!IsCachedIn(dirSearch[id])) {
00756         // Try to open directory
00757         void* dirp = drive->opendir(dirPath);
00758         if (!dirp) {
00759             if (dirSearch[id]) {
00760                 dirSearch[id]->id = MAX_OPENDIRS;
00761                 dirSearch[id] = 0;
00762             }
00763             return false;
00764         }
00765         // Read complete directory
00766         char dir_name[CROSS_LEN];
00767         bool is_directory;
00768         if (drive->read_directory_first(dirp, dir_name, is_directory)) {
00769             CreateEntry(dirSearch[id], dir_name, is_directory);
00770             while (drive->read_directory_next(dirp, dir_name, is_directory)) {
00771                 CreateEntry(dirSearch[id], dir_name, is_directory);
00772             }
00773         }
00774 
00775         // close dir
00776         drive->closedir(dirp);
00777 
00778         // Info
00779 /*      if (!dirp) {
00780             LOG_DEBUG("DIR: Error Caching in %s",dirPath);          
00781             return false;
00782         } else {    
00783             char buffer[128];
00784             sprintf(buffer,"DIR: Caching in %s (%d Files)",dirPath,dirSearch[srchNr]->fileList.size());
00785             LOG_DEBUG(buffer);
00786         };*/
00787     };
00788     if (SetResult(dirSearch[id], result, dirSearch[id]->nextEntry)) return true;
00789     if (dirSearch[id]) {
00790         dirSearch[id]->id = MAX_OPENDIRS;
00791         dirSearch[id] = 0;
00792     }
00793     return false;
00794 }
00795 
00796 bool DOS_Drive_Cache::SetResult(CFileInfo* dir, char* &result, Bitu entryNr)
00797 {
00798     static char res[CROSS_LEN] = { 0 };
00799 
00800     result = res;
00801     if (entryNr>=dir->fileList.size()) return false;
00802     CFileInfo* info = dir->fileList[entryNr];
00803     // copy filename, short version
00804     strcpy(res,info->shortname);
00805     // Set to next Entry
00806     dir->nextEntry = entryNr+1;
00807     return true;
00808 }
00809 
00810 // FindFirst / FindNext
00811 bool DOS_Drive_Cache::FindFirst(char* path, Bit16u& id) {
00812     Bit16u  dirID;
00813     // Cache directory in 
00814     if (!OpenDir(path,dirID)) return false;
00815 
00816     //Find a free slot.
00817     //If the next one isn't free, move on to the next, if none is free => reset and assume the worst
00818     Bit16u local_findcounter = 0;
00819     while ( local_findcounter < MAX_OPENDIRS ) {
00820         if (dirFindFirst[this->nextFreeFindFirst] == 0) break;
00821         if (++this->nextFreeFindFirst >= MAX_OPENDIRS) this->nextFreeFindFirst = 0; //Wrap around
00822         local_findcounter++;
00823     }
00824 
00825     Bit16u  dirFindFirstID = this->nextFreeFindFirst++;
00826     if (this->nextFreeFindFirst >= MAX_OPENDIRS) this->nextFreeFindFirst = 0; //Increase and wrap around for the next search.
00827 
00828     if (local_findcounter == MAX_OPENDIRS) { //Here is the reset from above.
00829         // no free slot found...
00830         LOG(LOG_MISC,LOG_ERROR)("DIRCACHE: FindFirst/Next: All slots full. Resetting");
00831         // Clear the internal list then.
00832         dirFindFirstID = 0;
00833         this->nextFreeFindFirst = 1; //the next free one after this search
00834         for(Bitu n=0; n<MAX_OPENDIRS;n++) { 
00835             // Clear and reuse slot
00836             DeleteFileInfo(dirFindFirst[n]);
00837             dirFindFirst[n]=0;
00838         }
00839        
00840     }       
00841     dirFindFirst[dirFindFirstID] = new CFileInfo();
00842     dirFindFirst[dirFindFirstID]-> nextEntry    = 0;
00843 
00844     // Copy entries to use with FindNext
00845     for (Bitu i=0; i<dirSearch[dirID]->fileList.size(); i++) {
00846         CopyEntry(dirFindFirst[dirFindFirstID],dirSearch[dirID]->fileList[i]);
00847     }
00848     // Now re-sort the fileList accordingly to output
00849     switch (sortDirType) {
00850         case ALPHABETICAL       : break;
00851 //      case ALPHABETICAL       : std::sort(dirFindFirst[dirFindFirstID]->fileList.begin(), dirFindFirst[dirFindFirstID]->fileList.end(), SortByName);      break;
00852         case DIRALPHABETICAL    : std::sort(dirFindFirst[dirFindFirstID]->fileList.begin(), dirFindFirst[dirFindFirstID]->fileList.end(), SortByDirName);       break;
00853         case ALPHABETICALREV    : std::sort(dirFindFirst[dirFindFirstID]->fileList.begin(), dirFindFirst[dirFindFirstID]->fileList.end(), SortByNameRev);       break;
00854         case DIRALPHABETICALREV : std::sort(dirFindFirst[dirFindFirstID]->fileList.begin(), dirFindFirst[dirFindFirstID]->fileList.end(), SortByDirNameRev);    break;
00855         case NOSORT             : break;
00856     }
00857 
00858 //  LOG(LOG_MISC,LOG_ERROR)("DIRCACHE: FindFirst : %s (ID:%02X)",path,dirFindFirstID);
00859     id = dirFindFirstID;
00860     return true;
00861 }
00862 
00863 bool DOS_Drive_Cache::FindNext(Bit16u id, char* &result) {
00864     // out of range ?
00865     if ((id>=MAX_OPENDIRS) || !dirFindFirst[id]) {
00866         LOG(LOG_MISC,LOG_ERROR)("DIRCACHE: FindFirst/Next failure : ID out of range: %04X",id);
00867         return false;
00868     }
00869     if (!SetResult(dirFindFirst[id], result, dirFindFirst[id]->nextEntry)) {
00870         // free slot
00871         DeleteFileInfo(dirFindFirst[id]); dirFindFirst[id] = 0;
00872         return false;
00873     }
00874     return true;
00875 }
00876 
00877 void DOS_Drive_Cache::ClearFileInfo(CFileInfo *dir) {
00878     for(Bit32u i=0; i<dir->fileList.size(); i++) {
00879         if (CFileInfo *info = dir->fileList[i])
00880             ClearFileInfo(info);
00881     }
00882     if (dir->id != MAX_OPENDIRS) {
00883         dirSearch[dir->id] = 0;
00884         dir->id = MAX_OPENDIRS;
00885     }
00886 }
00887 
00888 void DOS_Drive_Cache::DeleteFileInfo(CFileInfo *dir) {
00889     if (dir)
00890         ClearFileInfo(dir);
00891     delete dir;
00892 }