Skip to content

Commit 40b80de

Browse files
committed
ANDROID: file manager webui (wip)
1 parent 3974431 commit 40b80de

File tree

4 files changed

+127
-42
lines changed

4 files changed

+127
-42
lines changed

src/platform/android/app/src/main/java/net/sourceforge/smallbasic/MainActivity.java

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,13 @@
6565
import java.net.NetworkInterface;
6666
import java.net.SocketException;
6767
import java.net.URLDecoder;
68+
import java.util.ArrayList;
6869
import java.util.Collection;
6970
import java.util.Date;
7071
import java.util.Enumeration;
72+
import java.util.HashMap;
7173
import java.util.Locale;
74+
import java.util.Map;
7275
import java.util.Properties;
7376
import java.util.Queue;
7477
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -666,11 +669,6 @@ protected void onStop() {
666669
}
667670
}
668671

669-
private String buildTokenForm() {
670-
return "<p>Enter access token:</p><form method=post><input type=text name=token>" +
671-
"<input value=OK name=okay type=submit style='vertical-align:top'></form>";
672-
}
673-
674672
private void checkFilePermission() {
675673
if (!permitted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
676674
setupStorageEnvironment(false);
@@ -807,9 +805,9 @@ private Uri getSharedFile(File file) {
807805
return result;
808806
}
809807

810-
private String getString(final byte[] promptBytes) {
808+
private String getString(final byte[] bytes) {
811809
try {
812-
return new String(promptBytes, CP1252);
810+
return new String(bytes, CP1252);
813811
} catch (UnsupportedEncodingException e) {
814812
Log.i(TAG, "getString failed: ", e);
815813
return "";
@@ -930,50 +928,89 @@ private void setupStorageEnvironment(boolean external) {
930928
}
931929
}
932930

933-
private class WebServerImpl extends WebServer {
931+
private static class BasFileFilter implements FilenameFilter {
934932
@Override
935-
protected void execStream(InputStream inputStream) throws IOException {
936-
MainActivity.this.execStream(inputStream);
933+
public boolean accept(File dir, String name) {
934+
return name.endsWith(".bas");
937935
}
936+
}
937+
938+
private class WebServerImpl extends WebServer {
939+
private final Map<String, Long> fileLengths = new HashMap<>();
938940

939941
@Override
940-
protected Collection<FileData> getFileData() throws IOException {
941-
// TODO
942-
return null;
942+
protected void execStream(InputStream inputStream) throws IOException {
943+
MainActivity.this.execStream(inputStream);
943944
}
944945

945946
@Override
946947
protected Response getFile(String path, boolean asset) throws IOException {
947948
Response result;
948949
if (asset) {
949950
String name = "webui/" + path;
950-
AssetFileDescriptor fd = getAssets().openFd(name);
951-
result = new Response(getAssets().open(name), fd.getLength());
952-
}
953-
else {
951+
long length = getFileLength(name);
952+
log("Opened " + name + " " + length + " bytes");
953+
result = new Response(getAssets().open(name), length);
954+
} else {
954955
result = null;
955956
}
956957
return result;
957958
}
958959

959960
@Override
960-
protected void log(String message, Exception exception) {
961-
Log.i(TAG, message, exception);
961+
protected Collection<FileData> getFileData() throws IOException {
962+
Collection<FileData> result = new ArrayList<>();
963+
result.addAll(getFiles(new File(getExternalStorage())));
964+
result.addAll(getFiles(new File(getInternalStorage())));
965+
return result;
962966
}
963967

964968
@Override
965-
protected boolean renameFile(String from, String to) {
966-
return false;
969+
protected void log(String message, Exception exception) {
970+
Log.i(TAG, message, exception);
967971
}
968972

969973
@Override
970974
protected void log(String message) {
971975
Log.i(TAG, message);
972976
}
973977

978+
@Override
979+
protected boolean renameFile(String from, String to) {
980+
return false;
981+
}
982+
974983
@Override
975984
protected boolean saveFile(String fileName, String content) {
976985
return false;
977986
}
987+
988+
private long getFileLength(String name) throws IOException {
989+
Long length = fileLengths.get(name);
990+
if (length == null) {
991+
length = 0L;
992+
InputStream inputStream = getAssets().open(name);
993+
while (inputStream.available() > 0) {
994+
int unused = inputStream.read();
995+
length++;
996+
}
997+
inputStream.close();
998+
fileLengths.put(name, length);
999+
}
1000+
return length;
1001+
}
1002+
1003+
private Collection<FileData> getFiles(File path) {
1004+
Collection<FileData> result = new ArrayList<>();
1005+
if (path.isDirectory() && path.canRead()) {
1006+
File[] files = path.listFiles(new BasFileFilter());
1007+
if (files != null) {
1008+
for (File file : files) {
1009+
result.add(new FileData(file));
1010+
}
1011+
}
1012+
}
1013+
return result;
1014+
}
9781015
};
9791016
}

src/platform/android/app/src/main/java/net/sourceforge/smallbasic/WebServer.java

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
import java.io.BufferedOutputStream;
44
import java.io.ByteArrayInputStream;
55
import java.io.ByteArrayOutputStream;
6+
import java.io.File;
67
import java.io.IOException;
78
import java.io.InputStream;
89
import java.io.OutputStream;
910
import java.io.UnsupportedEncodingException;
1011
import java.net.ServerSocket;
1112
import java.net.Socket;
1213
import java.net.URLDecoder;
14+
import java.text.DateFormat;
1315
import java.util.ArrayList;
1416
import java.util.Collection;
1517
import java.util.Collections;
@@ -30,6 +32,9 @@ public abstract class WebServer {
3032
private static final int LINE_SIZE = 128;
3133
private static final String UTF_8 = "utf-8";
3234
private static final String TOKEN = "token";
35+
private static final int SC_OKAY = 200;
36+
private static final int SC_NOT_AUTHORISED = 401;
37+
private static final int SC_NOT_FOUND = 404;
3338

3439
public WebServer() {
3540
super();
@@ -52,8 +57,8 @@ public void run() {
5257
}
5358

5459
protected abstract void execStream(InputStream inputStream) throws IOException;
55-
protected abstract Collection<FileData> getFileData() throws IOException;
5660
protected abstract Response getFile(String path, boolean asset) throws IOException;
61+
protected abstract Collection<FileData> getFileData() throws IOException;
5762
protected abstract void log(String message);
5863
protected abstract void log(String message, Exception exception);
5964
protected abstract boolean renameFile(String from, String to);
@@ -154,7 +159,14 @@ private Response handleWebResponse(String asset) throws IOException {
154159
} else {
155160
path = asset;
156161
}
157-
return getFile(path, true);
162+
Response result;
163+
try {
164+
result = getFile(path, true);
165+
} catch (Exception e) {
166+
log("error: " + e);
167+
result = null;
168+
}
169+
return result != null ? result : new Response(SC_NOT_FOUND);
158170
}
159171

160172
/**
@@ -181,7 +193,6 @@ private void runServer(final int socketNum, final String token) throws IOExcepti
181193
request.invoke(inputStream, token);
182194
} catch (Exception e) {
183195
log("Server failed", e);
184-
break;
185196
} finally {
186197
log("socket cleanup");
187198
if (socket != null) {
@@ -192,7 +203,6 @@ private void runServer(final int socketNum, final String token) throws IOExcepti
192203
}
193204
}
194205
}
195-
log("server stopped");
196206
}
197207

198208
/**
@@ -208,6 +218,13 @@ public FileData(String fileName, String date, long size) {
208218
this.date = date;
209219
this.size = size;
210220
}
221+
222+
public FileData(File file) {
223+
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.DEFAULT);
224+
this.fileName = file.getName();
225+
this.date = dateFormat.format(file.lastModified());
226+
this.size = file.length();
227+
}
211228
}
212229

213230
/**
@@ -265,8 +282,14 @@ public Request(Socket socket, InputStream inputStream) throws IOException {
265282
this.headers = getHeaders(inputStream);
266283
this.socket = socket;
267284
this.token = getToken(headers);
268-
String[] fields = headers.get(0).split("\\s");
269-
if (fields.length > 1) {
285+
String first = headers.get(0);
286+
String[] fields;
287+
if (first != null) {
288+
fields = first.split("\\s");
289+
} else {
290+
fields = null;
291+
}
292+
if (fields != null && fields.length > 1) {
270293
this.method = fields[0];
271294
this.url = fields[1];
272295
} else {
@@ -405,6 +428,9 @@ private void handleGet(Map<String, Collection<String>> parameters, String tokenK
405428
if (url.startsWith("/api/download?")) {
406429
if (tokenKey.equals(token)) {
407430
handleDownload(parameters).send(socket, null);
431+
} else {
432+
log("Invalid token");
433+
new Response(SC_NOT_AUTHORISED).send(socket, null);
408434
}
409435
} else {
410436
handleWebResponse(url).send(socket, null);
@@ -421,23 +447,30 @@ private void handlePost(Map<String, String> data, String tokenKey) throws IOExce
421447
}
422448
log("userToken=" + userToken);
423449
if (tokenKey.equals(userToken)) {
424-
if (url.startsWith("/api/files")) {
450+
if (url.startsWith("/api/login")) {
425451
handleFileList().send(socket, userToken);
452+
} else if (url.startsWith("/api/files")) {
453+
handleFileList().send(socket, null);
426454
} else if (url.startsWith("/api/upload")) {
427455
handleUpload(data).send(socket, null);
428456
} else if (url.startsWith("/api/rename")) {
429457
handleRename(data).send(socket, null);
458+
} else {
459+
new Response(SC_NOT_FOUND).send(socket, null);
430460
}
431461
log("Sent POST response");
432462
} else {
433463
log("Invalid token");
434-
handleStatus(false, "Invalid token").send(socket, null);
464+
if (url.startsWith("/api/login")) {
465+
handleStatus(false, "Invalid token").send(socket, null);
466+
} else {
467+
new Response(SC_NOT_AUTHORISED).send(socket, null);
468+
}
435469
}
436470
}
437471

438472
private void handleRun(InputStream inputStream, String tokenKey) throws IOException {
439-
if (tokenKey.equals(token)) {
440-
execStream(inputStream);
473+
if (tokenKey.equals(token)) { execStream(inputStream);
441474
} else {
442475
log("Invalid token");
443476
}
@@ -450,29 +483,40 @@ private void handleRun(InputStream inputStream, String tokenKey) throws IOExcept
450483
public class Response {
451484
private final InputStream inputStream;
452485
private final long length;
486+
private final int errorCode;
453487

454488
public Response(InputStream inputStream, long length) {
455489
this.inputStream = inputStream;
456490
this.length = length;
491+
this.errorCode = SC_OKAY;
457492
}
458493

459494
public Response(byte[] bytes) {
460495
this.inputStream = new ByteArrayInputStream(bytes);
461496
this.length = bytes.length;
497+
this.errorCode = SC_OKAY;
498+
}
499+
500+
public Response(int errorCode) throws IOException {
501+
byte[] bytes = ("Error: " + errorCode).getBytes(UTF_8);
502+
this.inputStream = new ByteArrayInputStream(bytes);
503+
this.length = bytes.length;
504+
this.errorCode = errorCode;
462505
}
463506

464507
/**
465508
* Sends the response to the given socket
466509
*/
467-
void send(Socket socket, String session) throws IOException {
510+
void send(Socket socket, String cookie) throws IOException {
468511
log("sendResponse() entered");
469512
String contentLength = "Content-length: " + length + "\r\n";
470513
BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream());
471-
out.write("HTTP/1.0 200 OK\r\n".getBytes());
514+
String code = errorCode == SC_OKAY ? SC_OKAY + " OK" : String.valueOf(errorCode);
515+
out.write(("HTTP/1.0 " + code + "\r\n").getBytes());
472516
out.write("Content-type: text/html\r\n".getBytes());
473517
out.write(contentLength.getBytes());
474-
if (session != null) {
475-
out.write(("Set-Cookie: " + TOKEN + "=" + session + "\r\n").getBytes());
518+
if (cookie != null) {
519+
out.write(("Set-Cookie: " + TOKEN + "=" + cookie + "\r\n").getBytes());
476520
}
477521
out.write("Server: SmallBASIC for Android\r\n\r\n".getBytes());
478522
socket.setSendBufferSize(SEND_SIZE);

src/platform/android/webui/server/src/main/java/net/sourceforge/smallbasic/Server.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ protected Collection<FileData> getFileData() throws IOException {
4747
}
4848

4949
@Override
50-
protected Response getResponse(String path, boolean asset) throws IOException {
50+
protected Response getFile(String path, boolean asset) throws IOException {
5151
String prefix = asset ? "../build/" : "../basic/";
5252
File file = new File(prefix + path);
5353
return new Response(Files.newInputStream(file.toPath()), file.length());

src/platform/android/webui/src/App.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,13 @@ function fetchApi(api, body, success, fail) {
6969
.catch(fail);
7070
}
7171

72-
function getFiles(token, success, fail) {
73-
let body = token ? ("token=" + token) : "";
74-
fetchApi('/api/files', body, success, fail);
72+
function getFiles(success, fail) {
73+
fetchApi('/api/files', "", success, fail);
74+
}
75+
76+
function login(token, success, fail) {
77+
let body = "token=" + token;
78+
fetchApi('/api/login', body, success, fail);
7579
}
7680

7781
function upload(name, data, success, fail) {
@@ -94,7 +98,7 @@ function copyFiles(event, success, fail) {
9498
if (++index < files.length) {
9599
fileReader.readAsText(files[index]);
96100
} else {
97-
getFiles(null, success, fail);
101+
getFiles(success, fail);
98102
// reset input control
99103
input.value = input.defaultValue;
100104
}
@@ -159,7 +163,7 @@ function onCellEditCommit(props, params) {
159163
props.rows.forEach((row) => {
160164
if (row.id === params.id) {
161165
renameFile(row.fileName, params.value, () => {
162-
getFiles(null, (data) => {
166+
getFiles((data) => {
163167
props.setRows(data);
164168
}, (error) => {
165169
console.log(error);
@@ -200,7 +204,7 @@ function TokenInput(props) {
200204
const [error, setError] = useState(false);
201205

202206
const onClick = () => {
203-
getFiles(token, (data) => {
207+
login(token, (data) => {
204208
props.setRows(data);
205209
props.setToken(token);
206210
}, () => {

0 commit comments

Comments
 (0)