CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/Common/File/PathBrowser.cpp
Views: 1401
1
#include <algorithm>
2
#include <cstring>
3
#include <set>
4
5
#include "ppsspp_config.h"
6
7
#include "Common/Net/HTTPClient.h"
8
#include "Common/Net/URL.h"
9
10
#include "Common/File/PathBrowser.h"
11
#include "Common/File/FileUtil.h"
12
#include "Common/File/DirListing.h"
13
#include "Common/StringUtils.h"
14
#include "Common/TimeUtil.h"
15
#include "Common/Log.h"
16
#include "Common/Thread/ThreadUtil.h"
17
18
#if PPSSPP_PLATFORM(ANDROID)
19
#include "android/jni/app-android.h"
20
#endif
21
22
bool LoadRemoteFileList(const Path &url, const std::string &userAgent, bool *cancel, std::vector<File::FileInfo> &files) {
23
_dbg_assert_(url.Type() == PathType::HTTP);
24
25
http::Client http;
26
Buffer result;
27
int code = 500;
28
std::vector<std::string> responseHeaders;
29
30
http.SetUserAgent(userAgent);
31
32
Url baseURL(url.ToString());
33
if (!baseURL.Valid()) {
34
return false;
35
}
36
37
// Start by requesting the list of files from the server.
38
if (http.Resolve(baseURL.Host().c_str(), baseURL.Port())) {
39
if (http.Connect(2, 20.0, cancel)) {
40
http::RequestParams req(baseURL.Resource(), "text/plain, text/html; q=0.9, */*; q=0.8");
41
net::RequestProgress progress(cancel);
42
code = http.GET(req, &result, responseHeaders, &progress);
43
http.Disconnect();
44
}
45
}
46
47
if (code != 200 || (cancel && *cancel)) {
48
return false;
49
}
50
51
std::string listing;
52
std::vector<std::string> items;
53
result.TakeAll(&listing);
54
55
std::string contentType;
56
for (const std::string &header : responseHeaders) {
57
if (startsWithNoCase(header, "Content-Type:")) {
58
contentType = header.substr(strlen("Content-Type:"));
59
// Strip any whitespace (TODO: maybe move this to stringutil?)
60
contentType.erase(0, contentType.find_first_not_of(" \t\r\n"));
61
contentType.erase(contentType.find_last_not_of(" \t\r\n") + 1);
62
}
63
}
64
65
// TODO: Technically, "TExt/hTml ; chaRSet = Utf8" should pass, but "text/htmlese" should not.
66
// But unlikely that'll be an issue.
67
bool parseHtml = startsWithNoCase(contentType, "text/html");
68
bool parseText = startsWithNoCase(contentType, "text/plain");
69
70
if (parseText) {
71
// Plain text format - easy.
72
SplitString(listing, '\n', items);
73
} else if (parseHtml) {
74
// Try to extract from an automatic webserver directory listing...
75
GetQuotedStrings(listing, items);
76
} else {
77
ERROR_LOG(Log::IO, "Unsupported Content-Type: %s", contentType.c_str());
78
return false;
79
}
80
Path basePath(baseURL.ToString());
81
for (auto &item : items) {
82
// Apply some workarounds.
83
if (item.empty())
84
continue;
85
if (item.back() == '\r') {
86
item.pop_back();
87
if (item.empty())
88
continue;
89
}
90
if (item == baseURL.Resource())
91
continue;
92
93
File::FileInfo info;
94
if (item.back() == '/') {
95
item.pop_back();
96
if (item.empty())
97
continue;
98
info.isDirectory = true;
99
} else {
100
info.isDirectory = false;
101
}
102
info.name = item;
103
info.fullName = basePath / item;
104
info.exists = true;
105
info.size = 0;
106
info.isWritable = false;
107
files.push_back(info);
108
}
109
110
return !files.empty();
111
}
112
113
PathBrowser::~PathBrowser() {
114
{
115
std::unique_lock<std::mutex> guard(pendingLock_);
116
pendingCancel_ = true;
117
pendingStop_ = true;
118
pendingCond_.notify_all();
119
}
120
if (pendingThread_.joinable()) {
121
pendingThread_.join();
122
}
123
}
124
125
void PathBrowser::SetPath(const Path &path) {
126
path_ = path;
127
ApplyRestriction();
128
HandlePath();
129
}
130
131
void PathBrowser::RestrictToRoot(const Path &root) {
132
INFO_LOG(Log::System, "Restricting to root: %s", root.c_str());
133
restrictedRoot_ = root;
134
}
135
136
void PathBrowser::HandlePath() {
137
if (!path_.empty() && path_.ToString()[0] == '!') {
138
if (pendingActive_)
139
ResetPending();
140
ready_ = true;
141
return;
142
}
143
144
std::lock_guard<std::mutex> guard(pendingLock_);
145
ready_ = false;
146
pendingActive_ = true;
147
pendingCancel_ = false;
148
pendingFiles_.clear();
149
pendingPath_ = path_;
150
pendingCond_.notify_all();
151
152
if (pendingThread_.joinable())
153
return;
154
155
pendingThread_ = std::thread([&] {
156
SetCurrentThreadName("PathBrowser");
157
158
AndroidJNIThreadContext jniContext; // destructor detaches
159
160
std::unique_lock<std::mutex> guard(pendingLock_);
161
std::vector<File::FileInfo> results;
162
Path lastPath("NONSENSE THAT WONT EQUAL A PATH");
163
while (!pendingStop_) {
164
while (lastPath == pendingPath_ && !pendingCancel_) {
165
pendingCond_.wait(guard);
166
}
167
if (pendingStop_) {
168
break;
169
}
170
lastPath = pendingPath_;
171
if (lastPath.Type() == PathType::HTTP) {
172
guard.unlock();
173
results.clear();
174
success_ = LoadRemoteFileList(lastPath, userAgent_, &pendingCancel_, results);
175
guard.lock();
176
} else if (lastPath.empty()) {
177
results.clear();
178
success_ = true;
179
} else {
180
guard.unlock();
181
results.clear();
182
success_ = File::GetFilesInDir(lastPath, &results, nullptr);
183
if (!success_) {
184
WARN_LOG(Log::IO, "PathBrowser: Failed to list directory: %s", lastPath.c_str());
185
}
186
guard.lock();
187
}
188
189
if (pendingPath_ == lastPath) {
190
if (success_ && !pendingCancel_) {
191
pendingFiles_ = results;
192
}
193
pendingPath_.clear();
194
lastPath.clear();
195
ready_ = true;
196
}
197
}
198
});
199
}
200
201
void PathBrowser::ResetPending() {
202
std::lock_guard<std::mutex> guard(pendingLock_);
203
pendingCancel_ = true;
204
pendingPath_.clear();
205
}
206
207
std::string PathBrowser::GetFriendlyPath() const {
208
// Show relative to memstick root if there.
209
if (path_.StartsWith(aliasMatch_)) {
210
std::string p;
211
if (aliasMatch_.ComputePathTo(path_, p)) {
212
return aliasDisplay_ + p;
213
}
214
std::string str = path_.ToString();
215
if (aliasMatch_.size() < str.length()) {
216
return aliasDisplay_ + str.substr(aliasMatch_.size());
217
} else {
218
return aliasDisplay_;
219
}
220
}
221
222
std::string str = path_.ToString();
223
#if !PPSSPP_PLATFORM(ANDROID) && (PPSSPP_PLATFORM(LINUX) || PPSSPP_PLATFORM(MAC))
224
char *home = getenv("HOME");
225
if (home != nullptr && !strncmp(str.c_str(), home, strlen(home))) {
226
return std::string("~") + str.substr(strlen(home));
227
}
228
#endif
229
return path_.ToVisualString();
230
}
231
232
bool PathBrowser::GetListing(std::vector<File::FileInfo> &fileInfo, const char *filter, bool *cancel) {
233
std::unique_lock<std::mutex> guard(pendingLock_);
234
while (!IsListingReady() && (!cancel || !*cancel)) {
235
// In case cancel changes, just sleep. TODO: Replace with condition variable.
236
guard.unlock();
237
sleep_ms(50);
238
guard.lock();
239
}
240
241
fileInfo = ApplyFilter(pendingFiles_, filter);
242
return true;
243
}
244
245
void PathBrowser::ApplyRestriction() {
246
if (!path_.StartsWith(restrictedRoot_) && !startsWith(path_.ToString(), "!")) {
247
WARN_LOG(Log::System, "Applying path restriction: %s (%s didn't match)", restrictedRoot_.c_str(), path_.c_str());
248
path_ = restrictedRoot_;
249
}
250
}
251
252
bool PathBrowser::CanNavigateUp() {
253
if (path_ == restrictedRoot_) {
254
return false;
255
}
256
return path_.CanNavigateUp();
257
}
258
259
void PathBrowser::NavigateUp() {
260
_dbg_assert_(CanNavigateUp());
261
path_ = path_.NavigateUp();
262
ApplyRestriction();
263
}
264
265
// TODO: Support paths like "../../hello"
266
void PathBrowser::Navigate(const std::string &path) {
267
if (path == ".")
268
return;
269
if (path == "..") {
270
NavigateUp();
271
} else {
272
if (path.size() >= 2 && path[1] == ':' && path_.IsRoot())
273
path_ = Path(path);
274
else
275
path_ = path_ / path;
276
}
277
HandlePath();
278
}
279
280