Files
pip/libs/main/io_devices/pidir.cpp
peri4 caa7880cc4 get rid of piForeach
apply some code analyzer recommendations
ICU flag now check if libicu exists
prepare for more accurate growth of containers (limited PoT, then constantly increase size)
2024-11-20 20:01:47 +03:00

596 lines
14 KiB
C++

/*
PIP - Platform Independent Primitives
Directory
Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "pidir.h"
#include "piincludes_p.h"
const PIChar PIDir::separator = '/';
#ifdef QNX
# define _stat_struct_ struct stat
# define _stat_call_ stat
# define _stat_link_ lstat
#else
# define _stat_struct_ struct stat64
# define _stat_call_ stat64
# define _stat_link_ lstat64
#endif
#ifndef WINDOWS
# ifdef ANDROID
# include <dirent.h>
# else
# ifdef FREERTOS
extern "C" {
# include <sys/dirent.h>
}
# else
# include <sys/dir.h>
# endif
# endif
# include <sys/stat.h>
#endif
//! \class PIDir pidir.h
//! \details
//! \~english \section PIDir_sec0 Synopsis
//! \~russian \section PIDir_sec0 Краткий обзор
//! \~english
//! This class provide access to local directory.
//!
//! \~russian
//!
PIDir::PIDir(const PIString & dir) {
setDir(dir);
}
PIDir::PIDir(const PIFile & file) {
setDir(file.path());
if (isExists()) return;
int pos = path_.findLast(separator);
path_.cutRight(path_.size_s() - pos);
}
bool PIDir::operator==(const PIDir & d) const {
return d.absolutePath() == absolutePath();
}
bool PIDir::isAbsolute() const {
if (path_.isEmpty()) return false;
if (path_[0] == separator) return true;
#ifdef WINDOWS
return (path_.mid(1, 1) == ":");
#endif
return false;
}
PIString PIDir::name() const {
PIStringList fp = absolutePath().split(separator);
fp.removeStrings("");
if (fp.isEmpty()) return "";
return fp.back();
}
PIString PIDir::path() const {
#ifdef WINDOWS
if (path_.startsWith(separator)) {
if (path_.length() == 1)
return separator;
else
return path_.mid(1);
} else
#endif
return path_;
}
PIString PIDir::absolutePath() const {
if (isAbsolute()) {
#ifdef WINDOWS
if (path_.startsWith(separator)) {
if (path_.length() == 1)
return separator;
else
return path_.mid(1);
} else
#endif
return path_;
}
return PIDir(PIDir::current().path() + separator + path_).path();
}
//! \details
//! \~english
//! This function remove repeatedly separators and
//! resolve ".." in path. E.g. "/home/.//user/src/../.." will
//! become "/home". \n Returns reference to this %PIDir
//! \~russian
//! Этот метод удаляет повторяющиеся разделители и разрешает
//! "..". Например, путь "/home/.//user/src/../.." станет "/home". \n
//! Возвращает ссылку на этот %PIDir
PIDir & PIDir::cleanPath() {
PIString p(path_);
if (p.isEmpty()) {
path_ = ".";
return *this;
}
PIString sep = PIString(separator);
path_.replaceAll(sep + sep, sep);
bool is_abs = isAbsolute();
PIStringList l = PIString(p).split(separator);
l.removeAll(".");
l.removeAll("");
for (int i = 0; i < l.size_s() - 1; ++i) {
if (l[i] != ".." && l[i + 1] == "..") {
l.remove(i, 2);
i -= 2;
if (i < -1) i = -1;
}
}
if (is_abs)
while (!l.isEmpty()) {
if (l.front() == "..")
l.pop_front();
else
break;
}
path_ = l.join(separator);
if (is_abs) path_.prepend(separator);
if (path_.isEmpty()) path_ = ".";
return *this;
}
PIString PIDir::relative(const PIString & path) const {
PIDir td(path);
PIStringList dl(absolutePath().split(separator)), pl(td.absolutePath().split(separator)), rl;
auto checkPath = [](PIStringList & path) {
if (path.isEmpty()) return;
if (path[0].size() >= 2) {
if (path[0][0].isAlpha() && path[0][1] == ':') {
path[0][0] = path[0][0].toUpper();
}
}
};
checkPath(dl);
checkPath(pl);
// piCout << pl << "rel to" << dl;
while (!dl.isEmpty() && !pl.isEmpty()) {
if (dl.front() != pl.front()) break;
dl.pop_front();
pl.pop_front();
}
for (int i = 0; i < dl.size_s(); ++i)
rl << "..";
rl << pl;
if (rl.isEmpty()) return ".";
return rl.join(separator);
}
PIString PIDir::absolute(const PIString & path) const {
PIFile::FileInfo fi(path);
if (fi.isAbsolute()) return path;
PIString ret = absolutePath();
if (!ret.endsWith(separator) && !path.startsWith(separator)) ret += separator;
PIDir td(ret + path);
return td.cleanPath().path();
}
PIDir & PIDir::setDir(const PIString & path) {
path_ = path;
#ifdef WINDOWS
path_.replaceAll("\\", separator);
if (path_.length() > 2)
if (path_.mid(1, 2).contains(":")) path_.prepend(separator);
#endif
cleanPath();
return *this;
}
PIDir & PIDir::cd(const PIString & path) {
if (path_.isEmpty()) return *this;
if (path_.back() != separator) path_ += separator;
path_ += path;
return cleanPath();
}
bool PIDir::make(bool withParents) {
PIDir d = cleanedPath();
// PIString tp;
#ifndef WINDOWS
bool is_abs = isAbsolute();
#endif
if (withParents) {
PIStringList l = d.path().split(separator);
// piCout << l;
l.removeAll("");
// piCout << l;
PIString cdp;
for (const auto & i: l) {
if (!cdp.isEmpty()
#ifndef WINDOWS
|| is_abs
#endif
)
cdp += separator;
cdp += i;
// piCout << "dir" << cdp;
if (!isExists(cdp))
if (!makeDir(cdp)) return false;
}
/*for (int i = l.size_s() - 1; i >= 0; --i) {
if (i > 1) tp = PIStringList(l).remove(i, l.size_s() - i).join(separator);
else {
tp = separator;
if (!is_abs) tp.push_front('.');
}
piCout << "check" << tp;
if (isExists(tp)) {
for (int j = i + 1; j <= l.size_s(); ++j) {
tp = PIStringList(l).remove(j, l.size_s() - j).join(separator);
piCout << "make" << tp;
if (makeDir(tp)) continue;
else return false;
}
break;
};
}*/
return true;
} else if (makeDir(d.path()))
return true;
return false;
}
bool PIDir::rename(const PIString & new_name) {
if (!PIDir::rename(path(), new_name)) return false;
setDir(new_name);
return true;
}
#ifdef WINDOWS
bool sort_compare(const PIFile::FileInfo & v0, const PIFile::FileInfo & v1) {
return strcoll(v0.path.data(), v1.path.data()) < 0;
}
#endif
//! \~\details
//! \~english
//! Scan this directory and returns all directories
//! and files in one list, sorted alphabetically. This list
//! contains also "." and ".." members. There are absolute
//! pathes in returned list.
//! \attention This function doesn`t scan content of inner
//! directories!
//! \~russian
//! Читает директорию и возвращает все директории и файлы
//! одним списком, сортированным по алфавиту. Список содержит
//! также "." и "..". Возвращаются абсолютные пути.
//! \attention Этот метод не читает содержимое директорий
//! рекурсивно!
PIVector<PIFile::FileInfo> PIDir::entries() {
PIVector<PIFile::FileInfo> l;
if (!isExists()) return l;
PIString dp = absolutePath();
PIString p(dp);
if (dp == ".")
dp.clear();
else if (!dp.endsWith(separator))
dp += separator;
// piCout << "start entries from" << p;
#ifdef WINDOWS
if (dp == separator) {
char letters[1024];
PIFile::FileInfo fi;
DWORD ll = GetLogicalDriveStringsA(1023, letters);
PIString clet;
for (DWORD i = 0; i < ll; ++i) {
if (letters[i] == '\0') {
clet.resize(2);
fi.path = clet;
fi.flags = PIFile::FileInfo::Dir;
l << fi;
clet.clear();
} else
clet += PIChar(letters[i]);
}
} else {
WIN32_FIND_DATAA fd;
memset(&fd, 0, sizeof(fd));
p += "\\*";
void * hf = FindFirstFileA((LPCSTR)(p.data()), &fd);
if (!hf) return l;
bool hdd = false;
do {
PIString fn(fd.cFileName);
if (fn == "..") hdd = true;
l << PIFile::fileInfo(dp + fn);
memset(&fd, 0, sizeof(fd));
} while (FindNextFileA(hf, &fd) != 0);
FindClose(hf);
l.sort(sort_compare);
if (!hdd) {
PIFile::FileInfo fi;
fi.path = "..";
fi.flags = PIFile::FileInfo::Dir | PIFile::FileInfo::DotDot;
l.push_front(fi);
}
}
#else
# if defined(QNX) || defined(FREERTOS)
struct dirent * de = 0;
DIR * dir = 0;
dir = opendir(p.data());
if (dir) {
for (;;) {
de = readdir(dir);
if (!de) break;
l << PIFile::fileInfo(dp + PIString(de->d_name));
}
closedir(dir);
}
# else
dirent ** list;
int cnt = scandir(p.data(),
&list,
0,
# if defined(MAC_OS) || defined(ANDROID) || defined(BLACKBERRY)
alphasort);
# else
versionsort);
# endif
for (int i = 0; i < cnt; ++i) {
l << PIFile::fileInfo(dp + PIString(list[i]->d_name));
free(list[i]);
}
free(list);
# endif
#endif
// piCout << "end entries from" << p;
return l;
}
//! \~\details
//! \~english
//! Scan this directory recursively and returns all
//! directories and files in one list, sorted alphabetically.
//! This list doesn`t contains "." and ".." members. There
//! are absolute pathes in returned list, and
//! files placed after directories in this list.
//! \~russian
//! Читает директорию рекурсивно и возвращает все директории и файлы
//! одним списком, сортированным по алфавиту. Список не содержит
//! "." и "..". Возвращаются абсолютные пути, причём файлы
//! располагаются после директорий.
PIVector<PIFile::FileInfo> PIDir::allEntries() {
PIVector<PIFile::FileInfo> ret;
PIVector<PIFile::FileInfo> dirs;
PIStringList cdirs, ndirs;
cdirs << path();
while (!cdirs.isEmpty()) {
for (const auto & d: cdirs) {
scan_ = d;
PIVector<PIFile::FileInfo> el = PIDir(d).entries();
for (const auto & de: el) {
if (de.name() == "." || de.name() == "..") continue;
if (de.isSymbolicLink()) continue; /// TODO: resolve symlinks
if (de.isDir()) {
dirs << de;
ndirs << de.path;
} else
ret << de;
}
}
cdirs = ndirs;
ndirs.clear();
}
ret.insert(0, dirs);
scan_.clear();
return ret;
}
bool PIDir::isExists(const PIString & path) {
#ifdef WINDOWS
DWORD ret = GetFileAttributes((LPCTSTR)(path.data()));
return (ret != 0xFFFFFFFF) && (ret & FILE_ATTRIBUTE_DIRECTORY);
#else
DIR * dir_ = opendir(path.data());
if (dir_ == 0) return false;
closedir(dir_);
#endif
return true;
}
PIDir PIDir::current() {
#ifndef ESP_PLATFORM
char rc[1024];
#endif
#ifdef WINDOWS
memset(rc, 0, 1024);
if (GetCurrentDirectory(1024, (LPTSTR)rc) == 0) return PIString();
PIString ret(rc);
ret.replaceAll("\\", PIDir::separator);
ret.prepend(separator);
return PIDir(ret);
#else
# ifndef ESP_PLATFORM
if (getcwd(rc, 1024) == 0) return PIString();
return PIDir(rc);
# else
return PIDir("/spiffs");
# endif
#endif
}
PIDir PIDir::home() {
#ifndef ESP_PLATFORM
char * rc = nullptr;
#endif
#ifdef WINDOWS
rc = new char[1024];
memset(rc, 0, 1024);
if (ExpandEnvironmentStrings((LPCTSTR) "%HOMEPATH%", (LPTSTR)rc, 1024) == 0) {
delete[] rc;
return PIDir();
}
PIString hp(rc);
memset(rc, 0, 1024);
if (ExpandEnvironmentStrings((LPCTSTR) "%HOMEDRIVE%", (LPTSTR)rc, 1024) == 0) {
delete[] rc;
return PIDir();
}
PIString hd(rc);
hp.replaceAll("\\", PIDir::separator);
delete[] rc;
// s.prepend(separator);
return PIDir(hd + hp);
#else
# ifndef ESP_PLATFORM
rc = getenv("HOME");
if (!rc) return PIDir();
return PIDir(rc);
# else
return PIDir();
# endif
#endif
}
PIDir PIDir::temporary() {
char * rc = nullptr;
#ifdef WINDOWS
rc = new char[1024];
memset(rc, 0, 1024);
int ret = GetTempPath(1024, (LPTSTR)rc);
if (ret == 0) {
delete[] rc;
return PIDir();
}
PIString s(rc);
s.replaceAll("\\", PIDir::separator);
delete[] rc;
s.prepend(separator);
return PIDir(s);
#else
char template_rc[] = "/tmp/pidir_tmp_XXXXXX";
rc = mkdtemp(template_rc);
if (!rc) return PIDir();
PIString s(rc);
return PIDir(s.left(s.findLast(PIDir::separator)));
#endif
}
PIVector<PIFile::FileInfo> PIDir::allEntries(const PIString & path) {
return PIDir(path).allEntries();
}
bool PIDir::make(const PIString & path, bool withParents) {
PIDir d(path);
if (d.isExists()) return true;
return d.make(withParents);
}
bool PIDir::setCurrent(const PIString & path) {
#ifdef WINDOWS
if (SetCurrentDirectory((LPCTSTR)(path.data())) != 0) return true;
#else
if (chdir(path.data()) == 0) return true;
#endif
printf("[PIDir] setCurrent(\"%s\") error: %s\n", path.data(), errorString().data());
return false;
}
bool PIDir::makeDir(const PIString & path) {
#ifdef WINDOWS
if (CreateDirectory((LPCTSTR)(path.data()), NULL) != 0) return true;
#else
if (mkdir(path.data(), 16877) == 0) return true;
#endif
printf("[PIDir] makeDir(\"%s\") error: %s\n", path.data(), errorString().data());
return false;
}
bool PIDir::removeDir(const PIString & path) {
#ifdef WINDOWS
if (RemoveDirectory((LPCTSTR)(path.data())) != 0) return true;
#else
if (rmdir(path.data()) == 0) return true;
#endif
printf("[PIDir] removeDir(\"%s\") error: %s\n", path.data(), errorString().data());
return false;
}
bool PIDir::renameDir(const PIString & path, const PIString & new_name) {
if (::rename(path.data(), new_name.data()) == 0) return true;
printf("[PIDir] renameDir(\"%s\", \"%s\") error: %s\n", path.data(), new_name.data(), errorString().data());
return false;
}
PIDir::CurrentDirOverrider::CurrentDirOverrider(const PIString & path) {
save(PIFile::fileInfo(path));
}
PIDir::CurrentDirOverrider::CurrentDirOverrider(const PIFile::FileInfo & info) {
save(info);
}
void PIDir::CurrentDirOverrider::restore() {
if (!active) return;
active = false;
PIDir::setCurrent(prev_cd);
}
void PIDir::CurrentDirOverrider::save(const PIFile::FileInfo & info) {
prev_cd = PIDir::current().path();
PIString p = info.isDir() ? info.path : info.dir();
if (info.isAbsolute())
PIDir::setCurrent(p);
else
PIDir::setCurrent(PIDir::current().path() + PIDir::separator + p);
}