static void WStrVecTest() { WStrVec v; v.Append(str::Dup(L"foo")); v.Append(str::Dup(L"bar")); WCHAR *s = v.Join(); utassert(v.Count() == 2); utassert(str::Eq(L"foobar", s)); free(s); s = v.Join(L";"); utassert(v.Count() == 2); utassert(str::Eq(L"foo;bar", s)); free(s); v.Append(str::Dup(L"glee")); s = v.Join(L"_ _"); utassert(v.Count() == 3); utassert(str::Eq(L"foo_ _bar_ _glee", s)); free(s); v.Sort(); s = v.Join(); utassert(str::Eq(L"barfooglee", s)); free(s); { WStrVec v2(v); utassert(str::Eq(v2.At(1), L"foo")); v2.Append(str::Dup(L"nobar")); utassert(str::Eq(v2.At(3), L"nobar")); v2 = v; utassert(v2.Count() == 3 && v2.At(0) != v.At(0)); utassert(str::Eq(v2.At(1), L"foo")); utassert(&v2.At(2) == v2.AtPtr(2) && str::Eq(*v2.AtPtr(2), L"glee")); } { WStrVec v2; size_t count = v2.Split(L"a,b,,c,", L","); utassert(count == 5 && v2.Find(L"c") == 3); utassert(v2.Find(L"") == 2 && v2.Find(L"", 3) == 4 && v2.Find(L"", 5) == -1); utassert(v2.Find(L"B") == -1 && v2.FindI(L"B") == 1); ScopedMem<WCHAR> joined(v2.Join(L";")); utassert(str::Eq(joined, L"a;b;;c;")); } { WStrVec v2; size_t count = v2.Split(L"a,b,,c,", L",", true); utassert(count == 3 && v2.Find(L"c") == 2); ScopedMem<WCHAR> joined(v2.Join(L";")); utassert(str::Eq(joined, L"a;b;c")); ScopedMem<WCHAR> last(v2.Pop()); utassert(v2.Count() == 2 && str::Eq(last, L"c")); } }
// Select random files to test. We want to test each file type equally, so // we first group them by file extension and then select up to maxPerType // for each extension, randomly, and inter-leave the files with different // extensions, so their testing is evenly distributed. // Returns result in <files>. static void RandomizeFiles(WStrVec& files, int maxPerType) { WStrVec fileExts; Vec<WStrVec *> filesPerType; for (size_t i = 0; i < files.Count(); i++) { const WCHAR *file = files.At(i); const WCHAR *ext = path::GetExt(file); CrashAlwaysIf(!ext); int typeNo = fileExts.FindI(ext); if (-1 == typeNo) { fileExts.Append(str::Dup(ext)); filesPerType.Append(new WStrVec()); typeNo = (int)filesPerType.Count() - 1; } filesPerType.At(typeNo)->Append(str::Dup(file)); } for (size_t j = 0; j < filesPerType.Count(); j++) { WStrVec *all = filesPerType.At(j); WStrVec *random = new WStrVec(); for (int n = 0; n < maxPerType && all->Count() > 0; n++) { int idx = rand() % all->Count(); WCHAR *file = all->At(idx); random->Append(file); all->RemoveAtFast(idx); } filesPerType.At(j) = random; delete all; } files.Reset(); bool gotAll = false; while (!gotAll) { gotAll = true; for (size_t j = 0; j < filesPerType.Count(); j++) { WStrVec *random = filesPerType.At(j); if (random->Count() > 0) { gotAll = false; WCHAR *file = random->At(0); files.Append(file); random->RemoveAtFast(0); } } } for (size_t j = 0; j < filesPerType.Count(); j++) { delete filesPerType.At(j); } }
FileExistenceChecker() { DisplayState *state; for (size_t i = 0; i < 2 * FILE_HISTORY_MAX_RECENT && (state = gFileHistory.Get(i)) != NULL; i++) { if (!state->isMissing) paths.Append(str::Dup(state->filePath)); } // add missing paths from the list of most frequently opened documents Vec<DisplayState *> frequencyList; gFileHistory.GetFrequencyOrder(frequencyList); for (size_t i = 0; i < 2 * FILE_HISTORY_MAX_FREQUENT && i < frequencyList.Count(); i++) { state = frequencyList.At(i); if (!paths.Contains(state->filePath)) paths.Append(str::Dup(state->filePath)); } }
// extract ComicBookInfo metadata // cf. http://code.google.com/p/comicbookinfo/ bool CbxEngineImpl::Visit(const char *path, const char *value, json::DataType type) { if (json::Type_String == type && str::Eq(path, "/ComicBookInfo/1.0/title")) propTitle.Set(str::conv::FromUtf8(value)); else if (json::Type_Number == type && str::Eq(path, "/ComicBookInfo/1.0/publicationYear")) propDate.Set(str::Format(L"%s/%d", propDate ? propDate : L"", atoi(value))); else if (json::Type_Number == type && str::Eq(path, "/ComicBookInfo/1.0/publicationMonth")) propDate.Set(str::Format(L"%d%s", atoi(value), propDate ? propDate : L"")); else if (json::Type_String == type && str::Eq(path, "/appID")) propCreator.Set(str::conv::FromUtf8(value)); else if (json::Type_String == type && str::Eq(path, "/lastModified")) propModDate.Set(str::conv::FromUtf8(value)); else if (json::Type_String == type && str::Eq(path, "/X-summary")) propSummary.Set(str::conv::FromUtf8(value)); else if (str::StartsWith(path, "/ComicBookInfo/1.0/credits[")) { int idx = -1; const char *prop = str::Parse(path, "/ComicBookInfo/1.0/credits[%d]/", &idx); if (prop) { if (json::Type_String == type && str::Eq(prop, "person")) propAuthorTmp.Set(str::conv::FromUtf8(value)); else if (json::Type_Bool == type && str::Eq(prop, "primary") && propAuthorTmp && propAuthors.Find(propAuthorTmp) == -1) { propAuthors.Append(propAuthorTmp.StealData()); } } return true; } // stop parsing once we have all desired information return !propTitle || propAuthors.Count() == 0 || !propCreator || !propDate || str::FindChar(propDate, '/') <= propDate; }
bool ImageDirEngineImpl::LoadImageDir(const WCHAR *dirName) { fileName = str::Dup(dirName); fileExt = L""; ScopedMem<WCHAR> pattern(path::Join(dirName, L"*")); WIN32_FIND_DATA fdata; HANDLE hfind = FindFirstFile(pattern, &fdata); if (INVALID_HANDLE_VALUE == hfind) return false; do { if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { if (ImageEngine::IsSupportedFile(fdata.cFileName)) pageFileNames.Append(path::Join(dirName, fdata.cFileName)); } } while (FindNextFile(hfind, &fdata)); FindClose(hfind); if (pageFileNames.Count() == 0) return false; pageFileNames.SortNatural(); pages.AppendBlanks(pageFileNames.Count()); mediaboxes.AppendBlanks(pageFileNames.Count()); return true; }
/* The html looks like: <li> <object type="text/sitemap"> <param name="Keyword" value="- operator"> <param name="Name" value="Subtraction Operator (-)"> <param name="Local" value="html/vsoprsubtract.htm"> <param name="Name" value="Subtraction Operator (-)"> <param name="Local" value="html/js56jsoprsubtract.htm"> </object> <ul> ... optional children ... </ul> <li> ... siblings ... */ static bool VisitChmIndexItem(EbookTocVisitor *visitor, HtmlElement *el, UINT cp, int level) { CrashIf(el->tag != Tag_Object || level > 1 && (!el->up || el->up->tag != Tag_Li)); WStrVec references; ScopedMem<WCHAR> keyword, name; for (el = el->GetChildByTag(Tag_Param); el; el = el->next) { if (Tag_Param != el->tag) continue; ScopedMem<WCHAR> attrName(el->GetAttribute("name")); ScopedMem<WCHAR> attrVal(el->GetAttribute("value")); if (attrName && attrVal && cp != CP_CHM_DEFAULT) { ScopedMem<char> bytes(str::conv::ToCodePage(attrVal, CP_CHM_DEFAULT)); attrVal.Set(str::conv::FromCodePage(bytes, cp)); } if (!attrName || !attrVal) /* ignore incomplete/unneeded <param> */; else if (str::EqI(attrName, L"Keyword")) keyword.Set(attrVal.StealData()); else if (str::EqI(attrName, L"Name")) { name.Set(attrVal.StealData()); // some CHM documents seem to use a lonely Name instead of Keyword if (!keyword) keyword.Set(str::Dup(name)); } else if (str::EqI(attrName, L"Local") && name) { // remove the ITS protocol and any filename references from the URLs if (str::Find(attrVal, L"::/")) attrVal.Set(str::Dup(str::Find(attrVal, L"::/") + 3)); references.Append(name.StealData()); references.Append(attrVal.StealData()); } } if (!keyword) return false; if (references.Count() == 2) { visitor->Visit(keyword, references.At(1), level); return true; } visitor->Visit(keyword, NULL, level); for (size_t i = 0; i < references.Count(); i += 2) { visitor->Visit(references.At(i), references.At(i + 1), level + 1); } return true; }
static void CollectFilesToBench(WCHAR* dir, WStrVec& files) { DirIter di(dir, true /* recursive */); for (const WCHAR* filePath = di.First(); filePath; filePath = di.Next()) { if (IsFileToBench(filePath)) { files.Append(str::Dup(filePath)); } } }
FilesProvider(WStrVec& newFiles, int n, int offset) { // get every n-th file starting at offset for (size_t i = offset; i < newFiles.Count(); i += n) { const WCHAR *f = newFiles.At(i); files.Append(str::Dup(f)); } provided = 0; }
static void CALLBACK ReadDirectoryChangesNotification(DWORD errCode, DWORD bytesTransfered, LPOVERLAPPED overlapped) { ScopedCritSec cs(&g_threadCritSec); OverlappedEx *over = (OverlappedEx*)overlapped; WatchedDir* wd = (WatchedDir*)over->data; lf(L"ReadDirectoryChangesNotification() dir: %s, numBytes: %d", wd->dirPath, (int)bytesTransfered); CrashIf(wd != wd->overlapped.data); if (errCode == ERROR_OPERATION_ABORTED) { lf(" ERROR_OPERATION_ABORTED"); DeleteWatchedDir(wd); InterlockedDecrement(&gRemovalsPending); return; } // This might mean overflow? Not sure. if (!bytesTransfered) return; FILE_NOTIFY_INFORMATION *notify = (FILE_NOTIFY_INFORMATION*)wd->buf; // collect files that changed, removing duplicates WStrVec changedFiles; for (;;) { ScopedMem<WCHAR> fileName(str::DupN(notify->FileName, notify->FileNameLength / sizeof(WCHAR))); // files can get updated either by writing to them directly or // by writing to a .tmp file first and then moving that file in place // (the latter only yields a RENAMED action with the expected file name) if (notify->Action == FILE_ACTION_MODIFIED || notify->Action == FILE_ACTION_RENAMED_NEW_NAME) { if (!changedFiles.Contains(fileName)) { lf(L"ReadDirectoryChangesNotification() FILE_ACTION_MODIFIED, for '%s'", fileName); changedFiles.Append(fileName.StealData()); } else { lf(L"ReadDirectoryChangesNotification() eliminating duplicate notification for '%s'", fileName); } } else { lf(L"ReadDirectoryChangesNotification() action=%d, for '%s'", (int)notify->Action, fileName); } // step to the next entry if there is one DWORD nextOff = notify->NextEntryOffset; if (!nextOff) break; notify = (FILE_NOTIFY_INFORMATION *)((char*)notify + nextOff); } StartMonitoringDirForChanges(wd); for (const WCHAR *f : changedFiles) { NotifyAboutFile(wd, f); } }
static bool CollectStressTestSupportedFilesFromDirectory(const WCHAR* dirPath, const WCHAR* filter, WStrVec& paths) { bool hasFiles = false; DirIter di(dirPath); for (const WCHAR* filePath = di.First(); filePath; filePath = di.Next()) { if (IsStressTestSupportedFile(filePath, filter)) { paths.Append(str::Dup(filePath)); hasFiles = true; } } return hasFiles; }
static void CALLBACK ReadDirectoryChangesNotification(DWORD errCode, DWORD bytesTransfered, LPOVERLAPPED overlapped) { ScopedCritSec cs(&g_threadCritSec); OverlappedEx *over = (OverlappedEx*)overlapped; WatchedDir* wd = (WatchedDir*)over->data; lf(L"ReadDirectoryChangesNotification() dir: %s, numBytes: %d", wd->dirPath, (int)bytesTransfered); CrashIf(wd != wd->overlapped.data); if (errCode == ERROR_OPERATION_ABORTED) { lf(" ERROR_OPERATION_ABORTED"); DeleteWatchedDir(wd); return; } // This might mean overflow? Not sure. if (!bytesTransfered) return; FILE_NOTIFY_INFORMATION *notify = (FILE_NOTIFY_INFORMATION*)wd->buf; // collect files that changed, removing duplicates WStrVec changedFiles; for (;;) { WCHAR *fileName = str::DupN(notify->FileName, notify->FileNameLength / sizeof(WCHAR)); if (notify->Action == FILE_ACTION_MODIFIED) { if (!changedFiles.Contains(fileName)) { lf(L"ReadDirectoryChangesNotification() FILE_ACTION_MODIFIED, for '%s'", fileName); changedFiles.Append(fileName); fileName = NULL; } else { lf(L"ReadDirectoryChangesNotification() eliminating duplicate notification for '%s'", fileName); } } else { lf(L"ReadDirectoryChangesNotification() action=%d, for '%s'", (int)notify->Action, fileName); } free(fileName); // step to the next entry if there is one DWORD nextOff = notify->NextEntryOffset; if (!nextOff) break; notify = (FILE_NOTIFY_INFORMATION *)((char*)notify + nextOff); } StartMonitoringDirForChanges(wd); for (WCHAR **f = changedFiles.IterStart(); f; f = changedFiles.IterNext()) { NotifyAboutFile(wd, *f); } }
// Parses a command line according to the specification at // http://msdn.microsoft.com/en-us/library/17w5ykft.aspx : // * arguments are delimited by spaces or tabs // * whitespace in between two quotation marks are part of a single argument // * a single backslash in front of a quotation mark prevents this special treatment // * an even number of backslashes followed by either a backslash and a quotation // mark or just a quotation mark is collapsed into half as many backslashes void ParseCmdLine(const WCHAR *cmdLine, WStrVec& out, int maxParts) { if (!cmdLine) return; str::Str<WCHAR> arg(MAX_PATH / 2); const WCHAR *s; while (--maxParts != 0) { while (str::IsWs(*cmdLine)) cmdLine++; if (!*cmdLine) break; bool insideQuotes = false; for (; *cmdLine; cmdLine++) { if ('"' == *cmdLine) { insideQuotes = !insideQuotes; continue; } if (!insideQuotes && str::IsWs(*cmdLine)) { break; } if ('\\' == *cmdLine) { for (s = cmdLine + 1; '\\' == *s; s++); // backslashes escape only when followed by a quotation mark if ('"' == *s) cmdLine++; } arg.Append(*cmdLine); } out.Append(arg.StealData()); } if (*cmdLine) { while (str::IsWs(*cmdLine)) cmdLine++; if (*cmdLine) out.Append(str::Dup(cmdLine)); } }
// return names of processes that are running part of the installation // (i.e. have libmupdf.dll or npPdfViewer.dll loaded) static void ProcessesUsingInstallation(WStrVec& names) { ScopedHandle snap(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); if (INVALID_HANDLE_VALUE == snap) return; PROCESSENTRY32 proc = {0}; proc.dwSize = sizeof(proc); BOOL ok = Process32First(snap, &proc); while (ok) { if (IsUsingInstallation(proc.th32ProcessID)) { names.Append(str::Dup(proc.szExeFile)); } proc.dwSize = sizeof(proc); ok = Process32Next(snap, &proc); } }
static size_t GetAllMatchingFiles(const WCHAR* dir, const WCHAR* filter, WStrVec& files, bool showProgress) { WStrVec dirsToVisit; dirsToVisit.Append(str::Dup(dir)); while (dirsToVisit.size() > 0) { if (showProgress) { wprintf(L"."); fflush(stdout); } AutoFreeW path(dirsToVisit.PopAt(0)); CollectStressTestSupportedFilesFromDirectory(path, filter, files); AutoFreeW pattern(str::Format(L"%s\\*", path)); CollectPathsFromDirectory(pattern, dirsToVisit, true); } return files.size(); }
bool CollectPathsFromDirectory(const WCHAR *pattern, WStrVec& paths, bool dirsInsteadOfFiles) { ScopedMem<WCHAR> dirPath(path::GetDir(pattern)); WIN32_FIND_DATA fdata; HANDLE hfind = FindFirstFile(pattern, &fdata); if (INVALID_HANDLE_VALUE == hfind) return false; do { bool append = !dirsInsteadOfFiles; if ((fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) append = dirsInsteadOfFiles && !IsSpecialDir(fdata.cFileName); if (append) paths.Append(path::Join(dirPath, fdata.cFileName)); } while (FindNextFile(hfind, &fdata)); FindClose(hfind); return paths.Count() > 0; }
// removes thumbnails that don't belong to any frequently used item in file history void CleanUpThumbnailCache(FileHistory& fileHistory) { ScopedMem<WCHAR> thumbsPath(AppGenDataFilename(THUMBNAILS_DIR_NAME)); if (!thumbsPath) return; ScopedMem<WCHAR> pattern(path::Join(thumbsPath, L"*.png")); WStrVec files; WIN32_FIND_DATA fdata; HANDLE hfind = FindFirstFile(pattern, &fdata); if (INVALID_HANDLE_VALUE == hfind) return; do { if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) files.Append(str::Dup(fdata.cFileName)); } while (FindNextFile(hfind, &fdata)); FindClose(hfind); Vec<DisplayState *> list; fileHistory.GetFrequencyOrder(list); for (size_t i = 0; i < list.Count() && i < FILE_HISTORY_MAX_FREQUENT * 2; i++) { ScopedMem<WCHAR> bmpPath(GetThumbnailPath(list.At(i)->filePath)); if (!bmpPath) continue; int idx = files.Find(path::GetBaseName(bmpPath)); if (idx != -1) { CrashIf(idx < 0 || files.Count() <= (size_t)idx); WCHAR *fileName = files.At(idx); files.RemoveAt(idx); free(fileName); } } for (size_t i = 0; i < files.Count(); i++) { ScopedMem<WCHAR> bmpPath(path::Join(thumbsPath, files.At(i))); file::Delete(bmpPath); } }
// see http://itexmac.sourceforge.net/pdfsync.html for the specification int Pdfsync::RebuildIndex() { size_t len; ScopedMem<char> data(file::ReadAll(syncfilepath, &len)); if (!data) return PDFSYNCERR_SYNCFILE_CANNOT_BE_OPENED; // convert the file data into a list of zero-terminated strings str::TransChars(data, "\r\n", "\0\0"); // parse preamble (jobname and version marker) char *line = data; char *dataEnd = data + len; // replace star by spaces (TeX uses stars instead of spaces in filenames) str::TransChars(line, "*/", " \\"); ScopedMem<WCHAR> jobName(str::conv::FromAnsi(line)); jobName.Set(str::Join(jobName, L".tex")); jobName.Set(PrependDir(jobName)); line = Advance0Line(line, dataEnd); UINT versionNumber = 0; if (!line || !str::Parse(line, "version %u", &versionNumber) || versionNumber != 1) return PDFSYNCERR_SYNCFILE_CANNOT_BE_OPENED; // reset synchronizer database srcfiles.Reset(); lines.Reset(); points.Reset(); fileIndex.Reset(); sheetIndex.Reset(); Vec<size_t> filestack; UINT page = 1; sheetIndex.Append(0); // add the initial tex file to the source file stack filestack.Push(srcfiles.Count()); srcfiles.Append(jobName.StealData()); PdfsyncFileIndex findex = { 0 }; fileIndex.Append(findex); PdfsyncLine psline; PdfsyncPoint pspoint; // parse data UINT maxPageNo = engine->PageCount(); while ((line = Advance0Line(line, dataEnd)) != NULL) { if (!line) break; switch (*line) { case 'l': psline.file = filestack.Last(); if (str::Parse(line, "l %u %u %u", &psline.record, &psline.line, &psline.column)) lines.Append(psline); else if (str::Parse(line, "l %u %u", &psline.record, &psline.line)) { psline.column = 0; lines.Append(psline); } // else dbg("Bad 'l' line in the pdfsync file"); break; case 's': if (str::Parse(line, "s %u", &page)) sheetIndex.Append(points.Count()); // else dbg("Bad 's' line in the pdfsync file"); // if (0 == page || page > maxPageNo) // dbg("'s' line with invalid page number in the pdfsync file"); break; case 'p': pspoint.page = page; if (0 == page || page > maxPageNo) /* ignore point for invalid page number */; else if (str::Parse(line, "p %u %u %u", &pspoint.record, &pspoint.x, &pspoint.y)) points.Append(pspoint); else if (str::Parse(line, "p* %u %u %u", &pspoint.record, &pspoint.x, &pspoint.y)) points.Append(pspoint); // else dbg("Bad 'p' line in the pdfsync file"); break; case '(': { ScopedMem<WCHAR> filename(str::conv::FromAnsi(line + 1)); // if the filename contains quotes then remove them // TODO: this should never happen!? if (filename[0] == '"' && filename[str::Len(filename) - 1] == '"') filename.Set(str::DupN(filename + 1, str::Len(filename) - 2)); // undecorate the filepath: replace * by space and / by \ str::TransChars(filename, L"*/", L" \\"); // if the file name extension is not specified then add the suffix '.tex' if (str::IsEmpty(path::GetExt(filename))) filename.Set(str::Join(filename, L".tex")); // ensure that the path is absolute if (PathIsRelative(filename)) filename.Set(PrependDir(filename)); filestack.Push(srcfiles.Count()); srcfiles.Append(filename.StealData()); findex.start = findex.end = lines.Count(); fileIndex.Append(findex); } break; case ')': if (filestack.Count() > 1) fileIndex.At(filestack.Pop()).end = lines.Count(); // else dbg("Unbalanced ')' line in the pdfsync file"); break; default: // dbg("Ignoring invalid pdfsync line starting with '%c'", *line); break; } } fileIndex.At(0).end = lines.Count(); assert(filestack.Count() == 1); return Synchronizer::RebuildIndex(); }
static WCHAR *GetGhostscriptPath() { WCHAR *gsProducts[] = { L"AFPL Ghostscript", L"Aladdin Ghostscript", L"GPL Ghostscript", L"GNU Ghostscript", }; // find all installed Ghostscript versions WStrVec versions; REGSAM access = KEY_READ | KEY_WOW64_32KEY; TryAgain64Bit: for (int i = 0; i < dimof(gsProducts); i++) { HKEY hkey; ScopedMem<WCHAR> keyName(str::Join(L"Software\\", gsProducts[i])); if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, access, &hkey) != ERROR_SUCCESS) continue; WCHAR subkey[32]; for (DWORD ix = 0; RegEnumKey(hkey, ix, subkey, dimof(subkey)) == ERROR_SUCCESS; ix++) versions.Append(str::Dup(subkey)); RegCloseKey(hkey); } if ((access & KEY_WOW64_32KEY)) { // also look for 64-bit Ghostscript versions under 64-bit Windows access = KEY_READ | KEY_WOW64_64KEY; #ifndef _WIN64 // (unless this is 32-bit Windows) if (IsRunningInWow64()) #endif goto TryAgain64Bit; } versions.SortNatural(); // return the path to the newest installation for (size_t ix = versions.Count(); ix > 0; ix--) { for (int i = 0; i < dimof(gsProducts); i++) { ScopedMem<WCHAR> keyName(str::Format(L"Software\\%s\\%s", gsProducts[i], versions.At(ix - 1))); ScopedMem<WCHAR> GS_DLL(ReadRegStr(HKEY_LOCAL_MACHINE, keyName, L"GS_DLL")); if (!GS_DLL) continue; ScopedMem<WCHAR> dir(path::GetDir(GS_DLL)); ScopedMem<WCHAR> exe(path::Join(dir, L"gswin32c.exe")); if (file::Exists(exe)) return exe.StealData(); exe.Set(path::Join(dir, L"gswin64c.exe")); if (file::Exists(exe)) return exe.StealData(); } } // if Ghostscript isn't found in the Registry, try finding it in the %PATH% DWORD size = GetEnvironmentVariable(L"PATH", NULL, 0); ScopedMem<WCHAR> envpath(AllocArray<WCHAR>(size)); if (size > 0 && envpath) { GetEnvironmentVariable(L"PATH", envpath, size); WStrVec paths; paths.Split(envpath, L";", true); for (size_t ix = 0; ix < paths.Count(); ix++) { ScopedMem<WCHAR> exe(path::Join(paths.At(ix), L"gswin32c.exe")); if (file::Exists(exe)) return exe.StealData(); exe.Set(path::Join(paths.At(ix), L"gswin64c.exe")); if (file::Exists(exe)) return exe.StealData(); } } return NULL; }
explicit FilesProvider(const WCHAR *path) { files.Append(str::Dup(path)); provided = 0; }