/* 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 . */ #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 # else # ifdef FREERTOS extern "C" { # include } # else # include # endif # endif # include #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; piForeachC(PIString & 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 PIDir::entries() { PIVector 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 PIDir::allEntries() { PIVector ret; PIVector dirs; PIStringList cdirs, ndirs; cdirs << path(); while (!cdirs.isEmpty()) { piForeachC(PIString & d, cdirs) { scan_ = d; PIVector el = PIDir(d).entries(); piForeachC(PIFile::FileInfo & 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 rc = mkdtemp("/tmp/pidir_tmp_XXXXXX"); if (!rc) return PIDir(); PIString s(rc); return PIDir(s.left(s.findLast(PIDir::separator))); #endif } PIVector 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); }