big change: move the json loading and random server selection to main PieCannon class (so that it has a reason to live :)) and implement loading the json file in the android app.

I deliberated between re-using the json file in the android app and using a sqlite database or some such thing, given that a UI for editing these will have to be implemented at some point. I figure the json file is more config than data, and probably should be as consistent as possible between platforms.

For now at least this makes the app usable for me, without having to hard-code my server credentials.
This commit is contained in:
Adrian Kuschelyagi Malacoda 2020-10-30 06:52:53 -05:00
parent 432cfa4d21
commit da39586c4d
19 changed files with 225 additions and 74 deletions

View File

@ -21,6 +21,6 @@ TODO
TODO
## How to use
Place a `servers.json` in your `XDG_DATA_HOME`, by default this would be `~/.local/share/piecannon` (create this directory if it does not exist. See `servers.example.json` for an example of such a file. Whenever you initiate a file upload, Pie Cannon will select one of your defined servers at random and upload the file.
Place a `servers.json` in the Pie Cannon data directory. For GNU/Linux this is `XDG_DATA_HOME` (by default this would be `~/.local/share/piecannon`) (create this directory if it does not exist. For Android this would be the app's "externalFilesDir" (probably something like `Android/data/net.monarchpass.piecannon/files` - the app will tell you when you launch it). See `servers.example.json` for an example of such a file. Whenever you initiate a file upload, Pie Cannon will select one of your defined servers at random and upload the file.
For SFTP servers (the only supported type as of now), the `path` is relative to your home directory and should be where `url` points to. i.e. a file uploaded to `path` should be downloadable at `url`. In the future Pie Cannon should be able to get SSH credentials from your agent so you won't need to put a password in this file.

View File

@ -6,8 +6,9 @@
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/jcraft/jsch/0.1.55/jsch-0.1.55.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/jcraft/jsch/0.1.55/jsch-0.1.55.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/jodd/jodd-util/6.0.0/jodd-util-6.0.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/android/android/4.1.1.4/android-4.1.1.4.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/httpcomponents/httpclient/4.0.1/httpclient-4.0.1.jar" enabled="true" runInBatchMode="false"/>

View File

@ -34,11 +34,6 @@
<artifactId>jodd-util</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>com.google.android</groupId>
<artifactId>android</artifactId>

View File

@ -1,12 +1,22 @@
package net.monarchpass.piecannon;
import java.io.File;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class Main extends Activity {
@Override
public void onCreate (final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final File serversJson = new File(getExternalFilesDir(null), "servers.json");
final TextView text = (TextView)findViewById(R.id.welcome);
text.append("\n---\n");
text.append(serversJson.exists() ? "servers.json file found at: " : "Please place a servers.json file at: ");
text.append("\n");
text.append(serversJson.getPath());
}
}

View File

@ -10,6 +10,7 @@ import android.content.ContentResolver;
import com.google.common.io.ByteSource;
import java.net.URI;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.io.StringWriter;
@ -42,25 +43,34 @@ public class Send extends Activity {
public void uploadImage (final Uri imageUri) {
// handle image
final TextView text = (TextView)findViewById(R.id.text);
text.setText(imageUri.toString());
text.append("\n---\nReceived file url: \n");
text.append(imageUri.toString());
final ContentResolver resolver = getContentResolver();
final Server testServer = new TestServer();
final PieCannon cannon = new PieCannon();
try
{
cannon.loadServersFrom(new File(getExternalFilesDir(null), "servers.json"));
}
catch (final Exception exception)
{
logException(exception);
return;
}
executor.submit(() -> {
try
{
final URI uploaded = testServer.upload(createFileNameForUri(imageUri, resolver), new ContentByteSource(resolver, imageUri));
final URI uploaded = cannon.selectServer().upload(createFileNameForUri(imageUri, resolver), new ContentByteSource(resolver, imageUri));
runOnUiThread(() -> {
text.setText(uploaded.toString());
text.append("\n---\nUploaded file to url: \n");
text.append(uploaded.toString());
shareUploadUrl(uploaded);
});
}
catch (final Exception exception) {
final StringWriter buf = new StringWriter();
final PrintWriter out = new PrintWriter(buf);
exception.printStackTrace(out);
runOnUiThread(() -> text.setText(buf.toString()));
logException(exception);
}
});
}
@ -87,6 +97,17 @@ public class Send extends Activity {
throw new RuntimeException("Could not construct a file name");
}
private void logException (final Exception exception) {
final StringWriter buf = new StringWriter();
final PrintWriter out = new PrintWriter(buf);
exception.printStackTrace(out);
runOnUiThread(() -> {
final TextView text = (TextView)findViewById(R.id.text);
text.append("\n---\n");
text.append(buf.toString());
});
}
@AllArgsConstructor
public static class ContentByteSource extends ByteSource {
private final ContentResolver resolver;

View File

@ -1,18 +0,0 @@
package net.monarchpass.piecannon;
import net.monarchpass.piecannon.impl.SftpServer;
import java.net.URI;
public class TestServer extends SftpServer {
public TestServer () {
super(
"Test Server",
"",
22,
"",
"",
"",
URI.create("")
);
}
}

View File

@ -5,4 +5,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="net.monarchpass.piecannon.Main">
<TextView android:id="@+id/welcome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Welcome to Pie Cannon! There is no GUI for editing servers yet." />
</ScrollView>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="net.monarchpass.piecannon.Send">
<TextView android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pie Cannon at work!" />
</ScrollView>

View File

@ -27,11 +27,6 @@
<artifactId>xdg-java</artifactId>
<version>0.1.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>

View File

@ -3,34 +3,22 @@ package net.monarchpass.piecannon;
import java.net.URI;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.Random;
import java.util.List;
import java.util.ArrayList;
import java.util.stream.Collectors;
import org.freedesktop.BaseDirectory;
import com.google.common.collect.Streams;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import lombok.extern.java.Log;
import java.util.logging.Level;
import net.monarchpass.piecannon.util.ServerFactory;
@Log
public class App {
public static void main (final String... args) throws Exception {
final File serversJson = getServersJson();
final List<Server> servers = loadServersFrom(serversJson);
final PieCannon cannon = new PieCannon();
final List<Server> servers = cannon.loadServersFrom(serversJson);
log.log(Level.INFO, "{0} servers loaded from {1}", new Object[] {
servers.size(), serversJson
});
@ -40,7 +28,7 @@ public class App {
System.exit(1);
}
final Server server = servers.get(new Random().nextInt(servers.size()));
final Server server = cannon.selectServer();
log.log(Level.INFO, "Randomly selected server: {0}", server.getLabel());
if (args.length == 0) {
@ -57,23 +45,7 @@ public class App {
final URI result = server.upload(source);
System.out.println(result);
}
public static List<Server> loadServersFrom (final File serversJson) throws IOException {
if (!serversJson.exists()) {
return Collections.EMPTY_LIST;
}
final ServerFactory factory = new ServerFactory();
try (final InputStream in = new FileInputStream(serversJson)) {
final JsonParser parser = new JsonParser();
return Streams.stream(parser.parse(new InputStreamReader(in)).getAsJsonArray())
.filter(JsonElement::isJsonObject)
.map(JsonObject.class::cast)
.map(factory)
.collect(Collectors.toList());
}
}
public static File getServersJson () {
return new File(getDataDirectory(), "servers.json");
}

44
lib/.classpath Normal file
View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="target/generated-sources/annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

11
lib/.factorypath Normal file
View File

@ -0,0 +1,11 @@
<factorypath>
<factorypathentry kind="VARJAR" id="M2_REPO/com/github/mwiede/jsch/0.1.60/jsch-0.1.60.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/guava/guava/30.0-android/guava-30.0-android.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/errorprone/error_prone_annotations/2.3.4/error_prone_annotations-2.3.4.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/projectlombok/lombok/1.18.16/lombok-1.18.16.jar" enabled="true" runInBatchMode="false"/>
</factorypath>

34
lib/.project Normal file
View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>libpiecannon</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
<filteredResources>
<filter>
<id>1603692553450</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

View File

@ -0,0 +1,4 @@
eclipse.preferences.version=1
org.eclipse.jdt.apt.aptEnabled=true
org.eclipse.jdt.apt.genSrcDir=target/generated-sources/annotations
org.eclipse.jdt.apt.genTestSrcDir=target/generated-test-sources/test-annotations

View File

@ -0,0 +1,9 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
org.eclipse.jdt.core.compiler.processAnnotations=enabled
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=1.8

View File

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

View File

@ -22,6 +22,11 @@
<artifactId>guava</artifactId>
<version>30.0-android</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>

View File

@ -2,18 +2,62 @@ package net.monarchpass.piecannon;
import java.net.URI;
import java.io.File;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
import com.google.common.base.Charsets;
import com.google.common.io.CharSource;
import com.google.common.io.ByteStreams;
public class PieCannon {
import java.util.stream.Collectors;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.monarchpass.piecannon.util.ServerFactory;
public class PieCannon {
public List<Server> servers;
public List<Server> loadServersFrom (final File serversJson) throws IOException {
try (final InputStream in = new FileInputStream(serversJson)) {
return loadServersFrom(in);
}
}
public List<Server> loadServersFrom (final InputStream in) throws IOException {
final ServerFactory factory = new ServerFactory();
final JsonParser parser = new JsonParser();
final List<Server> loadedServers = new ArrayList<>();
for (final JsonElement element : parser.parse(new InputStreamReader(in)).getAsJsonArray()) {
if (!element.isJsonObject()) {
continue;
}
loadedServers.add(factory.apply((JsonObject)element));
}
servers = loadedServers;
return loadedServers;
}
public Server selectServer () {
if (servers == null) {
throw new IllegalStateException("Servers are not loaded");
}
return servers.get(new Random().nextInt(servers.size()));
}
public static boolean testServer (final Server server) {
final String testString = "piecannon-test-" + System.currentTimeMillis();
final URI result = server.upload("piecannon.test", CharSource.wrap(testString).asByteSource(Charsets.UTF_8));

View File

@ -1,10 +1,11 @@
package net.monarchpass.piecannon;
package net.monarchpass.piecannon.util;
import java.net.URI;
import java.util.function.Function;
import com.google.gson.JsonObject;
import net.monarchpass.piecannon.Server;
import net.monarchpass.piecannon.impl.SftpServer;
public class ServerFactory implements Function<JsonObject, Server> {