Skip to content

Commit a165b6b

Browse files
committed
Add devdock TUS upload example
1 parent 729c107 commit a165b6b

2 files changed

Lines changed: 181 additions & 0 deletions

File tree

example/build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,11 @@ apply plugin: 'java'
22

33
dependencies {
44
implementation fileTree(dir: 'libs', include: ['*.jar'])
5+
implementation 'org.json:json:20240303'
56
implementation rootProject
67
}
8+
9+
tasks.register('api2DevdockTusUpload', JavaExec) {
10+
classpath = sourceSets.main.runtimeClasspath
11+
mainClass = 'io.tus.java.example.Api2DevdockTusUpload'
12+
}
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package io.tus.java.example;
2+
3+
import io.tus.java.client.ProtocolException;
4+
import io.tus.java.client.TusClient;
5+
import io.tus.java.client.TusURLMemoryStore;
6+
import io.tus.java.client.TusUpload;
7+
import io.tus.java.client.TusUploader;
8+
import org.json.JSONArray;
9+
import org.json.JSONObject;
10+
11+
import java.io.ByteArrayInputStream;
12+
import java.io.IOException;
13+
import java.net.URL;
14+
import java.nio.charset.StandardCharsets;
15+
import java.nio.file.Files;
16+
import java.nio.file.Paths;
17+
import java.util.LinkedHashMap;
18+
import java.util.Map;
19+
20+
public final class Api2DevdockTusUpload {
21+
public static void main(String[] args) {
22+
try {
23+
System.setProperty("http.strictPostRedirect", "true");
24+
25+
final JSONObject scenario = loadScenario();
26+
final JSONObject createResponse = scenario.getJSONObject("prepared").getJSONObject("createResponse");
27+
final String uploadUrl = uploadWithTus(scenario, createResponse);
28+
29+
System.out.println(
30+
"Java TUS SDK devdock scenario "
31+
+ scenario.getString("scenarioId")
32+
+ " uploaded to "
33+
+ uploadUrl
34+
);
35+
} catch (Exception e) {
36+
e.printStackTrace();
37+
System.exit(1);
38+
}
39+
}
40+
41+
private static JSONObject loadScenario() throws IOException {
42+
String scenarioPath = System.getenv("API2_SDK_EXAMPLE_SCENARIO");
43+
if (scenarioPath == null || scenarioPath.isEmpty()) {
44+
scenarioPath = "example/api2-scenario.json";
45+
}
46+
47+
final byte[] contents = Files.readAllBytes(Paths.get(scenarioPath));
48+
return new JSONObject(new String(contents, StandardCharsets.UTF_8));
49+
}
50+
51+
private static String uploadWithTus(
52+
JSONObject scenario,
53+
JSONObject createResponse
54+
) throws IOException, ProtocolException {
55+
final JSONObject uploadConfig = scenario.getJSONObject("upload");
56+
final Object endpointValue = resolveValue(uploadConfig.getJSONObject("tusUrl"), scenario, createResponse);
57+
final byte[] content = scenarioBytes(uploadConfig);
58+
59+
final TusClient client = new TusClient();
60+
client.setUploadCreationURL(new URL(scalarString(endpointValue)));
61+
client.enableResuming(new TusURLMemoryStore());
62+
63+
final TusUpload upload = new TusUpload();
64+
upload.setInputStream(new ByteArrayInputStream(content));
65+
upload.setSize(content.length);
66+
upload.setFingerprint(scenario.getString("scenarioId") + "-java-devdock-example");
67+
upload.setMetadata(uploadMetadata(uploadConfig, scenario, createResponse));
68+
69+
final TusUploader uploader = client.resumeOrCreateUpload(upload);
70+
uploader.setChunkSize(content.length);
71+
while (uploader.uploadChunk() > -1) {
72+
// Continue until the client reports that the source is fully uploaded.
73+
}
74+
uploader.finish();
75+
76+
if (uploader.getOffset() != content.length) {
77+
throw new IllegalStateException(
78+
"remote offset " + uploader.getOffset() + ", expected " + content.length
79+
);
80+
}
81+
if (uploader.getUploadURL() == null) {
82+
throw new IllegalStateException("upload did not return a URL");
83+
}
84+
85+
return uploader.getUploadURL().toString();
86+
}
87+
88+
private static byte[] scenarioBytes(JSONObject uploadConfig) {
89+
final JSONObject source = uploadConfig.getJSONObject("source");
90+
final String kind = source.getString("kind");
91+
if (!"bytes".equals(kind)) {
92+
throw new IllegalArgumentException("unsupported source kind " + kind);
93+
}
94+
95+
final String encoding = source.getString("encoding");
96+
if (!"utf8".equals(encoding)) {
97+
throw new IllegalArgumentException("unsupported source encoding " + encoding);
98+
}
99+
100+
return source.getString("value").getBytes(StandardCharsets.UTF_8);
101+
}
102+
103+
private static Map<String, String> uploadMetadata(
104+
JSONObject uploadConfig,
105+
JSONObject scenario,
106+
JSONObject createResponse
107+
) {
108+
final JSONArray fields = uploadConfig.getJSONArray("metadata");
109+
final Map<String, String> metadata = new LinkedHashMap<String, String>();
110+
for (int index = 0; index < fields.length(); index++) {
111+
final JSONObject field = fields.getJSONObject(index);
112+
metadata.put(
113+
field.getString("name"),
114+
scalarString(resolveValue(field.getJSONObject("value"), scenario, createResponse))
115+
);
116+
}
117+
118+
return metadata;
119+
}
120+
121+
private static Object resolveValue(
122+
JSONObject valueSpec,
123+
JSONObject scenario,
124+
JSONObject createResponse
125+
) {
126+
if (valueSpec.has("value")) {
127+
return valueSpec.get("value");
128+
}
129+
130+
final JSONObject source = valueSpec.getJSONObject("source");
131+
final String root = source.getString("root");
132+
final Object rootValue;
133+
if ("scenario".equals(root)) {
134+
rootValue = scenario;
135+
} else if ("createResponse".equals(root)) {
136+
rootValue = createResponse;
137+
} else {
138+
throw new IllegalArgumentException("unsupported scenario value root " + root);
139+
}
140+
141+
return readPath(rootValue, source.getJSONArray("path"));
142+
}
143+
144+
private static Object readPath(Object value, JSONArray pathParts) {
145+
Object current = value;
146+
for (int index = 0; index < pathParts.length(); index++) {
147+
final Object part = pathParts.get(index);
148+
if (current instanceof JSONObject && part instanceof String) {
149+
current = ((JSONObject) current).get((String) part);
150+
continue;
151+
}
152+
153+
if (current instanceof JSONArray && part instanceof Number) {
154+
current = ((JSONArray) current).get(((Number) part).intValue());
155+
continue;
156+
}
157+
158+
throw new IllegalArgumentException("cannot read scenario path part " + part);
159+
}
160+
161+
return current;
162+
}
163+
164+
private static String scalarString(Object value) {
165+
if (JSONObject.NULL.equals(value)) {
166+
return "null";
167+
}
168+
169+
return String.valueOf(value);
170+
}
171+
172+
private Api2DevdockTusUpload() {
173+
throw new IllegalStateException("Utility class");
174+
}
175+
}

0 commit comments

Comments
 (0)