mirror of
https://github.com/Team4388/RidgeScout.git
synced 2026-06-09 00:37:59 -06:00
Merge pull request #6 from Team4388/python-file-transfer
Add HTTP file transfer
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
# Python server
|
||||
__pycache__/
|
||||
metadata.json
|
||||
api_key.txt
|
||||
server_data/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.ridgebotics.ridgescout.R;
|
||||
import com.ridgebotics.ridgescout.types.frcEvent;
|
||||
import com.ridgebotics.ridgescout.utility.AlertManager;
|
||||
import com.ridgebotics.ridgescout.utility.FileEditor;
|
||||
import com.ridgebotics.ridgescout.utility.SettingsManager;
|
||||
import com.ridgebotics.ridgescout.databinding.FragmentScoutingBinding;
|
||||
@@ -126,7 +127,12 @@ public class ScoutingFragment extends Fragment {
|
||||
binding.textName.setText("Welcome, " + SettingsManager.getUsername() + "!");
|
||||
|
||||
int matchNum = SettingsManager.getMatchNum();
|
||||
int nextMatch = event.getNextTeamMatch(SettingsManager.getTeamNum(), matchNum).matchIndex;
|
||||
int nextMatch = -1;
|
||||
try {
|
||||
nextMatch = event.getNextTeamMatch(SettingsManager.getTeamNum(), matchNum).matchIndex;
|
||||
} catch (Exception e){
|
||||
AlertManager.error(e);
|
||||
}
|
||||
|
||||
binding.textNextMatch.setText("Our next match: Match " + nextMatch);
|
||||
binding.textMatchAlliance.setText("Match: " + (matchNum+1) + ", " + SettingsManager.getAllyPos());
|
||||
|
||||
@@ -46,6 +46,7 @@ import com.ridgebotics.ridgescout.ui.views.CustomSpinnerView;
|
||||
import com.ridgebotics.ridgescout.ui.views.TallyCounterView;
|
||||
import com.ridgebotics.ridgescout.utility.DataManager;
|
||||
import com.ridgebotics.ridgescout.utility.FileEditor;
|
||||
import com.ridgebotics.ridgescout.utility.SettingsManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -92,11 +93,13 @@ public class SettingsFragment extends Fragment {
|
||||
|
||||
manager.addItem(new CheckboxSettingsItem(CustomEventsKey, "Custom Events"));
|
||||
|
||||
StringSettingsItem FTPServer = new StringSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPServer, "FTP Server (Sync)");
|
||||
StringSettingsItem FTPKey = new StringSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPKey, "Sync Key");
|
||||
manager.addItem(FTPKey);
|
||||
StringSettingsItem FTPServer = new StringSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPServer, "Sync Server (Sync)");
|
||||
manager.addItem(FTPServer);
|
||||
CheckboxSettingsItem FTPSendMetaFiles = new CheckboxSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPSendMetaFiles, "Sync meta files");
|
||||
CheckboxSettingsItem FTPSendMetaFiles = new CheckboxSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPSendMetaFiles, "⚠ Send meta files");
|
||||
manager.addItem(FTPSendMetaFiles);
|
||||
CheckboxSettingsItem FTPEnabled = new CheckboxSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPEnabled, "FTP Enabled", FTPServer, FTPSendMetaFiles);
|
||||
CheckboxSettingsItem FTPEnabled = new CheckboxSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPEnabled, "FTP Enabled", FTPServer, FTPKey, FTPSendMetaFiles);
|
||||
manager.addItem(FTPEnabled);
|
||||
|
||||
manager.addItem(new CheckboxSettingsItem(WifiModeKey, "Wifi Mode", FTPEnabled));
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
// This is now deprecated
|
||||
// Class to synchronise data over FTP.
|
||||
public class FTPSync extends Thread {
|
||||
public static final String remoteBasePath = "/RidgeScout/";
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
package com.ridgebotics.ridgescout.ui.transfer;
|
||||
|
||||
import static com.ridgebotics.ridgescout.utility.FileEditor.baseDir;
|
||||
|
||||
import com.ridgebotics.ridgescout.utility.AlertManager;
|
||||
import com.ridgebotics.ridgescout.utility.BuiltByteParser;
|
||||
import com.ridgebotics.ridgescout.utility.ByteBuilder;
|
||||
import com.ridgebotics.ridgescout.utility.FileEditor;
|
||||
import com.ridgebotics.ridgescout.utility.HttpGetFile;
|
||||
import com.ridgebotics.ridgescout.utility.HttpPutFile;
|
||||
import com.ridgebotics.ridgescout.utility.RequestTask;
|
||||
import com.ridgebotics.ridgescout.utility.SettingsManager;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
// This is now deprsicated
|
||||
// Class to syncronise data over FTP.
|
||||
public class HttpSync extends Thread {
|
||||
public static final String timestampsFilename = "timestamps";
|
||||
|
||||
private static final long millisTolerance = 1000;
|
||||
|
||||
private boolean after(Date a, Date b){
|
||||
return a.getTime() - b.getTime() > millisTolerance;
|
||||
}
|
||||
|
||||
|
||||
public interface onResult {
|
||||
void onResult(boolean error, int upCount, int downCount);
|
||||
}
|
||||
public interface UpdateIndicator {
|
||||
void onText(String text);
|
||||
}
|
||||
private static UpdateIndicator updateIndicator = text -> {};
|
||||
public static String text = "";
|
||||
private static void setUpdateIndicator(String m_text){
|
||||
text = m_text;
|
||||
updateIndicator.onText(m_text);
|
||||
}
|
||||
public static void setOnUpdateIndicator(UpdateIndicator m_updateIndicator){
|
||||
updateIndicator = m_updateIndicator;
|
||||
}
|
||||
|
||||
private static onResult onResult = (error, upCount, downCount) -> {};
|
||||
public static void setOnResult(onResult result){
|
||||
onResult = result;
|
||||
}
|
||||
|
||||
private static boolean isRunning = false;
|
||||
public static boolean getIsRunning(){return isRunning;}
|
||||
|
||||
public static void sync(){
|
||||
// DataManager.reload_event();
|
||||
HttpSync sync = new HttpSync();
|
||||
|
||||
sync.start();
|
||||
}
|
||||
|
||||
private int upCount = 0;
|
||||
private int downCount = 0;
|
||||
|
||||
private class TransferFile {
|
||||
public String filename;
|
||||
public Date updated;
|
||||
public String checksum;
|
||||
}
|
||||
|
||||
private List<TransferFile> localFiles = new ArrayList<>();
|
||||
private List<TransferFile> remoteFiles = new ArrayList<>();
|
||||
|
||||
|
||||
|
||||
private void await() {
|
||||
while(!runningRequest.get()) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AtomicBoolean runningRequest = new AtomicBoolean(false);
|
||||
|
||||
public void run() {
|
||||
isRunning = true;
|
||||
boolean sendMetaFiles = SettingsManager.getFTPSendMetaFiles();
|
||||
String serverIP = SettingsManager.getFTPServer();
|
||||
String serverKey = SettingsManager.getFTPKey();
|
||||
|
||||
setUpdateIndicator("Getting Metadata...");
|
||||
|
||||
// Load metadata from server
|
||||
getRemoteFileMetadata(serverIP, serverKey);
|
||||
|
||||
if(!isRunning){
|
||||
setUpdateIndicator("Error Connecting");
|
||||
onResult.onResult(true, upCount, downCount);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
getLocalFileMetadata();
|
||||
|
||||
|
||||
|
||||
|
||||
// Wait for metadata request to finish
|
||||
|
||||
setUpdateIndicator("Uploading 0%");
|
||||
|
||||
for(int i = 0; i < localFiles.size(); i++){
|
||||
TransferFile localFile = localFiles.get(i);
|
||||
|
||||
System.out.print("LocalFile: " + localFile.filename + ", " + localFile.checksum + ", " + localFile.updated + ": ");
|
||||
TransferFile remoteFile = findInFileArray(remoteFiles, localFile.filename);
|
||||
|
||||
|
||||
|
||||
if(
|
||||
(
|
||||
sendMetaFiles || !(
|
||||
localFile.filename.endsWith(".fields")
|
||||
)
|
||||
|
||||
)
|
||||
&& (remoteFile == null ||
|
||||
(
|
||||
!Objects.equals(localFile.checksum, remoteFile.checksum) &&
|
||||
after(localFile.updated, remoteFile.updated)
|
||||
)
|
||||
)) {
|
||||
uploadFile(localFile, serverIP, serverKey);
|
||||
// await();
|
||||
System.out.println("Uploaded");
|
||||
upCount++;
|
||||
}else {
|
||||
System.out.println("Did not upload");
|
||||
}
|
||||
|
||||
setUpdateIndicator("Uploading " + (Math.floor((double) (i * 1000) / localFiles.size()) / 10) + "%");
|
||||
}
|
||||
|
||||
setUpdateIndicator("Downloading 0%");
|
||||
|
||||
for(int i = 0; i < remoteFiles.size(); i++){
|
||||
TransferFile remoteFile = remoteFiles.get(i);
|
||||
|
||||
System.out.print("RemoteFile: " + remoteFile.filename + ", " + remoteFile.checksum + ", " + remoteFile.updated + ": ");
|
||||
TransferFile localFile = findInFileArray(localFiles, remoteFile.filename);
|
||||
|
||||
if(localFile == null ||
|
||||
(
|
||||
!Objects.equals(localFile.checksum, remoteFile.checksum) &&
|
||||
after(remoteFile.updated, localFile.updated) &&
|
||||
!localFile.updated.equals(remoteFile.updated)
|
||||
)
|
||||
) {
|
||||
downloadFile(remoteFile, serverIP);
|
||||
// await();
|
||||
System.out.println("Downloaded");
|
||||
downCount++;
|
||||
} else {
|
||||
System.out.println("Did not download");
|
||||
}
|
||||
|
||||
|
||||
setUpdateIndicator("Downloading " + (Math.floor((double) (i * 1000) / remoteFiles.size()) / 10) + "%");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
setUpdateIndicator("Finished, " + upCount + " Up, " + downCount + " Down");
|
||||
|
||||
|
||||
onResult.onResult(false, upCount, downCount);
|
||||
isRunning = false;
|
||||
}
|
||||
|
||||
|
||||
private TransferFile findInFileArray(List<TransferFile> files, String filename){
|
||||
for(TransferFile file : files) {
|
||||
if(file.filename.equals(filename))
|
||||
return file;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Date getLocalFileUtcTimestamp(File file) {
|
||||
return new Date(file.lastModified());
|
||||
}
|
||||
|
||||
public static String getSHA256Hash(String filePath) throws IOException, NoSuchAlgorithmException {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
FileInputStream fis = new FileInputStream(filePath);
|
||||
byte[] byteArray = new byte[1024];
|
||||
int bytesCount = 0;
|
||||
|
||||
while ((bytesCount = fis.read(byteArray)) != -1) {
|
||||
digest.update(byteArray, 0, bytesCount);
|
||||
}
|
||||
fis.close();
|
||||
|
||||
byte[] bytes = digest.digest();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void getLocalFileMetadata() {
|
||||
File localDir = new File(baseDir);
|
||||
File[] localFileNames = localDir.listFiles();
|
||||
|
||||
assert localFileNames != null;
|
||||
for (int i = 0; i < localFileNames.length; i++) {
|
||||
File file = localFileNames[i];
|
||||
|
||||
if(file.isDirectory()) continue;
|
||||
// Remove timestamts file
|
||||
if(file.getName().equals(timestampsFilename)) continue;
|
||||
|
||||
TransferFile tf = new TransferFile();
|
||||
tf.filename = file.getName();
|
||||
tf.updated = getLocalFileUtcTimestamp(file);
|
||||
try {
|
||||
tf.checksum = getSHA256Hash(file.getPath());
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
localFiles.add(tf);
|
||||
}
|
||||
}
|
||||
|
||||
// Send request to server and retrieve metadata
|
||||
private void getRemoteFileMetadata(String serverURL, String serverKey) {
|
||||
final RequestTask rq = new RequestTask();
|
||||
runningRequest.set(false);
|
||||
rq.onResult(metadata -> {
|
||||
try {
|
||||
JSONObject j = new JSONObject(metadata);
|
||||
for (Iterator<String> it = j.keys(); it.hasNext(); ) {
|
||||
String key = it.next();
|
||||
|
||||
JSONObject obj = j.getJSONObject(key);
|
||||
|
||||
TransferFile tf = new TransferFile();
|
||||
tf.filename = key;
|
||||
tf.updated = new Date(Long.parseLong(obj.getString("modified")));
|
||||
tf.checksum = obj.getString("sha256");
|
||||
|
||||
remoteFiles.add(tf);
|
||||
}
|
||||
}catch(JSONException | NullPointerException e ) {
|
||||
AlertManager.error(e);
|
||||
isRunning = false;
|
||||
}
|
||||
runningRequest.set(true);
|
||||
return null;
|
||||
});
|
||||
rq.execute((serverURL + "/api/metadata"), "api_key: " + serverKey);
|
||||
await();
|
||||
}
|
||||
|
||||
|
||||
// private boolean setTimestamps(Map<String, Date> timestamps){
|
||||
// try {
|
||||
// ByteBuilder bb = new ByteBuilder();
|
||||
// String[] filenames = timestamps.keySet().toArray(new String[0]);
|
||||
//
|
||||
// for(int i = 0; i < filenames.length; i++){
|
||||
// bb.addString(filenames[i]);
|
||||
// bb.addLong(timestamps.get(filenames[i]).getTime());
|
||||
// }
|
||||
//
|
||||
// FileEditor.writeFile(timestampsFilename, bb.build());
|
||||
//
|
||||
// uploadFile(new File(baseDir + timestampsFilename));
|
||||
// return true;
|
||||
// } catch (ByteBuilder.buildingException | IOException e) {
|
||||
// AlertManager.error("Failed Syncing!", e);
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private Map<String, Date> getTimestamps() {
|
||||
// try {
|
||||
// downloadFile(timestampsFilename, new File(baseDir + timestampsFilename));
|
||||
//
|
||||
// byte[] data = FileEditor.readFile(timestampsFilename);
|
||||
//
|
||||
// if(data == null || data.length == 0)
|
||||
// return new HashMap<>();
|
||||
//
|
||||
// BuiltByteParser bbp = new BuiltByteParser(data);
|
||||
// List<BuiltByteParser.parsedObject> pa = bbp.parse();
|
||||
//
|
||||
// Map<String, Date> output = new HashMap<>();
|
||||
// for(int i = 0; i < pa.size(); i+=2){
|
||||
//// System.out.println((long) pa.get(i).get());
|
||||
// output.put(
|
||||
// (String) pa.get(i).get(),
|
||||
// new Date((long) pa.get(i+1).get())
|
||||
// );
|
||||
// }
|
||||
// return output;
|
||||
//
|
||||
// }catch (IOException | BuiltByteParser.byteParsingExeption e){
|
||||
// AlertManager.error("Failed Syncing!", e);
|
||||
// return new HashMap<>();
|
||||
// }
|
||||
// }
|
||||
void uploadFile(TransferFile tf, String serverURL, String apiKey) {
|
||||
runningRequest.set(false);
|
||||
HttpPutFile uploadTask = new HttpPutFile(serverURL + "/api/" + tf.filename, new File(baseDir + tf.filename), new HttpPutFile.UploadCallback() {
|
||||
@Override
|
||||
public void onResult(String error) {
|
||||
if(error != null)
|
||||
AlertManager.error(error);
|
||||
runningRequest.set(true);
|
||||
}
|
||||
}, new String[]{
|
||||
"api_key: " + apiKey,
|
||||
("modified: " + tf.updated.getTime())
|
||||
}); // Pass auth token if needed
|
||||
|
||||
uploadTask.execute();
|
||||
await();
|
||||
}
|
||||
|
||||
|
||||
private void setLocalFileTimestamp(File file, Date date) {
|
||||
file.setLastModified(date.getTime());
|
||||
}
|
||||
void downloadFile(TransferFile tf, String serverURL) {
|
||||
runningRequest.set(false);
|
||||
File f = new File(baseDir + tf.filename);
|
||||
HttpGetFile uploadTask = new HttpGetFile(serverURL + "/api/" + tf.filename, f, new HttpGetFile.DownloadCallback() {
|
||||
@Override
|
||||
public void onResult(String error) {
|
||||
if(error != null)
|
||||
AlertManager.error(error);
|
||||
else
|
||||
setLocalFileTimestamp(f, tf.updated);
|
||||
runningRequest.set(true);
|
||||
|
||||
}
|
||||
}); // Pass auth token if needed
|
||||
|
||||
uploadTask.execute();
|
||||
await();
|
||||
}
|
||||
}
|
||||
@@ -65,13 +65,13 @@ public class TransferFragment extends Fragment {
|
||||
|
||||
binding.SyncButton.setOnClickListener(v -> {
|
||||
binding.SyncButton.setEnabled(false);
|
||||
FTPSync.sync();
|
||||
HttpSync.sync();
|
||||
});
|
||||
|
||||
if(FTPSync.getIsRunning())
|
||||
if(HttpSync.getIsRunning())
|
||||
binding.SyncButton.setEnabled(false);
|
||||
|
||||
FTPSync.setOnResult((error, upcount, downcount) -> {
|
||||
HttpSync.setOnResult((error, upcount, downcount) -> {
|
||||
if (getActivity() != null)
|
||||
getActivity().runOnUiThread(() -> {
|
||||
binding.SyncButton.setEnabled(true);
|
||||
@@ -79,8 +79,8 @@ public class TransferFragment extends Fragment {
|
||||
});
|
||||
});
|
||||
|
||||
binding.syncIndicator.setText(FTPSync.text);
|
||||
FTPSync.setOnUpdateIndicator(text -> {if(getActivity() != null) getActivity().runOnUiThread(() -> binding.syncIndicator.setText(text));});
|
||||
binding.syncIndicator.setText(HttpSync.text);
|
||||
HttpSync.setOnUpdateIndicator(text -> {if(getActivity() != null) getActivity().runOnUiThread(() -> binding.syncIndicator.setText(text));});
|
||||
|
||||
if(evcode.equals("unset")){
|
||||
binding.noEventError.setVisibility(View.VISIBLE);
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
package com.ridgebotics.ridgescout.utility;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
public class HttpGetFile extends AsyncTask<Void, Integer, File> {
|
||||
|
||||
public interface DownloadCallback {
|
||||
void onResult(String error);
|
||||
}
|
||||
|
||||
private String downloadUrl;
|
||||
private File destinationFile;
|
||||
private DownloadCallback callback;
|
||||
private String errorMessage;
|
||||
public HttpGetFile(String downloadUrl, File destinationFile, DownloadCallback callback) {
|
||||
this.downloadUrl = downloadUrl;
|
||||
this.destinationFile = destinationFile;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected File doInBackground(Void... voids) {
|
||||
HttpURLConnection connection = null;
|
||||
InputStream inputStream = null;
|
||||
FileOutputStream outputStream = null;
|
||||
|
||||
try {
|
||||
URL url = new URL(downloadUrl);
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
|
||||
// Configure connection for GET request
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setDoInput(true);
|
||||
connection.setConnectTimeout(30000); // 30 seconds
|
||||
connection.setReadTimeout(60000); // 60 seconds
|
||||
|
||||
connection.connect();
|
||||
|
||||
// Check response code
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode < 200 || responseCode >= 300) {
|
||||
String errorResponse = readErrorResponse(connection);
|
||||
errorMessage = "Download failed. Response code: " + responseCode +
|
||||
(errorResponse != null ? ". Error: " + errorResponse : "");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get file size for progress tracking
|
||||
long fileSize = connection.getContentLengthLong();
|
||||
if (fileSize == -1) {
|
||||
fileSize = connection.getContentLength(); // fallback for older API
|
||||
}
|
||||
|
||||
inputStream = connection.getInputStream();
|
||||
|
||||
// Create destination file and directories if needed
|
||||
if (destinationFile.getParentFile() != null && !destinationFile.getParentFile().exists()) {
|
||||
if (!destinationFile.getParentFile().mkdirs()) {
|
||||
errorMessage = "Failed to create destination directory: " + destinationFile.getParentFile().getAbsolutePath();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
outputStream = new FileOutputStream(destinationFile);
|
||||
|
||||
byte[] buffer = new byte[8192];
|
||||
long downloadedBytes = 0;
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
if (isCancelled()) {
|
||||
deletePartialFile();
|
||||
return null;
|
||||
}
|
||||
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
downloadedBytes += bytesRead;
|
||||
|
||||
// Update progress if file size is known
|
||||
if (fileSize > 0) {
|
||||
int progress = (int) ((downloadedBytes * 100) / fileSize);
|
||||
publishProgress(progress);
|
||||
}
|
||||
}
|
||||
|
||||
outputStream.flush();
|
||||
// Log.d(TAG, "Download successful. File saved to: " + destinationFile.getAbsolutePath());
|
||||
return destinationFile;
|
||||
|
||||
} catch (Exception e) {
|
||||
AlertManager.error(e);
|
||||
errorMessage = "Download error: " + e.getMessage();
|
||||
// Log.e(TAG, errorMessage, e);
|
||||
deletePartialFile();
|
||||
return null;
|
||||
} finally {
|
||||
closeResources(inputStream, outputStream, connection);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(File result) {
|
||||
if (callback != null) {
|
||||
callback.onResult(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
deletePartialFile();
|
||||
if (callback != null) {
|
||||
callback.onResult("Download cancelled");
|
||||
}
|
||||
}
|
||||
|
||||
private String readErrorResponse(HttpURLConnection connection) {
|
||||
try {
|
||||
InputStream errorStream = connection.getErrorStream();
|
||||
if (errorStream != null) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream));
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
reader.close();
|
||||
return response.toString();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
AlertManager.error(e);
|
||||
// Log.e(TAG, "Error reading error response", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void deletePartialFile() {
|
||||
if (destinationFile != null && destinationFile.exists()) {
|
||||
if (destinationFile.delete()) {
|
||||
// Log.d(TAG, "Partial download file deleted");
|
||||
} else {
|
||||
// Log.w(TAG, "Failed to delete partial download file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void closeResources(InputStream inputStream, OutputStream outputStream, HttpURLConnection connection) {
|
||||
try {
|
||||
if (inputStream != null) inputStream.close();
|
||||
} catch (IOException e) {
|
||||
AlertManager.error(e);
|
||||
// Log.e(TAG, "Error closing input stream", e);
|
||||
}
|
||||
|
||||
try {
|
||||
if (outputStream != null) outputStream.close();
|
||||
} catch (IOException e) {
|
||||
AlertManager.error(e);
|
||||
// Log.e(TAG, "Error closing output stream", e);
|
||||
}
|
||||
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package com.ridgebotics.ridgescout.utility;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
//import android.util.Log;
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
public class HttpPutFile extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
// private static final String TAG = "FileUploadTask";
|
||||
|
||||
public interface UploadCallback {
|
||||
void onResult(String error);
|
||||
}
|
||||
|
||||
private String uploadUrl;
|
||||
private File fileToUpload;
|
||||
private UploadCallback callback;
|
||||
private String errorMessage;
|
||||
private String[] headers;
|
||||
|
||||
public HttpPutFile(String uploadUrl, File fileToUpload, UploadCallback callback, String[] headers) {
|
||||
this.uploadUrl = uploadUrl;
|
||||
this.fileToUpload = fileToUpload;
|
||||
this.callback = callback;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
HttpURLConnection connection = null;
|
||||
InputStream fileInputStream = null;
|
||||
OutputStream outputStream = null;
|
||||
|
||||
try {
|
||||
if (!fileToUpload.exists()) {
|
||||
errorMessage = "File does not exist: " + fileToUpload.getAbsolutePath();
|
||||
return false;
|
||||
}
|
||||
|
||||
URL url = new URL(uploadUrl);
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
|
||||
// Configure connection for PUT request
|
||||
connection.setRequestMethod("PUT");
|
||||
connection.setDoOutput(true);
|
||||
connection.setDoInput(true);
|
||||
connection.setUseCaches(false);
|
||||
connection.setRequestProperty("Content-Type", "application/octet-stream");
|
||||
connection.setRequestProperty("Content-Length", String.valueOf(fileToUpload.length()));
|
||||
connection.setConnectTimeout(30000); // 30 seconds
|
||||
connection.setReadTimeout(60000); // 60 seconds
|
||||
|
||||
for(int i = 0; i < headers.length; i++){
|
||||
String[] split = headers[i].split(": ");
|
||||
connection.setRequestProperty(split[0], split[1]);
|
||||
}
|
||||
connection.connect();
|
||||
|
||||
outputStream = connection.getOutputStream();
|
||||
fileInputStream = new FileInputStream(fileToUpload);
|
||||
|
||||
byte[] buffer = new byte[8192];
|
||||
long totalBytes = fileToUpload.length();
|
||||
long uploadedBytes = 0;
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = fileInputStream.read(buffer)) != -1) {
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
uploadedBytes += bytesRead;
|
||||
|
||||
// Update progress
|
||||
int progress = (int) ((uploadedBytes * 100) / totalBytes);
|
||||
publishProgress(progress);
|
||||
}
|
||||
|
||||
outputStream.flush();
|
||||
|
||||
// Check response code
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode >= 200 && responseCode < 300) {
|
||||
// Log.d(TAG, "Upload successful. Response code: " + responseCode);
|
||||
return true;
|
||||
} else {
|
||||
// Read error response if available
|
||||
String errorResponse = readErrorResponse(connection);
|
||||
errorMessage = "Upload failed. Response code: " + responseCode +
|
||||
(errorResponse != null ? ". Error: " + errorResponse : "");
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
AlertManager.error(e);
|
||||
errorMessage = "Upload error: " + e.getMessage();
|
||||
// Log.e(TAG, errorMessage, e);
|
||||
return false;
|
||||
} finally {
|
||||
closeResources(fileInputStream, outputStream, connection);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean success) {
|
||||
if (callback != null) {
|
||||
callback.onResult(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
if (callback != null) {
|
||||
callback.onResult("Upload cancelled");
|
||||
}
|
||||
}
|
||||
|
||||
private String readErrorResponse(HttpURLConnection connection) {
|
||||
try {
|
||||
InputStream errorStream = connection.getErrorStream();
|
||||
if (errorStream != null) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream));
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
reader.close();
|
||||
return response.toString();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
AlertManager.error(e);
|
||||
// Log.e(TAG, "Error reading error response", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void closeResources(InputStream inputStream, OutputStream outputStream, HttpURLConnection connection) {
|
||||
try {
|
||||
if (inputStream != null) inputStream.close();
|
||||
} catch (IOException e) {
|
||||
AlertManager.error(e);
|
||||
// Log.e(TAG, "Error closing input stream", e);
|
||||
}
|
||||
|
||||
try {
|
||||
if (outputStream != null) outputStream.close();
|
||||
} catch (IOException e) {
|
||||
AlertManager.error(e);
|
||||
// Log.e(TAG, "Error closing output stream", e);
|
||||
}
|
||||
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ public class SettingsManager {
|
||||
public static final String BtUUIDKey = "bt_uuid";
|
||||
public static final String FTPEnabled = "ftp_enabled";
|
||||
public static final String FTPServer = "ftp_server";
|
||||
public static final String FTPKey = "ftp_key";
|
||||
public static final String FTPSendMetaFiles = "ftp_send_meta_files";
|
||||
|
||||
public static final String EnableQuickAllianceChangeKey = "enable_quick_alliance_change";
|
||||
@@ -53,7 +54,8 @@ public class SettingsManager {
|
||||
hm.put(TeamsDataModeKey, 0);
|
||||
hm.put(BtUUIDKey, UUID.randomUUID().toString());
|
||||
hm.put(FTPEnabled, false);
|
||||
hm.put(FTPServer, "0.0.0.0");
|
||||
hm.put(FTPServer, "http://127.0.0.1:8080");
|
||||
hm.put(FTPKey, "5uper_5ecure_k3y");
|
||||
hm.put(FTPSendMetaFiles, false);
|
||||
hm.put(EnableQuickAllianceChangeKey, false);
|
||||
hm.put(CustomEventsKey, false);
|
||||
@@ -131,6 +133,9 @@ public class SettingsManager {
|
||||
public static String getFTPServer(){return prefs.getString( FTPServer, (String) defaults.get(FTPServer));}
|
||||
public static void setFTPServer(String str){ getEditor().putString( FTPServer,str).apply();}
|
||||
|
||||
public static String getFTPKey(){return prefs.getString( FTPKey, (String) defaults.get(FTPKey));}
|
||||
public static void setFTPKey(String str){ getEditor().putString( FTPKey,str).apply();}
|
||||
|
||||
public static boolean getFTPSendMetaFiles(){return prefs.getBoolean(FTPSendMetaFiles, (boolean) defaults.get(FTPSendMetaFiles));}
|
||||
public static void setFTPSendMetaFiles(boolean bool){getEditor().putBoolean(FTPSendMetaFiles,bool).apply();}
|
||||
|
||||
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
from ast import mod
|
||||
import os
|
||||
import json
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
from bottle import Bottle, run, get, put, static_file, response, request,HTTPResponse
|
||||
from random import SystemRandom
|
||||
|
||||
from utils import *
|
||||
|
||||
app = Bottle()
|
||||
|
||||
file_metadata = {}
|
||||
|
||||
def save_metadata():
|
||||
global file_metadata
|
||||
write(METADATA_PATH4, json.dumps(file_metadata))
|
||||
|
||||
def load_metadata():
|
||||
global file_metadata
|
||||
data = read(METADATA_PATH4)
|
||||
if data is not None:
|
||||
file_metadata = json.loads(data)
|
||||
|
||||
api_key = None
|
||||
cryptogen = SystemRandom()
|
||||
|
||||
def aquire_key():
|
||||
global api_key
|
||||
global cryptogen
|
||||
|
||||
if api_key is None:
|
||||
try:
|
||||
api_key = read(API_KEY_PATH).decode("utf-8").strip()
|
||||
except:
|
||||
ran = cryptogen.randrange(10**80)
|
||||
api_key = "%064x" % ran
|
||||
write(API_KEY_PATH, api_key)
|
||||
|
||||
@app.route('/')
|
||||
def list_html():
|
||||
global file_metadata
|
||||
load_metadata()
|
||||
|
||||
content = '<html><body><table><tr>'
|
||||
for heading in ['File', 'Size', 'Modified', 'Sha256']:
|
||||
content += f'<th>{heading}</th>'
|
||||
content += "</tr>"
|
||||
|
||||
print(file_metadata)
|
||||
|
||||
for filename in file_metadata.keys():
|
||||
content += "<tr>"
|
||||
content += f'<td><a href="/api/{filename}">{filename}</a></td>'
|
||||
content += f'<td>{file_metadata[filename]["size"]}B</td>'
|
||||
content += f'<td>{file_metadata[filename]["modified"]}</td>'
|
||||
content += f'<td>{file_metadata[filename]["sha256"]}</td>'
|
||||
content += "</tr>"
|
||||
|
||||
content += '</table></body></html>'
|
||||
|
||||
return content
|
||||
|
||||
|
||||
@app.route('/api/metadata')
|
||||
def metadata():
|
||||
global file_metadata
|
||||
load_metadata()
|
||||
response.content_type = 'application/json'
|
||||
return json.dumps(file_metadata)
|
||||
|
||||
|
||||
@app.route('/api/<filename>', method='PUT')
|
||||
def upload(filename):
|
||||
global api_key
|
||||
try:
|
||||
sentkey = request.headers[API_KEY_HEADER]
|
||||
if sentkey != api_key:
|
||||
return HTTPResponse(status=403, body=f"Invalid Key")
|
||||
except:
|
||||
return HTTPResponse(status=403, body="You must specify an 'api_key' header")
|
||||
|
||||
|
||||
global file_metadata
|
||||
load_metadata()
|
||||
|
||||
data = request.body.read()
|
||||
|
||||
try:
|
||||
modified = request.headers[MODIFIED_HEADER]
|
||||
except:
|
||||
modified = (datetime.now() - datetime(1970, 1, 1)).total_seconds()
|
||||
|
||||
sha256_hash = hashlib.sha256()
|
||||
sha256_hash.update(data)
|
||||
|
||||
file_metadata[filename] = {
|
||||
'size': len(data),
|
||||
'modified': modified,
|
||||
'sha256': sha256_hash.hexdigest()
|
||||
}
|
||||
|
||||
save_metadata()
|
||||
|
||||
|
||||
|
||||
write(os.path.join(DATA_ROOT, filename), data)
|
||||
# save_metadata()
|
||||
# response.content_type = 'application/json'
|
||||
# return json.dumps(file_metadata)
|
||||
|
||||
|
||||
@app.route('/api/<filename>')
|
||||
def download(filename):
|
||||
|
||||
data = read(os.path.join(DATA_ROOT, filename))
|
||||
|
||||
if data is not None:
|
||||
response.content_type = 'application/octet-stream'
|
||||
return data
|
||||
else:
|
||||
HTTPResponse(status=404, body="File not found")
|
||||
|
||||
if __name__ == '__main__':
|
||||
mkdir(DATA_ROOT)
|
||||
aquire_key()
|
||||
app.run(host='0.0.0.0', port=8080)
|
||||
@@ -0,0 +1,2 @@
|
||||
bottle
|
||||
random
|
||||
@@ -0,0 +1,40 @@
|
||||
import os
|
||||
|
||||
ROOT = os.path.dirname(__file__)
|
||||
DATA_ROOT = os.path.join(os.path.dirname(__file__), 'server_data')
|
||||
|
||||
METADATA_PATH4 = os.path.join(ROOT, 'metadata.json')
|
||||
API_KEY_PATH = os.path.join(ROOT, 'api_key.txt')
|
||||
|
||||
MODIFIED_HEADER = 'modified'
|
||||
API_KEY_HEADER = 'api_key'
|
||||
|
||||
def mkdir(path):
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
|
||||
def ls(path):
|
||||
try:
|
||||
return os.listdir(path)
|
||||
except:
|
||||
return []
|
||||
|
||||
def read(path):
|
||||
if not os.path.exists(path):
|
||||
return None
|
||||
try:
|
||||
with open(path, 'rb') as f:
|
||||
return f.read()
|
||||
except Exception as e:
|
||||
print(f"Error reading file {path}: {e}")
|
||||
return None
|
||||
|
||||
def write(path, data):
|
||||
if not os.path.exists(path):
|
||||
with open(path, mode='ab'): pass
|
||||
|
||||
if isinstance(data, str):
|
||||
data = str.encode(data)
|
||||
|
||||
with open(path, 'wb') as f:
|
||||
f.write(data)
|
||||
Reference in New Issue
Block a user