CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Common/File/PathBrowser.cpp
Views: 1401
#include <algorithm>1#include <cstring>2#include <set>34#include "ppsspp_config.h"56#include "Common/Net/HTTPClient.h"7#include "Common/Net/URL.h"89#include "Common/File/PathBrowser.h"10#include "Common/File/FileUtil.h"11#include "Common/File/DirListing.h"12#include "Common/StringUtils.h"13#include "Common/TimeUtil.h"14#include "Common/Log.h"15#include "Common/Thread/ThreadUtil.h"1617#if PPSSPP_PLATFORM(ANDROID)18#include "android/jni/app-android.h"19#endif2021bool LoadRemoteFileList(const Path &url, const std::string &userAgent, bool *cancel, std::vector<File::FileInfo> &files) {22_dbg_assert_(url.Type() == PathType::HTTP);2324http::Client http;25Buffer result;26int code = 500;27std::vector<std::string> responseHeaders;2829http.SetUserAgent(userAgent);3031Url baseURL(url.ToString());32if (!baseURL.Valid()) {33return false;34}3536// Start by requesting the list of files from the server.37if (http.Resolve(baseURL.Host().c_str(), baseURL.Port())) {38if (http.Connect(2, 20.0, cancel)) {39http::RequestParams req(baseURL.Resource(), "text/plain, text/html; q=0.9, */*; q=0.8");40net::RequestProgress progress(cancel);41code = http.GET(req, &result, responseHeaders, &progress);42http.Disconnect();43}44}4546if (code != 200 || (cancel && *cancel)) {47return false;48}4950std::string listing;51std::vector<std::string> items;52result.TakeAll(&listing);5354std::string contentType;55for (const std::string &header : responseHeaders) {56if (startsWithNoCase(header, "Content-Type:")) {57contentType = header.substr(strlen("Content-Type:"));58// Strip any whitespace (TODO: maybe move this to stringutil?)59contentType.erase(0, contentType.find_first_not_of(" \t\r\n"));60contentType.erase(contentType.find_last_not_of(" \t\r\n") + 1);61}62}6364// TODO: Technically, "TExt/hTml ; chaRSet = Utf8" should pass, but "text/htmlese" should not.65// But unlikely that'll be an issue.66bool parseHtml = startsWithNoCase(contentType, "text/html");67bool parseText = startsWithNoCase(contentType, "text/plain");6869if (parseText) {70// Plain text format - easy.71SplitString(listing, '\n', items);72} else if (parseHtml) {73// Try to extract from an automatic webserver directory listing...74GetQuotedStrings(listing, items);75} else {76ERROR_LOG(Log::IO, "Unsupported Content-Type: %s", contentType.c_str());77return false;78}79Path basePath(baseURL.ToString());80for (auto &item : items) {81// Apply some workarounds.82if (item.empty())83continue;84if (item.back() == '\r') {85item.pop_back();86if (item.empty())87continue;88}89if (item == baseURL.Resource())90continue;9192File::FileInfo info;93if (item.back() == '/') {94item.pop_back();95if (item.empty())96continue;97info.isDirectory = true;98} else {99info.isDirectory = false;100}101info.name = item;102info.fullName = basePath / item;103info.exists = true;104info.size = 0;105info.isWritable = false;106files.push_back(info);107}108109return !files.empty();110}111112PathBrowser::~PathBrowser() {113{114std::unique_lock<std::mutex> guard(pendingLock_);115pendingCancel_ = true;116pendingStop_ = true;117pendingCond_.notify_all();118}119if (pendingThread_.joinable()) {120pendingThread_.join();121}122}123124void PathBrowser::SetPath(const Path &path) {125path_ = path;126ApplyRestriction();127HandlePath();128}129130void PathBrowser::RestrictToRoot(const Path &root) {131INFO_LOG(Log::System, "Restricting to root: %s", root.c_str());132restrictedRoot_ = root;133}134135void PathBrowser::HandlePath() {136if (!path_.empty() && path_.ToString()[0] == '!') {137if (pendingActive_)138ResetPending();139ready_ = true;140return;141}142143std::lock_guard<std::mutex> guard(pendingLock_);144ready_ = false;145pendingActive_ = true;146pendingCancel_ = false;147pendingFiles_.clear();148pendingPath_ = path_;149pendingCond_.notify_all();150151if (pendingThread_.joinable())152return;153154pendingThread_ = std::thread([&] {155SetCurrentThreadName("PathBrowser");156157AndroidJNIThreadContext jniContext; // destructor detaches158159std::unique_lock<std::mutex> guard(pendingLock_);160std::vector<File::FileInfo> results;161Path lastPath("NONSENSE THAT WONT EQUAL A PATH");162while (!pendingStop_) {163while (lastPath == pendingPath_ && !pendingCancel_) {164pendingCond_.wait(guard);165}166if (pendingStop_) {167break;168}169lastPath = pendingPath_;170if (lastPath.Type() == PathType::HTTP) {171guard.unlock();172results.clear();173success_ = LoadRemoteFileList(lastPath, userAgent_, &pendingCancel_, results);174guard.lock();175} else if (lastPath.empty()) {176results.clear();177success_ = true;178} else {179guard.unlock();180results.clear();181success_ = File::GetFilesInDir(lastPath, &results, nullptr);182if (!success_) {183WARN_LOG(Log::IO, "PathBrowser: Failed to list directory: %s", lastPath.c_str());184}185guard.lock();186}187188if (pendingPath_ == lastPath) {189if (success_ && !pendingCancel_) {190pendingFiles_ = results;191}192pendingPath_.clear();193lastPath.clear();194ready_ = true;195}196}197});198}199200void PathBrowser::ResetPending() {201std::lock_guard<std::mutex> guard(pendingLock_);202pendingCancel_ = true;203pendingPath_.clear();204}205206std::string PathBrowser::GetFriendlyPath() const {207// Show relative to memstick root if there.208if (path_.StartsWith(aliasMatch_)) {209std::string p;210if (aliasMatch_.ComputePathTo(path_, p)) {211return aliasDisplay_ + p;212}213std::string str = path_.ToString();214if (aliasMatch_.size() < str.length()) {215return aliasDisplay_ + str.substr(aliasMatch_.size());216} else {217return aliasDisplay_;218}219}220221std::string str = path_.ToString();222#if !PPSSPP_PLATFORM(ANDROID) && (PPSSPP_PLATFORM(LINUX) || PPSSPP_PLATFORM(MAC))223char *home = getenv("HOME");224if (home != nullptr && !strncmp(str.c_str(), home, strlen(home))) {225return std::string("~") + str.substr(strlen(home));226}227#endif228return path_.ToVisualString();229}230231bool PathBrowser::GetListing(std::vector<File::FileInfo> &fileInfo, const char *filter, bool *cancel) {232std::unique_lock<std::mutex> guard(pendingLock_);233while (!IsListingReady() && (!cancel || !*cancel)) {234// In case cancel changes, just sleep. TODO: Replace with condition variable.235guard.unlock();236sleep_ms(50);237guard.lock();238}239240fileInfo = ApplyFilter(pendingFiles_, filter);241return true;242}243244void PathBrowser::ApplyRestriction() {245if (!path_.StartsWith(restrictedRoot_) && !startsWith(path_.ToString(), "!")) {246WARN_LOG(Log::System, "Applying path restriction: %s (%s didn't match)", restrictedRoot_.c_str(), path_.c_str());247path_ = restrictedRoot_;248}249}250251bool PathBrowser::CanNavigateUp() {252if (path_ == restrictedRoot_) {253return false;254}255return path_.CanNavigateUp();256}257258void PathBrowser::NavigateUp() {259_dbg_assert_(CanNavigateUp());260path_ = path_.NavigateUp();261ApplyRestriction();262}263264// TODO: Support paths like "../../hello"265void PathBrowser::Navigate(const std::string &path) {266if (path == ".")267return;268if (path == "..") {269NavigateUp();270} else {271if (path.size() >= 2 && path[1] == ':' && path_.IsRoot())272path_ = Path(path);273else274path_ = path_ / path;275}276HandlePath();277}278279280