mirror of
https://github.com/Team4388/RidgeScout.git
synced 2026-06-09 08:38:03 -06:00
Compare commits
9 Commits
TODO
...
test-branch
| Author | SHA1 | Date | |
|---|---|---|---|
| b41ed2667c | |||
| 5279c085e1 | |||
| 41460fcd7e | |||
| 782fb73050 | |||
| 7e9954d78a | |||
| 65baecac35 | |||
| e278bc10a1 | |||
| 5d727cf359 | |||
| ae147771cb |
@@ -1,3 +1,9 @@
|
||||
# Python server
|
||||
__pycache__/
|
||||
metadata.json
|
||||
api_key.txt
|
||||
server_data/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
### TODO:
|
||||
##### Scouting:
|
||||
- Make an easier way to make game-specific UI elements
|
||||
##### Data Analysis:
|
||||
- Add analysis for the rest of the data types besides Tally
|
||||
- Add a "scout note" system for scouters to contribute to the scouting report?
|
||||
- Make data that has been marked for rescouting not processed in either by team or by type analysis.
|
||||
##### Functionality:
|
||||
- Rewrite FTP transfer to be over HTTP requests
|
||||
- - Potentially using a python server or some other pre-existing file transfer over HTTP
|
||||
- Delete file menu
|
||||
- Make "Sync meta files" option only block uploading fields specifically.
|
||||
##### UI:
|
||||
- Update docs and README.md with new features
|
||||
- Improve file status indicator for scouting
|
||||
- - The autosave timeout can be significantly reduced, so the save indicator is not a necessity
|
||||
- - A new system for the rescout indicator should be used.
|
||||
- Improve UI elements for scouting data by team
|
||||
- Field button in settings overlaps with other elements in some devices
|
||||
|
||||
### In Progress:
|
||||
##### Scouting:
|
||||
##### Data Analysis:
|
||||
##### Functionality:
|
||||
##### UI:
|
||||
|
||||
### Done:
|
||||
##### Scouting:
|
||||
##### Data Analysis:
|
||||
##### Functionality:
|
||||
##### UI:
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.ridgebotics.ridgescout.scoutingData;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.ridgebotics.ridgescout.scoutingData.transfer.TransferType;
|
||||
import com.ridgebotics.ridgescout.types.ScoutingArray;
|
||||
import com.ridgebotics.ridgescout.types.data.RawDataType;
|
||||
@@ -31,15 +33,15 @@ public class ScoutingDataWriter {
|
||||
switch (data[i].getValueType()){
|
||||
case NUM:
|
||||
bb.addInt((int) data[i].forceGetValue());
|
||||
System.out.println("Saved INT: " + data[i].getUUID() + ", ("+ data[i].get() +")");
|
||||
Log.i(ScoutingDataWriter.class.toString(),"Saved INT: " + data[i].getUUID() + ", ("+ data[i].get() +")");
|
||||
break;
|
||||
case STRING:
|
||||
bb.addString((String) data[i].forceGetValue());
|
||||
System.out.println("Saved STR: " + data[i].getUUID() + ", ("+ data[i].get() +")");
|
||||
Log.i(ScoutingDataWriter.class.toString(), "Saved STR: " + data[i].getUUID() + ", ("+ data[i].get() +")");
|
||||
break;
|
||||
case NUMARR:
|
||||
bb.addIntArray((int[]) data[i].forceGetValue());
|
||||
System.out.println("Saved INT Array: " + data[i].getUUID() + ", ("+ Arrays.toString((int[]) data[i].get()) +")");
|
||||
Log.i(ScoutingDataWriter.class.toString(), "Saved INT Array: " + data[i].getUUID() + ", ("+ Arrays.toString((int[]) data[i].get()) +")");
|
||||
}
|
||||
}
|
||||
byte[] bytes = bb.build();
|
||||
@@ -82,17 +84,17 @@ public class ScoutingDataWriter {
|
||||
case 1: // Int
|
||||
rawDataTypes[i] = IntType.newNull(values[version][i].UUID);
|
||||
rawDataTypes[i].forceSetValue(objects.get(i+2).get());
|
||||
System.out.println("Loaded INT: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ rawDataTypes[i].get() +")");
|
||||
Log.i(ParsedScoutingDataResult.class.toString(),"Loaded INT: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ rawDataTypes[i].get() +")");
|
||||
break;
|
||||
case 2: // String
|
||||
rawDataTypes[i] = StringType.newNull(values[version][i].UUID);
|
||||
rawDataTypes[i].forceSetValue(objects.get(i+2).get());
|
||||
System.out.println("Loaded STR: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ rawDataTypes[i].get() +")");
|
||||
Log.i(ParsedScoutingDataResult.class.toString(),"Loaded STR: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ rawDataTypes[i].get() +")");
|
||||
break;
|
||||
case 3: // Int array
|
||||
rawDataTypes[i] = IntArrType.newNull(values[version][i].UUID);
|
||||
rawDataTypes[i].forceSetValue(objects.get(i+2).get());
|
||||
System.out.println("Loaded intARR: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ Arrays.toString((int[]) rawDataTypes[i].get()) +")");
|
||||
Log.i(ParsedScoutingDataResult.class.toString(),"Loaded intARR: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ Arrays.toString((int[]) rawDataTypes[i].get()) +")");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.ridgebotics.ridgescout.types;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.ridgebotics.ridgescout.scoutingData.transfer.CreateTransferType;
|
||||
import com.ridgebotics.ridgescout.scoutingData.transfer.DirectTransferType;
|
||||
import com.ridgebotics.ridgescout.scoutingData.transfer.TransferType;
|
||||
@@ -44,7 +46,7 @@ public class ScoutingArray {
|
||||
}
|
||||
this.array = new_values;
|
||||
version++;
|
||||
System.out.println("Updated to " + version);
|
||||
Log.i(getClass().toString(),"Updated to " + version);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ public abstract class FieldType {
|
||||
public static final int numberType = 251;
|
||||
public static final int checkboxType = 250;
|
||||
public static final int fieldposType = 249;
|
||||
public static final int toggleType = 248;
|
||||
|
||||
public enum inputTypes {
|
||||
SLIDER,
|
||||
@@ -32,7 +33,8 @@ public abstract class FieldType {
|
||||
TALLY,
|
||||
NUMBER,
|
||||
CHECKBOX,
|
||||
FIELDPOS
|
||||
FIELDPOS,
|
||||
TOGGLE;
|
||||
}
|
||||
public String UUID;
|
||||
public String name;
|
||||
@@ -89,8 +91,6 @@ public abstract class FieldType {
|
||||
public abstract void setViewValue(Object value);
|
||||
public abstract RawDataType getViewValue();
|
||||
|
||||
|
||||
|
||||
public abstract void add_individual_view(LinearLayout parent, RawDataType data);
|
||||
public abstract void add_compiled_view(LinearLayout parent, RawDataType[] data);
|
||||
public abstract void add_history_view(LinearLayout parent, RawDataType[] data);
|
||||
|
||||
@@ -65,6 +65,7 @@ public class TallyType extends FieldType {
|
||||
|
||||
public TallyCounterView tally = null;
|
||||
|
||||
|
||||
public View createView(Context context, Function<RawDataType, Integer> onUpdate){
|
||||
tally = new TallyCounterView(context);
|
||||
tally.setOnCountChangedListener(n -> onUpdate.apply(getViewValue()));
|
||||
|
||||
@@ -0,0 +1,368 @@
|
||||
package com.ridgebotics.ridgescout.types.input;
|
||||
|
||||
import static com.ridgebotics.ridgescout.utility.Colors.chart_background;
|
||||
import static com.ridgebotics.ridgescout.utility.Colors.chart_text;
|
||||
import static com.ridgebotics.ridgescout.utility.Colors.dropdown_value_text_1;
|
||||
import static com.ridgebotics.ridgescout.utility.Colors.dropdown_value_text_2;
|
||||
import static com.ridgebotics.ridgescout.utility.Colors.tally_data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TableLayout;
|
||||
import android.widget.TableRow;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.github.mikephil.charting.charts.LineChart;
|
||||
import com.github.mikephil.charting.components.Legend;
|
||||
import com.github.mikephil.charting.data.Entry;
|
||||
import com.github.mikephil.charting.data.LineData;
|
||||
import com.github.mikephil.charting.data.LineDataSet;
|
||||
import com.ridgebotics.ridgescout.types.data.IntType;
|
||||
import com.ridgebotics.ridgescout.types.data.RawDataType;
|
||||
import com.ridgebotics.ridgescout.ui.data.DataProcessing;
|
||||
import com.ridgebotics.ridgescout.ui.views.CandlestickHeader;
|
||||
import com.ridgebotics.ridgescout.ui.views.CandlestickView;
|
||||
import com.ridgebotics.ridgescout.ui.views.TallyCounterView;
|
||||
import com.ridgebotics.ridgescout.utility.BuiltByteParser;
|
||||
import com.ridgebotics.ridgescout.utility.ByteBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ToggleType extends FieldType {
|
||||
public int get_byte_id() {return toggleType;}
|
||||
public inputTypes getInputType(){return inputTypes.TOGGLE;}
|
||||
public RawDataType.valueTypes getValueType(){return RawDataType.valueTypes.NUM;}
|
||||
public Object get_fallback_value(){return 0;}
|
||||
public ToggleType(){}
|
||||
public String get_type_name(){return "Toggle";}
|
||||
public ToggleType(String UUID, String name, String description, int default_value){
|
||||
super(UUID, name, description);
|
||||
this.default_value = default_value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public void encodeData(ByteBuilder bb) throws ByteBuilder.buildingException {
|
||||
bb.addInt((int) default_value);
|
||||
}
|
||||
public void decodeData(ArrayList<BuiltByteParser.parsedObject> objects) {
|
||||
default_value = objects.get(0).get();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public TallyCounterView tally = null;
|
||||
|
||||
|
||||
public View createView(Context context, Function<RawDataType, Integer> onUpdate){
|
||||
tally = new TallyCounterView(context);
|
||||
tally.setOnCountChangedListener(n -> onUpdate.apply(getViewValue()));
|
||||
|
||||
setViewValue(default_value);
|
||||
|
||||
return tally;
|
||||
|
||||
}
|
||||
|
||||
public void setViewValue(Object value) {
|
||||
if(tally == null) return;
|
||||
if(IntType.isNull((int)value)){
|
||||
nullify();
|
||||
return;
|
||||
}
|
||||
|
||||
isBlank = false;
|
||||
tally.setVisibility(View.VISIBLE);
|
||||
tally.setValue((int) value);
|
||||
}
|
||||
public void nullify(){
|
||||
isBlank = true;
|
||||
tally.setVisibility(View.GONE);
|
||||
}
|
||||
public RawDataType getViewValue(){
|
||||
if(tally == null) return null;
|
||||
if(tally.getVisibility() == View.GONE) return IntType.newNull(name);
|
||||
return new IntType(name, tally.getValue());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public void add_individual_view(LinearLayout parent, RawDataType data){
|
||||
if(data.isNull()) return;
|
||||
|
||||
TextView tv = new TextView(parent.getContext());
|
||||
tv.setLayoutParams(new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
));
|
||||
tv.setGravity(Gravity.CENTER_HORIZONTAL);
|
||||
tv.setText(String.valueOf((int) data.get()));
|
||||
tv.setTextSize(24);
|
||||
parent.addView(tv);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private static float calculateMean(int[] data) {
|
||||
float sum = 0;
|
||||
for (int value : data) {
|
||||
sum += (float) value;
|
||||
}
|
||||
return sum / data.length;
|
||||
}
|
||||
|
||||
private static float calculateStandardDeviation(int[] data, float mean) {
|
||||
float sum = 0;
|
||||
for (int value : data) {
|
||||
sum += (float) Math.pow((float) value - mean, 2);
|
||||
}
|
||||
return (float) Math.sqrt(sum / (data.length - 1));
|
||||
}
|
||||
|
||||
private static List<Entry> generateNormalDistribution(float mean, float stdDev, int count, int scale) {
|
||||
List<Entry> entries = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
float y = (float) ((1 / (stdDev * Math.sqrt(2 * Math.PI)))
|
||||
* Math.exp(-0.5 * Math.pow(((float) i - mean) / stdDev, 2)));
|
||||
entries.add(new Entry((float) i, y*scale)); // Scale y for visibility
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
private static int findMin(RawDataType[] data){
|
||||
int min = (int)data[0].get();
|
||||
for(int i = 1; i < data.length; i++)
|
||||
if((int)data[i].get() < min)
|
||||
min = (int)data[i].get();
|
||||
return min;
|
||||
}
|
||||
|
||||
private static int findMax(RawDataType[] data){
|
||||
int max = (int)data[0].get();
|
||||
for(int i = 1; i < data.length; i++)
|
||||
if((int)data[i].get() > max)
|
||||
max = (int)data[i].get();
|
||||
return max;
|
||||
}
|
||||
|
||||
public void add_compiled_view(LinearLayout parent, RawDataType[] data){
|
||||
LineChart chart = new LineChart(parent.getContext());
|
||||
FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
layout.height = 350;
|
||||
chart.setLayoutParams(layout);
|
||||
chart.setBackgroundColor(0xff252025);
|
||||
|
||||
int min = findMin(data);
|
||||
int max = findMax(data);
|
||||
|
||||
int[] values = new int[max-min+1];
|
||||
|
||||
for (int i = 0; i < data.length; i++)
|
||||
if(data[i] != null && data[i].isNull())
|
||||
values[(int) data[i].get()-min]++;
|
||||
|
||||
|
||||
ArrayList<Integer> mean_temp = new ArrayList<>();
|
||||
for (int i = 0; i < data.length; i++)
|
||||
if((int)data[i].get() != 0)
|
||||
mean_temp.add((int) data[i].get());
|
||||
|
||||
int[] mean_vals = mean_temp.stream().mapToInt(Integer::intValue).toArray();
|
||||
|
||||
List<Entry> entries = new ArrayList<>();
|
||||
for (int i = 0; i < values.length; i++)
|
||||
entries.add(new Entry(i, values[i]));
|
||||
|
||||
|
||||
LineDataSet dataSet = new LineDataSet(entries, name);
|
||||
dataSet.setColor(tally_data);
|
||||
dataSet.setValueTextColor(dropdown_value_text_1);
|
||||
dataSet.setDrawCircles(false);
|
||||
dataSet.setDrawValues(false);
|
||||
|
||||
|
||||
|
||||
// Calculate mean and standard deviation
|
||||
float mean = calculateMean(mean_vals);
|
||||
float stdDev = calculateStandardDeviation(mean_vals, mean);
|
||||
|
||||
// Generate normal distribution curve
|
||||
List<Entry> normalDistEntries = generateNormalDistribution(mean-min, stdDev, max-min+1, (max-min)/data.length);
|
||||
|
||||
|
||||
LineDataSet normalDistSet = new LineDataSet(normalDistEntries, "Normal Distribution");
|
||||
normalDistSet.setColor(dropdown_value_text_2);
|
||||
normalDistSet.setDrawCircles(false);
|
||||
normalDistSet.setDrawValues(false);
|
||||
normalDistSet.setLineWidth(2f);
|
||||
|
||||
LineData lineData = new LineData(dataSet, normalDistSet);
|
||||
|
||||
chart.setData(lineData);
|
||||
chart.invalidate();
|
||||
|
||||
chart.getDescription().setEnabled(false);
|
||||
chart.setTouchEnabled(false);
|
||||
chart.setDragEnabled(false);
|
||||
chart.setScaleEnabled(false);
|
||||
|
||||
dataSet.setValueTextColor(dropdown_value_text_2);
|
||||
|
||||
chart.getXAxis().setTextColor(chart_text);
|
||||
chart.getAxisLeft().setTextColor(chart_text);
|
||||
chart.getAxisRight().setTextColor(chart_text);
|
||||
|
||||
Legend legend = chart.getLegend();
|
||||
legend.setTextColor(chart_text);
|
||||
|
||||
parent.addView(chart);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void add_history_view(LinearLayout parent, RawDataType[] data){
|
||||
LineChart chart = new LineChart(parent.getContext());
|
||||
FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
layout.height = 350;
|
||||
chart.setLayoutParams(layout);
|
||||
chart.setBackgroundColor(chart_background);
|
||||
|
||||
int min = findMin(data);
|
||||
int max = findMax(data);
|
||||
|
||||
List<Entry> entries = new ArrayList<>();
|
||||
for (int i = 0; i < data.length; i++){
|
||||
if(data[i] == null) continue;
|
||||
if(data[i].isNull()) continue;
|
||||
|
||||
entries.add(new Entry(i, (float)(int) data[i].get()));
|
||||
}
|
||||
|
||||
|
||||
LineDataSet dataSet = new LineDataSet(entries, name);
|
||||
dataSet.setColor(tally_data);
|
||||
dataSet.setValueTextColor(dropdown_value_text_1);
|
||||
dataSet.setDrawCircles(false);
|
||||
dataSet.setDrawValues(false);
|
||||
|
||||
LineData lineData = new LineData(dataSet);
|
||||
|
||||
chart.setData(lineData);
|
||||
chart.invalidate();
|
||||
|
||||
chart.getDescription().setEnabled(false);
|
||||
chart.setTouchEnabled(false);
|
||||
chart.setDragEnabled(false);
|
||||
chart.setScaleEnabled(false);
|
||||
|
||||
dataSet.setValueTextColor(dropdown_value_text_2);
|
||||
|
||||
chart.getXAxis().setTextColor(chart_text);
|
||||
chart.getAxisLeft().setTextColor(chart_text);
|
||||
chart.getAxisRight().setTextColor(chart_text);
|
||||
|
||||
Legend legend = chart.getLegend();
|
||||
legend.setTextColor(chart_text);
|
||||
|
||||
|
||||
chart.getAxisLeft().setAxisMinimum(min);
|
||||
chart.getAxisLeft().setAxisMaximum(max);
|
||||
|
||||
chart.getAxisRight().setAxisMinimum(min);
|
||||
chart.getAxisRight().setAxisMaximum(max);
|
||||
|
||||
|
||||
parent.addView(chart);
|
||||
}
|
||||
|
||||
public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){
|
||||
int[] tmp_abs_bounds = DataProcessing.getNumberBounds(data);
|
||||
int absmin = tmp_abs_bounds[0];
|
||||
int absmax = tmp_abs_bounds[1];
|
||||
|
||||
//(int[]) teamData.get(i).get())[0];
|
||||
// AlertManager.alert("Results","Min: " + min + " Max: " + max);
|
||||
|
||||
parent.removeAllViews();
|
||||
|
||||
List<CandlestickView> views = new ArrayList<>();
|
||||
|
||||
for(Integer teamNum : data.keySet()){
|
||||
CandlestickView candlestickView = new CandlestickView(parent.getContext());
|
||||
candlestickView.fromTeamData(data.get(teamNum), teamNum, absmin, absmax);
|
||||
views.add(candlestickView);
|
||||
}
|
||||
|
||||
|
||||
TableRow row = new TableRow(parent.getContext());
|
||||
|
||||
// Make candlestick chart fill full width
|
||||
parent.setColumnStretchable(1, true);
|
||||
|
||||
// Fill in top left cell
|
||||
row.addView(new View(parent.getContext()));
|
||||
|
||||
CandlestickHeader header = new CandlestickHeader(parent.getContext());
|
||||
header.setScale(absmin, absmax);
|
||||
row.addView(header);
|
||||
|
||||
parent.addView(row);
|
||||
|
||||
// parent.addView(new );
|
||||
|
||||
try {
|
||||
Collections.sort(views, (a, b) -> (int) ((b.average - a.average) * 50.f));
|
||||
}catch (Exception e){}
|
||||
|
||||
for(int i = 0; i < views.size(); i++){
|
||||
row = new TableRow(parent.getContext());
|
||||
CandlestickView view = views.get(i);
|
||||
|
||||
TextView teamNum = new TextView(parent.getContext());
|
||||
TableRow.LayoutParams params = new TableRow.LayoutParams();
|
||||
params.gravity = Gravity.CENTER;
|
||||
teamNum.setLayoutParams(params);
|
||||
teamNum.setPadding(10,10,10,10);
|
||||
teamNum.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline6);
|
||||
teamNum.setText(String.valueOf(view.teamNum));
|
||||
|
||||
|
||||
row.addView(teamNum);
|
||||
row.addView(view);
|
||||
|
||||
parent.addView(row);
|
||||
}
|
||||
}
|
||||
|
||||
public String toString(RawDataType data){
|
||||
return String.valueOf((int) data.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import static com.ridgebotics.ridgescout.utility.DataManager.rescout_list;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -63,7 +64,7 @@ public class FieldDataFragment extends Fragment {
|
||||
|
||||
for (int i = 0; i < filenames.size(); i++) {
|
||||
try {
|
||||
System.out.println("Loading: " + filenames.get(i));
|
||||
Log.i(getClass().toString(), "Loading: " + filenames.get(i));
|
||||
ScoutingDataWriter.ParsedScoutingDataResult psda = ScoutingDataWriter.load(filenames.get(i), match_values, match_transferValues);
|
||||
if (psda.data.array[fieldIndex] != null && psda.data.array[fieldIndex].get() != null && !psda.data.array[fieldIndex].isNull())
|
||||
teamData.add(psda.data.array[fieldIndex]);
|
||||
@@ -75,7 +76,7 @@ public class FieldDataFragment extends Fragment {
|
||||
data.put(teamNum, teamData);
|
||||
}
|
||||
|
||||
System.out.println("Finished!");
|
||||
Log.i(getClass().toString(), "Finished!");
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import static com.ridgebotics.ridgescout.utility.DataManager.event;
|
||||
import android.app.AlertDialog;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -276,7 +277,7 @@ public class EventFragment extends Fragment {
|
||||
builder.setPositiveButton("OK", (dialogInterface, i) -> {
|
||||
|
||||
int index = dropdown.getIndex();
|
||||
System.out.println(index);
|
||||
Log.i(getClass().toString(), String.valueOf(index));
|
||||
if(!(index >= 0 && index < teamNums.size())) return;
|
||||
|
||||
event.teams.remove(index);
|
||||
|
||||
+20
-18
@@ -9,6 +9,7 @@ import static com.ridgebotics.ridgescout.utility.DataManager.event;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -48,8 +49,9 @@ public class MatchScoutingFragment extends Fragment {
|
||||
alliance_position = SettingsManager.getAllyPos();
|
||||
username = SettingsManager.getUsername();
|
||||
|
||||
binding.username.setText(username);
|
||||
binding.alliancePosText.setText(alliance_position);
|
||||
binding.bindicator.setUsername(username);
|
||||
binding.bindicator.setAlliancePos(alliance_position);
|
||||
binding.bindicator.bringToFront();
|
||||
|
||||
binding.matchTeamCard.setVisibility(View.GONE);
|
||||
clear_fields();
|
||||
@@ -65,7 +67,7 @@ public class MatchScoutingFragment extends Fragment {
|
||||
|
||||
|
||||
|
||||
binding.nextButton.setOnClickListener(v -> {
|
||||
binding.bindicator.match_indicator_next_button.setOnClickListener(v -> {
|
||||
if(edited) save();
|
||||
SettingsManager.setMatchNum(cur_match_num+1);
|
||||
cur_match_num += 1;
|
||||
@@ -74,21 +76,21 @@ public class MatchScoutingFragment extends Fragment {
|
||||
});
|
||||
|
||||
if(SettingsManager.getEnableQuickAlliancePosChange())
|
||||
binding.fileIndicator.setOnClickListener(v -> {
|
||||
binding.bindicator.setOnClickListener(v -> {
|
||||
// if(e.getAction() != MotionEvent.ACTION_MOVE) return true;
|
||||
// System.out.println(e.getAxisValue(0));
|
||||
if(edited) save();
|
||||
|
||||
alliance_position = incrementMatchPos(alliance_position);
|
||||
SettingsManager.setAllyPos(alliance_position);
|
||||
binding.alliancePosText.setText(alliance_position);
|
||||
binding.bindicator.setAlliancePos(alliance_position);
|
||||
|
||||
update_match_num();
|
||||
update_scouting_data();
|
||||
// return true;
|
||||
});
|
||||
|
||||
binding.backButton.setOnClickListener(v -> {
|
||||
binding.bindicator.match_indicator_back_button.setOnClickListener(v -> {
|
||||
if(edited) save();
|
||||
SettingsManager.setMatchNum(cur_match_num-1);
|
||||
cur_match_num -= 1;
|
||||
@@ -150,7 +152,7 @@ public class MatchScoutingFragment extends Fragment {
|
||||
|
||||
|
||||
public void save(){
|
||||
System.out.println("Saved!");
|
||||
Log.i(this.getClass().toString(), "Saved!");
|
||||
edited = false;
|
||||
enableRescoutButton();
|
||||
AlertManager.toast("Saved " + filename);
|
||||
@@ -158,7 +160,7 @@ public class MatchScoutingFragment extends Fragment {
|
||||
}
|
||||
|
||||
public void set_indicator_color(int color){
|
||||
binding.fileIndicator.setBackgroundColor(color);
|
||||
binding.bindicator.setColor(color);
|
||||
}
|
||||
|
||||
public void update_asm(){
|
||||
@@ -238,18 +240,18 @@ public class MatchScoutingFragment extends Fragment {
|
||||
|
||||
edited = false;
|
||||
|
||||
binding.matchnum.setText(String.valueOf(cur_match_num+1));
|
||||
binding.bindicator.setMatchNum(String.valueOf(cur_match_num+1));
|
||||
|
||||
if(cur_match_num <= 0){
|
||||
binding.backButton.setVisibility(View.GONE);
|
||||
binding.bindicator.match_indicator_back_button.setVisibility(View.GONE);
|
||||
}else{
|
||||
binding.backButton.setVisibility(View.VISIBLE);
|
||||
binding.bindicator.match_indicator_back_button.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
if(cur_match_num >= event.matches.size()-1){
|
||||
binding.nextButton.setVisibility(View.GONE);
|
||||
binding.bindicator.match_indicator_next_button.setVisibility(View.GONE);
|
||||
}else{
|
||||
binding.nextButton.setVisibility(View.VISIBLE);
|
||||
binding.bindicator.match_indicator_next_button.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +273,7 @@ public class MatchScoutingFragment extends Fragment {
|
||||
break;
|
||||
}
|
||||
|
||||
binding.barTeamNum.setText(String.valueOf(team_num));
|
||||
binding.bindicator.setTeamNum(String.valueOf(team_num));
|
||||
|
||||
frcTeam team = null;
|
||||
for(int i=0; i < event.teams.size(); i++){
|
||||
@@ -377,14 +379,14 @@ public class MatchScoutingFragment extends Fragment {
|
||||
}
|
||||
|
||||
if(ScoutingDataWriter.save(DataManager.match_values.length-1, ScoutingDataWriter.checkAddName(fileUsernames, username), filename, types))
|
||||
System.out.println("Saved!");
|
||||
Log.i(getClass().toString(), "Saved!");
|
||||
else
|
||||
System.out.println("Error saving");
|
||||
Log.i(getClass().toString(), "Error saving");
|
||||
}
|
||||
|
||||
private void enableRescoutButton(){
|
||||
set_indicator_color(rescout ? rescout_color : saved_color);
|
||||
binding.fileIndicator.setOnLongClickListener(v -> {
|
||||
binding.bindicator.setOnLongClickListener(v -> {
|
||||
rescout = !rescout;
|
||||
if(rescout){
|
||||
set_indicator_color(rescout_color);
|
||||
@@ -401,6 +403,6 @@ public class MatchScoutingFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void disableRescoutButton(){
|
||||
binding.fileIndicator.setOnLongClickListener(null);
|
||||
binding.bindicator.setOnLongClickListener(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import static com.ridgebotics.ridgescout.utility.DataManager.pit_transferValues;
|
||||
import static com.ridgebotics.ridgescout.utility.DataManager.pit_values;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -91,10 +92,10 @@ public class PitScoutingFragment extends Fragment {
|
||||
}
|
||||
|
||||
if(ScoutingDataWriter.save(pit_values.length-1, ScoutingDataWriter.checkAddName(fileUsernames, username), filename, types)) {
|
||||
System.out.println("Saved!");
|
||||
AlertManager.toast("Saved " + filename);
|
||||
Log.i(getClass().toString(), "Saved!");
|
||||
Log.i(getClass().toString(), "Saved " + filename);
|
||||
}else
|
||||
System.out.println("Error saving");
|
||||
Log.i(getClass().toString(), "Error saving");
|
||||
}
|
||||
|
||||
public void set_indicator_color(int color){
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.ridgebotics.ridgescout.types.input.NumberType;
|
||||
import com.ridgebotics.ridgescout.types.input.SliderType;
|
||||
import com.ridgebotics.ridgescout.types.input.TallyType;
|
||||
import com.ridgebotics.ridgescout.types.input.TextType;
|
||||
import com.ridgebotics.ridgescout.types.input.ToggleType;
|
||||
import com.ridgebotics.ridgescout.utility.AlertManager;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -29,7 +30,8 @@ public class FieldEditorHelper {
|
||||
paramNumber,
|
||||
paramString,
|
||||
paramStringArray,
|
||||
paramNumberArray
|
||||
paramNumberArray,
|
||||
paramBoolean
|
||||
}
|
||||
|
||||
public static class parameterType {
|
||||
@@ -64,6 +66,15 @@ public class FieldEditorHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static class paramBoolean extends parameterType{
|
||||
public boolean val;
|
||||
public paramBoolean(String name, boolean val){
|
||||
this.name = name + " (Boolean)";
|
||||
this.val = val;
|
||||
this.id = parameterTypeEnum.paramBoolean;
|
||||
}
|
||||
}
|
||||
|
||||
// public static class paramNumberArray extends parameterType{
|
||||
// public int[] val;
|
||||
// public paramNumberArray(String name, int[] val){
|
||||
@@ -113,6 +124,12 @@ public class FieldEditorHelper {
|
||||
new paramNumber("Default Y", 0)
|
||||
};
|
||||
|
||||
public static final parameterType[] defaultToggleParam = new parameterType[]{
|
||||
new paramString("Name", "New Toggle"),
|
||||
new paramString("Description", ""),
|
||||
new paramBoolean("Default true or false",true)
|
||||
};
|
||||
|
||||
|
||||
private static parameterType[] getSliderParams(SliderType s){
|
||||
return new parameterType[]{
|
||||
@@ -173,6 +190,13 @@ public class FieldEditorHelper {
|
||||
new paramNumber("Default Y", ((int[]) s.default_value)[1])
|
||||
};
|
||||
}
|
||||
private static parameterType[] getToggleParams(ToggleType s){
|
||||
return new parameterType[]{
|
||||
new paramString("Name", s.name),
|
||||
new paramString("Description", s.description),
|
||||
new paramBoolean("Default true or false", (boolean) s.default_value)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -224,6 +248,12 @@ public class FieldEditorHelper {
|
||||
};
|
||||
}
|
||||
|
||||
public static void setToggleParam(ToggleType s, parameterType[] types){
|
||||
s.name = ((paramString) types[0]).val;
|
||||
s.description = ((paramString) types[1]).val;
|
||||
s.default_value = ((paramBoolean) types[2]).val;
|
||||
}
|
||||
|
||||
|
||||
private static void setInputParameter(FieldType t, parameterType[] types){
|
||||
switch (t.getInputType()){
|
||||
@@ -248,6 +278,9 @@ public class FieldEditorHelper {
|
||||
case FIELDPOS:
|
||||
setFieldPosParam((FieldposType) t, types);
|
||||
break;
|
||||
case TOGGLE:
|
||||
setToggleParam((ToggleType) t, types);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,6 +302,8 @@ public class FieldEditorHelper {
|
||||
return getCheckboxParam((CheckboxType) t);
|
||||
case FIELDPOS:
|
||||
return getFieldPosParam((FieldposType) t);
|
||||
case TOGGLE:
|
||||
return getToggleParams((ToggleType) t);
|
||||
}
|
||||
return new parameterType[]{};
|
||||
}
|
||||
@@ -305,6 +340,16 @@ public class FieldEditorHelper {
|
||||
));
|
||||
return text;
|
||||
}
|
||||
private static View createBooleanEdit(Context c, boolean value){
|
||||
EditText text = new EditText(c);
|
||||
text.setText(value);
|
||||
text.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
));
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
private static View createEdit(Context c, parameterType t){
|
||||
switch (t.id){
|
||||
@@ -384,6 +429,11 @@ public class FieldEditorHelper {
|
||||
fieldposType.UUID = UUID.randomUUID().toString();
|
||||
setFieldPosParam(fieldposType, defaultFieldPosParam);
|
||||
return fieldposType;
|
||||
case 7:
|
||||
ToggleType toggleType = new ToggleType();
|
||||
toggleType.UUID = UUID.randomUUID().toString();
|
||||
setToggleParam(toggleType, defaultToggleParam);
|
||||
return toggleType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -270,7 +271,7 @@ public class FieldsFragment extends Fragment {
|
||||
|
||||
System.arraycopy(currentValues, 0, newValues, 0, currentValues.length);
|
||||
|
||||
System.out.println("Length: " + values.size());
|
||||
Log.i(getClass().toString(), "Length: " + values.size());
|
||||
|
||||
newValues[currentValues.length] = new FieldType[values.size()];
|
||||
for(int i = 0; i < values.size(); i++) {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.ridgebotics.ridgescout.ui.transfer;
|
||||
import static com.ridgebotics.ridgescout.utility.DataManager.evcode;
|
||||
import static com.ridgebotics.ridgescout.utility.FileEditor.baseDir;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.ridgebotics.ridgescout.utility.AlertManager;
|
||||
import com.ridgebotics.ridgescout.utility.BuiltByteParser;
|
||||
import com.ridgebotics.ridgescout.utility.ByteBuilder;
|
||||
@@ -26,6 +28,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/";
|
||||
@@ -156,13 +159,13 @@ public class FTPSync extends Thread {
|
||||
|
||||
if (remoteTimestamp == null || after(localTimeStamp, remoteTimestamp)) {
|
||||
uploadFile(localFile);
|
||||
System.out.println("Uploaded" + localFile.getName());
|
||||
Log.i(getClass().toString(), "Uploaded" + localFile.getName());
|
||||
|
||||
setLocalFileTimestamp(localFile, curSyncTime);
|
||||
remoteTimestamps.put(localFile.getName(), curSyncTime);
|
||||
upCount++;
|
||||
}else{
|
||||
System.out.println("Did not upload");
|
||||
Log.i(getClass().toString(), "Did not upload");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,17 +194,17 @@ public class FTPSync extends Thread {
|
||||
if (!localFile.exists() || (after(remoteTimestamp, localTimeStamp) && !localTimeStamp.equals(remoteTimestamp))) {
|
||||
downloadFile(remoteFile, localFile);
|
||||
|
||||
System.out.println("Downloaded " + localFile.getName());
|
||||
Log.i(getClass().toString(), "Downloaded " + localFile.getName());
|
||||
|
||||
if(!localFile.exists()) System.out.println("Not exist");
|
||||
else if(after(remoteTimestamp, localTimeStamp)) System.out.println("Before: " + (localTimeStamp.getTime()-remoteTimestamp.getTime()));
|
||||
if(!localFile.exists()) Log.i(getClass().toString(), "Not exist");
|
||||
else if(after(remoteTimestamp, localTimeStamp)) Log.i(getClass().toString(), "Before: " + (localTimeStamp.getTime()-remoteTimestamp.getTime()));
|
||||
|
||||
// Date d = getUtcTimestamp(remoteFile);
|
||||
setLocalFileTimestamp(localFile, remoteTimestamps.get(localFile.getName()));
|
||||
// remoteTimestamps.put(remoteFile, curSyncTime);
|
||||
downCount++;
|
||||
}else{
|
||||
System.out.println("Did not download");
|
||||
Log.i(getClass().toString(), "Did not download");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
package com.ridgebotics.ridgescout.ui.transfer;
|
||||
|
||||
import static com.ridgebotics.ridgescout.utility.FileEditor.baseDir;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
Log.d(getClass().toString(), "LocalFile: " + localFile.filename + ", " + localFile.checksum + ", " + localFile.updated + ": Uploaded");
|
||||
upCount++;
|
||||
}else {
|
||||
Log.d(getClass().toString(), "LocalFile: " + localFile.filename + ", " + localFile.checksum + ", " + localFile.updated + ": Not uploaded");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
Log.d(getClass().toString(), "RemoteFile: " + remoteFile.filename + ", " + remoteFile.checksum + ", " + remoteFile.updated + ": Downloaded");
|
||||
downCount++;
|
||||
} else {
|
||||
Log.d(getClass().toString(), "RemoteFile: " + remoteFile.filename + ", " + remoteFile.checksum + ", " + remoteFile.updated + ": Not downloaded");
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
|
||||
+2
-1
@@ -8,6 +8,7 @@ import android.bluetooth.BluetoothSocket;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
@@ -139,7 +140,7 @@ public class BluetoothReceiver {
|
||||
} catch (IOException e) {
|
||||
if (e.getMessage() != null && e.getMessage().contains("bt socket closed, read return: -1")) {
|
||||
receiveddata.onConnectionStop();
|
||||
System.out.println("Bluetooth socket closed, treating as end of stream");
|
||||
Log.i(getClass().toString(), "Bluetooth socket closed, treating as end of stream");
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
package com.ridgebotics.ridgescout.ui.transfer.bluetooth;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -117,7 +118,7 @@ public class BluetoothReceiverFragment extends Fragment {
|
||||
|
||||
private void receiveData(byte[] data, int bytes) {
|
||||
byte[] newBytes = FileEditor.getByteBlock(data, 0, bytes);
|
||||
System.out.println("Recieved " + bytes + " Bytes over bluetooth!");
|
||||
Log.i(getClass().toString(), "Recieved " + bytes + " Bytes over bluetooth!");
|
||||
recievedBytes.add(newBytes);
|
||||
}
|
||||
|
||||
@@ -138,7 +139,7 @@ public class BluetoothReceiverFragment extends Fragment {
|
||||
ScoutingFile f = ScoutingFile.decode((byte[]) result.get(i).get());
|
||||
|
||||
if (f != null) {
|
||||
System.out.println(f.filename);
|
||||
Log.i(getClass().toString(), f.filename);
|
||||
if (f.write())
|
||||
result_filenames += f.filename + "\n";
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.ridgebotics.ridgescout.types.data.RawDataType;
|
||||
@@ -208,7 +209,7 @@ public class CandlestickView extends View {
|
||||
upperQuartile = DataProcessing.calculatePercentile(teamDataArray, 75);
|
||||
}
|
||||
|
||||
System.out.println(locmin + ", " + lowerQuartile + ", " + avg + ", " + upperQuartile + ", " + locmax);
|
||||
Log.i(getClass().toString(), locmin + ", " + lowerQuartile + ", " + avg + ", " + upperQuartile + ", " + locmax);
|
||||
setData(locmin, lowerQuartile, avg, upperQuartile, locmax, absmin, absmax);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.ridgebotics.ridgescout.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.ridgebotics.ridgescout.R;
|
||||
import com.ridgebotics.ridgescout.types.frcTeam;
|
||||
|
||||
import org.w3c.dom.Text;
|
||||
|
||||
// A view for displaying information about a team.
|
||||
public class MatchScoutingIndicator extends RelativeLayout {
|
||||
public MatchScoutingIndicator(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public MatchScoutingIndicator(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public TextView match_indicator_alliance_pos_text;
|
||||
public TextView match_indicator_bar_team_num;
|
||||
public TextView match_indicator_matchnum;
|
||||
public TextView match_indicator_username;
|
||||
public ImageButton match_indicator_back_button;
|
||||
public ImageButton match_indicator_next_button;
|
||||
private ConstraintLayout box;
|
||||
private View coloredBackground;
|
||||
|
||||
public void init(Context context) {
|
||||
LayoutInflater.from(context).inflate(R.layout.view_match_scouting_indicator, this, true);
|
||||
match_indicator_back_button = findViewById(R.id.match_indicator_back_button);
|
||||
match_indicator_next_button = findViewById(R.id.match_indicator_next_button);
|
||||
match_indicator_alliance_pos_text = findViewById(R.id.match_indicator_alliance_pos_text);
|
||||
match_indicator_username = findViewById(R.id.match_indicator_username);
|
||||
match_indicator_matchnum = findViewById(R.id.match_indicator_matchnum);
|
||||
match_indicator_bar_team_num = findViewById(R.id.match_indicator_bar_team_num);
|
||||
box = findViewById(R.id.file_indicator_box);
|
||||
coloredBackground = findViewById(R.id.match_indicator_background);
|
||||
}
|
||||
|
||||
public void setUsername(String username){
|
||||
match_indicator_username.setText(username);
|
||||
}
|
||||
|
||||
public void setAlliancePos(String alliancePos){
|
||||
match_indicator_alliance_pos_text.setText(alliancePos);
|
||||
}
|
||||
|
||||
public void setMatchNum(String matchNum){
|
||||
match_indicator_matchnum.setText(matchNum);
|
||||
}
|
||||
|
||||
public void setTeamNum(String teamNum){
|
||||
match_indicator_bar_team_num.setText(teamNum);
|
||||
}
|
||||
|
||||
public void setColor(int color){
|
||||
Drawable drawable = box.getBackground();
|
||||
drawable.mutate();
|
||||
drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
|
||||
|
||||
float[] hsv = new float[3];
|
||||
Color.colorToHSV(color,hsv);
|
||||
|
||||
coloredBackground.setBackgroundColor(
|
||||
Color.HSVToColor(220, new float[]{
|
||||
hsv[0],
|
||||
Math.min(hsv[1], 0.75f),
|
||||
Math.min(hsv[2], 0.5f)
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();}
|
||||
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="800dp"
|
||||
android:height="800dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:pathData="M14,5v10l-9,-5 9,-5z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="800dp"
|
||||
android:height="800dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:pathData="M15,10l-9,5V5l9,5z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
@@ -5,16 +5,28 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<com.ridgebotics.ridgescout.ui.views.MatchScoutingIndicator
|
||||
android:id="@+id/bindicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.0">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/MatchScoutArea"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="48dp">
|
||||
|
||||
@@ -25,16 +37,15 @@
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_margin="5dp"
|
||||
android:background="@drawable/border"
|
||||
android:padding="10dp"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/scouting_notice_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
|
||||
android:text="Scouting Notice">
|
||||
</TextView>
|
||||
android:text="Scouting Notice"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"></TextView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -46,75 +57,4 @@
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/file_indicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#60ff0000"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/back_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Back"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/next_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Next"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/alliance_pos_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Temp"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/matchnum"
|
||||
app:layout_constraintStart_toEndOf="@+id/back_button"
|
||||
app:layout_constraintTop_toBottomOf="@id/username" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Temp"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toStartOf="@+id/matchnum"
|
||||
app:layout_constraintStart_toEndOf="@+id/back_button"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/matchnum"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Temp"
|
||||
android:textAlignment="center"
|
||||
android:textSize="24sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bar_team_num"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Temp"
|
||||
android:textSize="24sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/next_button"
|
||||
app:layout_constraintStart_toEndOf="@+id/matchnum"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/file_indicator_box"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="5dp"
|
||||
android:background="@drawable/border"
|
||||
android:padding="3dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<View
|
||||
android:id="@+id/match_indicator_background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/match_indicator_back_button"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/triangle_left"
|
||||
android:text="Back"
|
||||
android:background="@color/zxing_transparent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/match_indicator_next_button"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/triangle_right"
|
||||
android:text="Next"
|
||||
android:background="@color/zxing_transparent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/match_indicator_alliance_pos_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="Temp"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/match_indicator_next_button"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/match_indicator_username"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:text="Temp"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/match_indicator_back_button"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/match_indicator_matchnum"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Temp"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/match_indicator_bar_team_num"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="3dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Temp"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/match_indicator_matchnum"
|
||||
app:layout_constraintStart_toEndOf="@+id/match_indicator_matchnum"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</RelativeLayout>
|
||||
+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