summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/m_utils.h25
-rw-r--r--libs/win32/mir_core.libbin480964 -> 486362 bytes
-rw-r--r--libs/win64/mir_core.libbin486162 -> 491708 bytes
-rw-r--r--src/mir_app/src/database.cpp19
-rw-r--r--src/mir_app/src/profilemanager.cpp213
-rw-r--r--src/mir_app/src/profilemanager.h17
-rw-r--r--src/mir_core/mir_core.vcxproj3
-rw-r--r--src/mir_core/mir_core.vcxproj.filters3
-rw-r--r--src/mir_core/src/Windows/fileutil.cpp78
-rw-r--r--src/mir_core/src/mir_core.def18
-rw-r--r--src/mir_core/src/mir_core64.def18
11 files changed, 247 insertions, 147 deletions
diff --git a/include/m_utils.h b/include/m_utils.h
index f7b505815e..fc9ede2ecd 100644
--- a/include/m_utils.h
+++ b/include/m_utils.h
@@ -251,36 +251,37 @@ class MIR_CORE_EXPORT MFilePath : public CMStringW
{
class MIR_CORE_EXPORT MFileIterator
{
+ #ifdef _WINDOWS
+ WIN32_FIND_DATAW m_data;
+ HANDLE m_hFind = INVALID_HANDLE_VALUE;
+ #endif
+
class iterator
{
- const MFileIterator &ptr;
- int code = 0;
+ MFileIterator *ptr;
public:
- __inline iterator(const MFileIterator &_p, int code) : ptr(_p) {}
+ __inline iterator(MFileIterator *_p) : ptr(_p) {}
iterator operator++();
- __inline bool operator!=(const iterator &p) { return p.code == code; }
- __inline operator const MFileIterator*() const { return &ptr; }
+ __inline bool operator!=(const iterator &p) { return p.ptr != ptr; }
+ __inline operator const MFileIterator*() const { return ptr; }
};
public:
MFileIterator(const wchar_t*);
~MFileIterator();
- wchar_t m_path[MAX_PATH];
+ __inline const wchar_t* getPath() const { return m_data.cFileName; }
+
bool isDir() const;
- __inline iterator begin() const;
- __inline iterator end() const { return iterator(*this, 0); }
+ __inline iterator begin();
+ __inline iterator end() { return iterator(nullptr); }
};
public:
MFilePath(): CMStringW() {}
MFilePath(const wchar_t *init) : CMStringW(init) {}
- MFilePath& operator=(const wchar_t *pszSrc) {
- *this = pszSrc;
- return *this;
- }
bool isExist() const;
bool move(const wchar_t *pwszDest);
diff --git a/libs/win32/mir_core.lib b/libs/win32/mir_core.lib
index d93c1fdea8..971464567e 100644
--- a/libs/win32/mir_core.lib
+++ b/libs/win32/mir_core.lib
Binary files differ
diff --git a/libs/win64/mir_core.lib b/libs/win64/mir_core.lib
index 9f95adc332..0419079867 100644
--- a/libs/win64/mir_core.lib
+++ b/libs/win64/mir_core.lib
Binary files differ
diff --git a/src/mir_app/src/database.cpp b/src/mir_app/src/database.cpp
index 275e72b65e..d20361c3b4 100644
--- a/src/mir_app/src/database.cpp
+++ b/src/mir_app/src/database.cpp
@@ -184,7 +184,7 @@ static void moveProfileDirProfiles(const wchar_t *profiledir, bool isRootDir)
{
MFilePath pfd, path, path2;
if (isRootDir)
- pfd = VARSW(L"%miranda_path%\\*.dat");
+ pfd = VARSW(L"%miranda_path%\\*.dat").get();
else
pfd.Format(L"%s\\*.dat", profiledir);
@@ -193,7 +193,7 @@ static void moveProfileDirProfiles(const wchar_t *profiledir, bool isRootDir)
if (idx != -1)
pfd.Trim(idx);
- auto *wszFileName = NEWWSTR_ALLOCA(it.m_path);
+ auto *wszFileName = NEWWSTR_ALLOCA(it.getPath());
auto *c = wcsrchr(wszFileName, '.'); if (c) *c = 0;
path.Format(L"%s\\%s", pfd.c_str(), wszFileName);
@@ -205,14 +205,14 @@ static void moveProfileDirProfiles(const wchar_t *profiledir, bool isRootDir)
wchar_t buf[512];
mir_snwprintf(buf,
TranslateT("Miranda is trying to upgrade your profile structure.\nIt cannot move profile %s to the new location %s\nBecause profile with this name already exists. Please resolve the issue manually."),
- path, path2);
+ path.c_str(), path2.c_str());
MessageBoxW(nullptr, buf, L"Miranda NG", MB_ICONERROR | MB_OK);
}
else if (!path.move(path2)) {
wchar_t buf[512];
mir_snwprintf(buf,
TranslateT("Miranda is trying to upgrade your profile structure.\nIt cannot move profile %s to the new location %s automatically\nMost likely this is due to insufficient privileges. Please move profile manually."),
- path, path2);
+ path.c_str(), path2.c_str());
MessageBoxW(nullptr, buf, L"Miranda NG", MB_ICONERROR | MB_OK);
break;
}
@@ -220,7 +220,7 @@ static void moveProfileDirProfiles(const wchar_t *profiledir, bool isRootDir)
}
// returns 1 if a single profile (full path) is found within the profile dir
-static int getProfile1(MFilePath &szProfile, wchar_t *profiledir, BOOL * noProfiles)
+static int getProfile1(MFilePath &szProfile, wchar_t *profiledir, bool *noProfiles)
{
int found = 0;
@@ -241,11 +241,11 @@ static int getProfile1(MFilePath &szProfile, wchar_t *profiledir, BOOL * noProfi
for (auto &it: searchspec.search()) {
// make sure the first hit is actually a *.dat file
- if (!it.isDir())
+ if (!it.isDir() || !wcscmp(it.getPath(), L".") || !wcscmp(it.getPath(), L".."))
continue;
MFilePath newProfile;
- newProfile.Format(L"%s\\%s\\%s.dat", profiledir, it.m_path, it.m_path);
+ newProfile.Format(L"%s\\%s\\%s.dat", profiledir, it.getPath(), it.getPath());
if (!newProfile.isExist())
continue;
@@ -308,14 +308,13 @@ static int getProfile(MFilePath &szProfile)
return 0;
}
- PROFILEMANAGERDATA pd = {};
+ PROFILEMANAGERDATA pd(szProfile);
if (CmdLine_GetOption(L"ForceShowPM")) {
LBL_Show:
- pd.ptszProfile = szProfile.GetBuffer();
- pd.ptszProfileDir = g_profileDir;
if (!getProfileManager(&pd))
return 0;
+ szProfile = pd.m_profile;
return 1;
}
diff --git a/src/mir_app/src/profilemanager.cpp b/src/mir_app/src/profilemanager.cpp
index 636201761a..47759dee29 100644
--- a/src/mir_app/src/profilemanager.cpp
+++ b/src/mir_app/src/profilemanager.cpp
@@ -33,37 +33,94 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#define WM_INPUTCHANGED (WM_USER + 0x3000)
#define WM_FOCUSTEXTBOX (WM_USER + 0x3001)
-typedef BOOL (__cdecl *ENUMPROFILECALLBACK)(wchar_t *tszFullPath, wchar_t *profile, LPARAM lParam);
-
/////////////////////////////////////////////////////////////////////////////////////////
// Profile creator
-static int findProfiles(wchar_t *szProfileDir, ENUMPROFILECALLBACK callback, LPARAM lParam)
+static BOOL EnumProfilesForList(const wchar_t *tszFullPath, wchar_t *profile, CCtrlListView &list, const wchar_t *szProfile)
+{
+ wchar_t sizeBuf[64];
+ bool bFileLocked;
+
+ wchar_t *p = wcsrchr(profile, '.');
+ mir_wstrcpy(sizeBuf, L"0 KB");
+ if (p != nullptr) *p = 0;
+
+ LVITEM item = { 0 };
+ item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
+ item.pszText = profile;
+ item.iItem = 0;
+
+ struct _stat statbuf;
+ if (_wstat(tszFullPath, &statbuf) == 0) {
+ if (statbuf.st_size > 1000000) {
+ mir_snwprintf(sizeBuf, L"%.3lf", (double)statbuf.st_size / 1048576.0);
+ mir_wstrcpy(sizeBuf + 5, L" MB");
+ }
+ else {
+ mir_snwprintf(sizeBuf, L"%.3lf", (double)statbuf.st_size / 1024.0);
+ mir_wstrcpy(sizeBuf + 5, L" KB");
+ }
+ bFileLocked = Profile_CheckOpened(tszFullPath);
+ }
+ else bFileLocked = true;
+
+ DATABASELINK *dblink;
+ switch (touchDatabase(tszFullPath, &dblink)) {
+ case ERROR_SUCCESS:
+ item.iImage = (bFileLocked) ? 1 : 0;
+ break;
+
+ case EGROKPRF_OBSOLETE:
+ item.iImage = 2;
+ break;
+
+ case EGROKPRF_CANTREAD:
+ item.iImage = (bFileLocked) ? 1 : 3;
+ break;
+
+ default:
+ item.iImage = 3;
+ }
+
+ item.lParam = (LPARAM)dblink;
+
+ int iItem = list.InsertItem(&item);
+ if (mir_wstrcmpi(szProfile, tszFullPath) == 0)
+ list.SetItemState(iItem, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
+
+ list.SetItemText(iItem, 2, sizeBuf);
+
+ if (dblink != nullptr)
+ list.SetItemText(iItem, 1, TranslateW(dblink->szFullName));
+ else if (bFileLocked) // file locked
+ list.SetItemText(iItem, 1, TranslateT("<In use>"));
+ else
+ list.SetItemText(iItem, 1, TranslateT("<Unknown format>"));
+
+ return TRUE;
+}
+
+static int findProfiles(CCtrlListView &list, const wchar_t *szProfile)
{
// find in Miranda NG profile subfolders
- wchar_t searchspec[MAX_PATH];
- mir_snwprintf(searchspec, L"%s\\*.*", szProfileDir);
+ MFilePath searchspec;
+ searchspec.Format(L"%s\\*.*", g_profileDir);
- WIN32_FIND_DATA ffd;
- HANDLE hFind = FindFirstFile(searchspec, &ffd);
- if (hFind == INVALID_HANDLE_VALUE)
- return 0;
-
- do {
+ for (auto &it: searchspec.search()) {
// find all subfolders except "." and ".."
- if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && mir_wstrcmp(ffd.cFileName, L".") && mir_wstrcmp(ffd.cFileName, L"..")) {
- wchar_t buf[MAX_PATH], profile[MAX_PATH];
- mir_snwprintf(buf, L"%s\\%s\\%s.dat", szProfileDir, ffd.cFileName, ffd.cFileName);
- if (_waccess(buf, 0) == 0) {
- mir_snwprintf(profile, L"%s.dat", ffd.cFileName);
- if (!callback(buf, profile, lParam))
- break;
- }
+ if (!it.isDir() || !wcscmp(it.getPath(), L".") || !wcscmp(it.getPath(), L".."))
+ continue;
+
+ MFilePath fullPath;
+ fullPath.Format(L"%s\\%s\\%s.dat", g_profileDir, it.getPath(), it.getPath());
+ if (fullPath.isExist()) {
+ wchar_t profileName[MAX_PATH];
+ mir_snwprintf(profileName, L"%s.dat", it.getPath());
+ if (!EnumProfilesForList(fullPath, profileName, list, szProfile))
+ break;
}
}
- while (FindNextFile(hFind, &ffd));
- FindClose(hFind);
return 1;
}
@@ -158,10 +215,10 @@ public:
mir_subclassWindow(m_profileName.GetHwnd(), ProfileNameValidate);
// decide if there is a default profile name given in the INI and if it should be used
- if (m_pd->noProfiles || (shouldAutoCreate(m_pd->ptszProfile) && _waccess(m_pd->ptszProfile, 0))) {
- wchar_t *profile = wcsrchr(m_pd->ptszProfile, '\\');
+ if (m_pd->noProfiles || (shouldAutoCreate(m_pd->m_profile) && !m_pd->m_profile.isExist())) {
+ wchar_t *profile = wcsrchr(m_pd->m_profile.GetBuffer(), '\\');
if (profile) ++profile;
- else profile = m_pd->ptszProfile;
+ else profile = m_pd->m_profile.GetBuffer();
wchar_t *p = wcsrchr(profile, '.');
wchar_t c = 0;
@@ -211,11 +268,11 @@ public:
return false;
// profile placed in "profile_name" subfolder
- mir_snwprintf(m_pd->ptszProfile, MAX_PATH, L"%s\\%s\\%s.dat", m_pd->ptszProfileDir, szName.get(), szName.get());
+ m_pd->m_profile.Format(L"%s\\%s\\%s.dat", g_profileDir, szName.get(), szName.get());
m_pd->newProfile = 1;
m_pd->dblink = (DATABASELINK *)m_driverList.GetItemData(curSel);
- if (CreateProfile(m_pd->ptszProfile, m_pd->dblink) == 0)
+ if (CreateProfile(m_pd->m_profile, m_pd->dblink) == 0)
SetWindowLongPtr(m_hwnd, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE);
else
m_pd->bRun = true;
@@ -240,91 +297,13 @@ class CChooseProfileDlg : public CDlgBase
PROFILEMANAGERDATA *m_pd;
HANDLE m_hFileNotify;
- struct ProfileEnumData
- {
- ProfileEnumData(CCtrlListView &_list, wchar_t *_profile) :
- list(_list),
- szProfile(_profile)
- {}
-
- CCtrlListView &list;
- wchar_t* szProfile;
- };
-
- static BOOL EnumProfilesForList(wchar_t *tszFullPath, wchar_t *profile, LPARAM lParam)
- {
- ProfileEnumData *ped = (ProfileEnumData*)lParam;
- CCtrlListView &list = ped->list;
-
- wchar_t sizeBuf[64];
- bool bFileLocked;
-
- wchar_t *p = wcsrchr(profile, '.');
- mir_wstrcpy(sizeBuf, L"0 KB");
- if (p != nullptr) *p = 0;
-
- LVITEM item = { 0 };
- item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
- item.pszText = profile;
- item.iItem = 0;
-
- struct _stat statbuf;
- if (_wstat(tszFullPath, &statbuf) == 0) {
- if (statbuf.st_size > 1000000) {
- mir_snwprintf(sizeBuf, L"%.3lf", (double)statbuf.st_size / 1048576.0);
- mir_wstrcpy(sizeBuf + 5, L" MB");
- }
- else {
- mir_snwprintf(sizeBuf, L"%.3lf", (double)statbuf.st_size / 1024.0);
- mir_wstrcpy(sizeBuf + 5, L" KB");
- }
- bFileLocked = Profile_CheckOpened(tszFullPath);
- }
- else bFileLocked = true;
-
- DATABASELINK *dblink;
- switch (touchDatabase(tszFullPath, &dblink)) {
- case ERROR_SUCCESS:
- item.iImage = (bFileLocked) ? 1 : 0;
- break;
-
- case EGROKPRF_OBSOLETE:
- item.iImage = 2;
- break;
-
- case EGROKPRF_CANTREAD:
- item.iImage = (bFileLocked) ? 1 : 3;
- break;
-
- default:
- item.iImage = 3;
- }
-
- item.lParam = (LPARAM)dblink;
-
- int iItem = list.InsertItem(&item);
- if (mir_wstrcmpi(ped->szProfile, tszFullPath) == 0)
- list.SetItemState(iItem, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
-
- list.SetItemText(iItem, 2, sizeBuf);
-
- if (dblink != nullptr)
- list.SetItemText(iItem, 1, TranslateW(dblink->szFullName));
- else if (bFileLocked) // file locked
- list.SetItemText(iItem, 1, TranslateT("<In use>"));
- else
- list.SetItemText(iItem, 1, TranslateT("<Unknown format>"));
-
- return TRUE;
- }
-
void DeleteProfile(const LVITEM &item)
{
CMStringW wszMessage(FORMAT, TranslateT("Are you sure you want to remove profile \"%s\"?"), item.pszText);
if (IDYES != MessageBoxW(nullptr, wszMessage, L"Miranda NG", MB_YESNO | MB_TASKMODAL | MB_ICONWARNING))
return;
- wszMessage.Format(L"%s\\%s", m_pd->ptszProfileDir, item.pszText);
+ wszMessage.Format(L"%s\\%s", g_profileDir, item.pszText);
DeleteDirectoryTreeW(wszMessage, true);
m_profileList.DeleteItem(item.iItem);
@@ -332,7 +311,7 @@ class CChooseProfileDlg : public CDlgBase
void CheckProfile(const wchar_t *profile)
{
- CMStringW wszFullName(FORMAT, L"%s\\%s\\%s.dat", m_pd->ptszProfileDir, profile, profile);
+ CMStringW wszFullName(FORMAT, L"%s\\%s\\%s.dat", g_profileDir, profile, profile);
if (TryLoadPlugin(plugin_checker, false))
CallService(MS_DB_CHECKPROFILE, (WPARAM)wszFullName.c_str(), 0);
@@ -342,7 +321,7 @@ class CChooseProfileDlg : public CDlgBase
void CompactProfile(DATABASELINK *dblink, const wchar_t *profile)
{
- CMStringW wszFullName(FORMAT, L"%s\\%s\\%s.dat", m_pd->ptszProfileDir, profile, profile);
+ CMStringW wszFullName(FORMAT, L"%s\\%s\\%s.dat", g_profileDir, profile, profile);
if (auto *db = dblink->Load(wszFullName, false)) {
db->Compact();
@@ -383,11 +362,11 @@ class CChooseProfileDlg : public CDlgBase
// profile is placed in "profile_name" subfolder
wchar_t tmpPath[MAX_PATH];
- mir_snwprintf(tmpPath, L"%s\\%s.dat", m_pd->ptszProfileDir, profile);
+ mir_snwprintf(tmpPath, L"%s\\%s.dat", g_profileDir, profile);
if (_waccess(tmpPath, 2))
- mir_snwprintf(m_pd->ptszProfile, MAX_PATH, L"%s\\%s\\%s.dat", m_pd->ptszProfileDir, profile, profile);
+ m_pd->m_profile.Format(L"%s\\%s\\%s.dat", g_profileDir, profile, profile);
else
- wcsncpy_s(m_pd->ptszProfile, MAX_PATH, tmpPath, _TRUNCATE);
+ m_pd->m_profile = tmpPath;
}
void ExecuteMenu(LPARAM lParam)
@@ -504,11 +483,10 @@ public:
m_profileList.SetExtendedListViewStyle(m_profileList.GetExtendedListViewStyle() | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_LABELTIP | LVS_EX_FULLROWSELECT);
// find all the profiles
- ProfileEnumData ped(m_profileList, m_pd->ptszProfile);
- findProfiles(m_pd->ptszProfileDir, EnumProfilesForList, (LPARAM)&ped);
+ findProfiles(m_profileList, m_pd->m_profile);
PostMessage(m_hwnd, WM_FOCUSTEXTBOX, 0, 0);
- m_hFileNotify = FindFirstChangeNotification(m_pd->ptszProfileDir, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE);
+ m_hFileNotify = FindFirstChangeNotification(g_profileDir, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE);
if (m_hFileNotify != INVALID_HANDLE_VALUE)
SetTimer(m_hwnd, 0, 1200, nullptr);
return true;
@@ -545,7 +523,7 @@ public:
wchar_t profilename[MAX_PATH], tszFullPath[MAX_PATH];
struct _stat statbuf;
m_profileList.GetItemText(pTip->iItem, 0, profilename, _countof(profilename));
- mir_snwprintf(tszFullPath, L"%s\\%s\\%s.dat", m_pd->ptszProfileDir, profilename, profilename);
+ mir_snwprintf(tszFullPath, L"%s\\%s\\%s.dat", g_profileDir, profilename, profilename);
_wstat(tszFullPath, &statbuf);
mir_snwprintf(pTip->pszText, pTip->cchTextMax, L"%s\n%s: %s\n%s: %s", tszFullPath, TranslateT("Created"), rtrimw(NEWWSTR_ALLOCA(_wctime(&statbuf.st_ctime))), TranslateT("Modified"), rtrimw(NEWWSTR_ALLOCA(_wctime(&statbuf.st_mtime))));
}
@@ -563,15 +541,14 @@ public:
case WM_TIMER:
if (WaitForSingleObject(m_hFileNotify, 0) == WAIT_OBJECT_0) {
m_profileList.DeleteAllItems();
- ProfileEnumData ped(m_profileList, m_pd->ptszProfile);
- findProfiles(m_pd->ptszProfileDir, EnumProfilesForList, (LPARAM)&ped);
+ findProfiles(m_profileList, m_pd->m_profile);
FindNextChangeNotification(m_hFileNotify);
}
break;
case WM_FOCUSTEXTBOX:
SetFocus(m_profileList.GetHwnd());
- if (m_pd->ptszProfile[0] == 0 || m_profileList.GetSelectedCount() == 0)
+ if (m_pd->m_profile.IsEmpty() || m_profileList.GetSelectedCount() == 0)
m_profileList.SetItemState(0, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
break;
@@ -624,7 +601,7 @@ public:
SendMessage(m_hwnd, WM_SETICON, ICON_SMALL, (LPARAM)LoadImage(g_plugin.getInst(), MAKEINTRESOURCE(IDI_DETAILSLOGO), IMAGE_ICON, g_iIconSX, g_iIconSY, 0));
SendMessage(m_hwnd, WM_SETICON, ICON_BIG, (LPARAM)LoadImage(g_plugin.getInst(), MAKEINTRESOURCE(IDI_DETAILSLOGO), IMAGE_ICON, g_iIconX, g_iIconY, 0));
- if (m_pd->noProfiles || shouldAutoCreate(m_pd->ptszProfile))
+ if (m_pd->noProfiles || shouldAutoCreate(m_pd->m_profile))
m_tab.ActivatePage(1);
// service mode combobox
diff --git a/src/mir_app/src/profilemanager.h b/src/mir_app/src/profilemanager.h
index f4c68a46bf..953a026e02 100644
--- a/src/mir_app/src/profilemanager.h
+++ b/src/mir_app/src/profilemanager.h
@@ -26,13 +26,16 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
struct PROFILEMANAGERDATA
{
- wchar_t *ptszProfile; // in/out
- wchar_t *ptszProfileDir; // in/out
- BOOL noProfiles; // in
+ PROFILEMANAGERDATA(MFilePath &str) :
+ m_profile(str)
+ {}
+
+ MFilePath &m_profile; // in/out
+ bool noProfiles = false; // in
- BOOL bRun; // out
- BOOL newProfile; // out
- DATABASELINK *dblink; // out
+ bool bRun = false; // out
+ bool newProfile = false; // out
+ DATABASELINK *dblink = 0; // out
};
char* makeFileName(const wchar_t *tszOriginalName);
@@ -40,7 +43,7 @@ int touchDatabase(const wchar_t *tszProfile, DATABASELINK **pDblink);
int getProfileManager(PROFILEMANAGERDATA *pd);
int getProfilePath(wchar_t *buf, size_t cch);
int isValidProfileName(const wchar_t *name);
-bool shouldAutoCreate(wchar_t *szProfile);
+bool shouldAutoCreate(const MFilePath &szProfile);
extern wchar_t g_profileDir[MAX_PATH], g_profileName[MAX_PATH], g_shortProfileName[MAX_PATH];
extern bool g_bDbCreated;
diff --git a/src/mir_core/mir_core.vcxproj b/src/mir_core/mir_core.vcxproj
index f0466fabab..c748d431ce 100644
--- a/src/mir_core/mir_core.vcxproj
+++ b/src/mir_core/mir_core.vcxproj
@@ -140,6 +140,9 @@
<ClCompile Include="src\Windows\CTimer.cpp">
<PrecompiledHeaderFile>../stdafx.h</PrecompiledHeaderFile>
</ClCompile>
+ <ClCompile Include="src\Windows\fileutil.cpp">
+ <PrecompiledHeaderFile>../stdafx.h</PrecompiledHeaderFile>
+ </ClCompile>
<ClCompile Include="src\Windows\hyperlink.cpp">
<PrecompiledHeaderFile>../stdafx.h</PrecompiledHeaderFile>
</ClCompile>
diff --git a/src/mir_core/mir_core.vcxproj.filters b/src/mir_core/mir_core.vcxproj.filters
index 92df544754..c6fbd4c6a1 100644
--- a/src/mir_core/mir_core.vcxproj.filters
+++ b/src/mir_core/mir_core.vcxproj.filters
@@ -176,6 +176,9 @@
<ClCompile Include="src\Windows\winver.cpp">
<Filter>Source Files\Windows</Filter>
</ClCompile>
+ <ClCompile Include="src\Windows\fileutil.cpp">
+ <Filter>Source Files\Windows</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\miranda.h">
diff --git a/src/mir_core/src/Windows/fileutil.cpp b/src/mir_core/src/Windows/fileutil.cpp
new file mode 100644
index 0000000000..2522cc7cbe
--- /dev/null
+++ b/src/mir_core/src/Windows/fileutil.cpp
@@ -0,0 +1,78 @@
+/*
+Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org)
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation version 2
+of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "../stdafx.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MFilePath::MFileIterator::iterator MFilePath::MFileIterator::iterator::operator++()
+{
+ if (ptr != nullptr) {
+ if (::FindNextFileW(ptr->m_hFind, &ptr->m_data) == 0) {
+ ::FindClose(ptr->m_hFind); ptr->m_hFind = INVALID_HANDLE_VALUE;
+ ptr = nullptr;
+ }
+ }
+ return *this;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MFilePath::MFileIterator::MFileIterator(const wchar_t *pwszPath)
+{
+ if (pwszPath != nullptr)
+ m_hFind = ::FindFirstFileW(pwszPath, &m_data);
+}
+
+MFilePath::MFileIterator::~MFileIterator()
+{
+ if (m_hFind != INVALID_HANDLE_VALUE)
+ ::FindClose(m_hFind);
+}
+
+MFilePath::MFileIterator::iterator MFilePath::MFileIterator::begin()
+{
+ if (m_hFind == INVALID_HANDLE_VALUE)
+ return MFilePath::MFileIterator::iterator(nullptr);
+
+ return MFilePath::MFileIterator::iterator(this);
+}
+
+bool MFilePath::MFileIterator::isDir() const
+{
+ if (m_hFind == INVALID_HANDLE_VALUE)
+ return false;
+
+ return (m_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+bool MFilePath::isExist() const
+{
+ return _waccess(c_str(), 0) == 0;
+}
+
+bool MFilePath::move(const wchar_t *pwszDest)
+{
+ return MoveFileW(c_str(), pwszDest) != 0;
+}
+
+MFilePath::MFileIterator MFilePath::search()
+{
+ return MFileIterator(c_str());
+}
diff --git a/src/mir_core/src/mir_core.def b/src/mir_core/src/mir_core.def
index 021c8d309b..aff924ba4a 100644
--- a/src/mir_core/src/mir_core.def
+++ b/src/mir_core/src/mir_core.def
@@ -1515,3 +1515,21 @@ db_copy_module @1736
?db_is_module_empty@@YG_NIPBD@Z @1737 NONAME
?AddOption@CCtrlTreeOpts@@QAEXPB_W0AA_N@Z @1738 NONAME
?AddOption@CCtrlTreeOpts@@QAEXPB_W0AAII@Z @1739 NONAME
+??0MFileIterator@MFilePath@@QAE@PB_W@Z @1740 NONAME
+??0MFilePath@@QAE@$$QAV0@@Z @1741 NONAME
+??0MFilePath@@QAE@ABV0@@Z @1742 NONAME
+??0MFilePath@@QAE@PB_W@Z @1743 NONAME
+??0MFilePath@@QAE@XZ @1744 NONAME
+??1MFileIterator@MFilePath@@QAE@XZ @1745 NONAME
+??1MFilePath@@QAE@XZ @1746 NONAME
+??4MFileIterator@MFilePath@@QAEAAV01@ABV01@@Z @1747 NONAME
+??4MFilePath@@QAEAAV0@$$QAV0@@Z @1748 NONAME
+??4MFilePath@@QAEAAV0@ABV0@@Z @1749 NONAME
+?begin@MFileIterator@MFilePath@@QAE?AViterator@12@XZ @1750 NONAME
+?end@MFileIterator@MFilePath@@QAE?AViterator@12@XZ @1751 NONAME
+?getPath@MFileIterator@MFilePath@@QBEPB_WXZ @1752 NONAME
+?isDir@MFileIterator@MFilePath@@QBE_NXZ @1753 NONAME
+?isExist@MFilePath@@QBE_NXZ @1754 NONAME
+?move@MFilePath@@QAE_NPB_W@Z @1755 NONAME
+?search@MFilePath@@QAE?AVMFileIterator@1@XZ @1756 NONAME
+??Eiterator@MFileIterator@MFilePath@@QAE?AV012@XZ @1757 NONAME
diff --git a/src/mir_core/src/mir_core64.def b/src/mir_core/src/mir_core64.def
index 7a4fc63354..fb1e14fda7 100644
--- a/src/mir_core/src/mir_core64.def
+++ b/src/mir_core/src/mir_core64.def
@@ -1515,3 +1515,21 @@ db_copy_module @1736
?db_is_module_empty@@YA_NIPEBD@Z @1737 NONAME
?AddOption@CCtrlTreeOpts@@QEAAXPEB_W0AEA_N@Z @1738 NONAME
?AddOption@CCtrlTreeOpts@@QEAAXPEB_W0AEAII@Z @1739 NONAME
+??0MFileIterator@MFilePath@@QEAA@PEB_W@Z @1740 NONAME
+??0MFilePath@@QEAA@$$QEAV0@@Z @1741 NONAME
+??0MFilePath@@QEAA@AEBV0@@Z @1742 NONAME
+??0MFilePath@@QEAA@PEB_W@Z @1743 NONAME
+??0MFilePath@@QEAA@XZ @1744 NONAME
+??1MFileIterator@MFilePath@@QEAA@XZ @1745 NONAME
+??1MFilePath@@QEAA@XZ @1746 NONAME
+??4MFileIterator@MFilePath@@QEAAAEAV01@AEBV01@@Z @1747 NONAME
+??4MFilePath@@QEAAAEAV0@$$QEAV0@@Z @1748 NONAME
+??4MFilePath@@QEAAAEAV0@AEBV0@@Z @1749 NONAME
+?begin@MFileIterator@MFilePath@@QEAA?AViterator@12@XZ @1750 NONAME
+?end@MFileIterator@MFilePath@@QEAA?AViterator@12@XZ @1751 NONAME
+?getPath@MFileIterator@MFilePath@@QEBAPEB_WXZ @1752 NONAME
+?isDir@MFileIterator@MFilePath@@QEBA_NXZ @1753 NONAME
+?isExist@MFilePath@@QEBA_NXZ @1754 NONAME
+?move@MFilePath@@QEAA_NPEB_W@Z @1755 NONAME
+?search@MFilePath@@QEAA?AVMFileIterator@1@XZ @1756 NONAME
+??Eiterator@MFileIterator@MFilePath@@QEAA?AV012@XZ @1757 NONAME