Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Common/File/PathBrowser.cpp
5665 views
1
#include <cstring>
2
#include <set>
3
4
#include "ppsspp_config.h"
5
6
#include "Common/Net/HTTPClient.h"
7
#include "Common/Net/URL.h"
8
9
#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"
16
17
#if PPSSPP_PLATFORM(ANDROID)
18
#include "android/jni/app-android.h"
19
#endif
20
21
static bool LoadRemoteFileList(const Path &url, std::string_view userAgent, bool *cancel, std::vector<File::FileInfo> &files) {
22
_dbg_assert_(url.Type() == PathType::HTTP);
23
24
http::Client http(nullptr);
25
Buffer result;
26
int code = 500;
27
std::vector<std::string> responseHeaders;
28
29
http.SetUserAgent(userAgent);
30
31
Url baseURL(url.ToString());
32
if (!baseURL.Valid()) {
33
return false;
34
}
35
36
// Start by requesting the list of files from the server.
37
if (http.Resolve(baseURL.Host().c_str(), baseURL.Port())) {
38
if (http.Connect(2, 20.0, cancel)) {
39
http::RequestParams req(baseURL.Resource(), "text/plain, text/html; q=0.9, */*; q=0.8");
40
net::RequestProgress progress(cancel);
41
code = http.GET(req, &result, responseHeaders, &progress);
42
http.Disconnect();
43
}
44
}
45
46
if (code != 200 || (cancel && *cancel)) {
47
return false;
48
}
49
50
std::string listing;
51
std::vector<std::string> items;
52
result.TakeAll(&listing);
53
54
constexpr std::string_view ContentTypeHeader = "Content-Type:";
55
std::string contentType;
56
for (const std::string &header : responseHeaders) {
57
if (startsWithNoCase(header, ContentTypeHeader)) {
58
contentType = header.substr(ContentTypeHeader.size());
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
VERBOSE_LOG(Log::IO, "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
bool PathBrowser::GetListing(std::vector<File::FileInfo> &fileInfo, const char *extensionFilter, bool *cancel) {
208
std::unique_lock<std::mutex> guard(pendingLock_);
209
while (!IsListingReady() && (!cancel || !*cancel)) {
210
// In case cancel changes, just sleep. TODO: Replace with condition variable.
211
guard.unlock();
212
sleep_ms(50, "pathbrowser-poll");
213
guard.lock();
214
}
215
216
fileInfo = ApplyFilter(pendingFiles_, extensionFilter, "");
217
return true;
218
}
219
220
void PathBrowser::ApplyRestriction() {
221
if (!path_.StartsWith(restrictedRoot_) && !startsWith(path_.ToString(), "!")) {
222
WARN_LOG(Log::IO, "Applying path restriction: %s (%s didn't match)", restrictedRoot_.c_str(), path_.c_str());
223
path_ = restrictedRoot_;
224
}
225
}
226
227
bool PathBrowser::CanNavigateUp() {
228
if (path_ == restrictedRoot_) {
229
return false;
230
}
231
return path_.CanNavigateUp();
232
}
233
234
void PathBrowser::NavigateUp() {
235
_dbg_assert_(CanNavigateUp());
236
path_ = path_.NavigateUp();
237
ApplyRestriction();
238
}
239
240
// TODO: Support paths like "../../hello"
241
void PathBrowser::Navigate(std::string_view path) {
242
if (path == ".")
243
return;
244
if (path == "..") {
245
NavigateUp();
246
} else {
247
if (path.size() >= 2 && path[1] == ':' && path_.IsRoot())
248
path_ = Path(path);
249
else
250
path_ = path_ / path;
251
}
252
HandlePath();
253
}
254
255